[FORMAT] Run php-cs-fixer in php-gettext

This commit is contained in:
Diogo Cordeiro 2019-06-23 17:36:15 +01:00
parent a1edc2c6a9
commit b408208e4c
12 changed files with 1026 additions and 470 deletions

View File

@ -0,0 +1,41 @@
PACKAGE = php-gettext-$(VERSION)
VERSION = 1.0.12
DIST_FILES = \
gettext.php \
gettext.inc \
streams.php \
AUTHORS \
README \
COPYING \
Makefile \
examples/index.php \
examples/pigs_dropin.php \
examples/pigs_fallback.php \
examples/locale/sr_CS/LC_MESSAGES/messages.po \
examples/locale/sr_CS/LC_MESSAGES/messages.mo \
examples/locale/de_CH/LC_MESSAGES/messages.po \
examples/locale/de_CH/LC_MESSAGES/messages.mo \
examples/update \
tests/LocalesTest.php \
tests/ParsingTest.php
check:
phpunit --verbose tests
dist: check
if [ -d $(PACKAGE) ]; then \
rm -rf $(PACKAGE); \
fi; \
mkdir $(PACKAGE); \
if [ -d $(PACKAGE) ]; then \
cp -rp --parents $(DIST_FILES) $(PACKAGE); \
tar cvzf $(PACKAGE).tar.gz $(PACKAGE); \
rm -rf $(PACKAGE); \
fi;
sign: dist
gpg --armor --sign --detach-sig $(PACKAGE).tar.gz
clean:
rm -f $(PACKAGE).tar.gz $(PACKAGE).tar.gz.asc

View File

@ -1,4 +1,4 @@
PHP-gettext 1.0 (https://launchpad.net/php-gettext) PHP-gettext 1.0.12 (https://launchpad.net/php-gettext)
Copyright 2003, 2006, 2009 -- Danilo "angry with PHP[1]" Segan Copyright 2003, 2006, 2009 -- Danilo "angry with PHP[1]" Segan
Licensed under GPLv2 (or any later version, see COPYING) Licensed under GPLv2 (or any later version, see COPYING)
@ -28,7 +28,7 @@ Why?
I got used to having gettext work even without gettext I got used to having gettext work even without gettext
library. It's there in my favourite language Python, so I was library. It's there in my favourite language Python, so I was
surprised that I couldn't find it in PHP. I even searched for it, surprised that I couldn't find it in PHP. I even Googled for it,
but to no avail. but to no avail.
So, I said, what the heck, I'm going to write it for this So, I said, what the heck, I'm going to write it for this

View File

@ -0,0 +1,27 @@
<html>
<head>
<title>PHP-gettext examples</title>
</head>
<body>
<h1>PHP-gettext</h1>
<h2>Introduction</h2>
<p>PHP-gettext provides a simple gettext replacement that works independently from the system's gettext abilities.
It can read MO files and use them for translating strings.</p>
<p>This version has the ability to cache all strings and translations to speed up the string lookup.
While the cache is enabled by default, it can be switched off with the second parameter in the constructor (e.g. when using very large MO files
that you don't want to keep in memory)</p>
<h2>Examples</h2>
<ul>
<li><a href="pigs_dropin.php">PHP-gettext as a dropin replacement</a></li>
<li><a href="pigs_fallback.php">PHP-gettext as a fallback solution</a></li>
</ul>
<hr />
<p>Copyright (c) 2003-2006 Danilo Segan</p>
<p>Copyright (c) 2005-2006 Steven Armstrong</p>
</body>
</html>

View File

@ -0,0 +1,30 @@
# Sample translation for PHP-gettext 1.0
# Copyright (c) 2003 Danilo Segan <danilo@kvota.net>
#
msgid ""
msgstr ""
"Project-Id-Version: pigs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2003-10-23 04:50+0200\n"
"PO-Revision-Date: 2003-11-01 23:40+0100\n"
"Last-Translator: Danilo Segan <danilo@kvota.net>\n"
"Language-Team: Serbian (sr) <danilo@kvota.net>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#"Plural-Forms: nplurals=2; plural=n != 1;\n"
#: pigs.php:19
msgid ""
"This is how the story goes.\n"
"\n"
msgstr ""
"Und so geht die Geschichte.\n"
"\n"
#: pigs.php:21
#, php-format
msgid "%d pig went to the market\n"
msgid_plural "%d pigs went to the market\n"
msgstr[0] "%d Schwein ging zum Markt\n"
msgstr[1] "%d Schweine gingen zum Markt\n"

View File

@ -0,0 +1,30 @@
# Sample translation for PHP-gettext 1.0
# Copyright (c) 2003,2006 Danilo Segan <danilo@kvota.net>
#
msgid ""
msgstr ""
"Project-Id-Version: pigs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2003-10-23 04:50+0200\n"
"PO-Revision-Date: 2006-02-02 21:06+0100\n"
"Last-Translator: Danilo Segan <danilo@kvota.net>\n"
"Language-Team: Serbian (sr) <danilo@kvota.net>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
#: pigs.php:19
msgid ""
"This is how the story goes.\n"
"\n"
msgstr "Овако иде прича.\n\n"
#: pigs.php:21
#, php-format
msgid "%d pig went to the market\n"
msgid_plural "%d pigs went to the market\n"
msgstr[0] "%d мало прасе је отишло на пијац\n"
msgstr[1] "%d мала прасета су отишла на пијац\n"
msgstr[2] "%d малих прасића је отишло на пијац\n"

View File

@ -0,0 +1,94 @@
<?php
/*
Copyright (c) 2003,2004,2005,2009 Danilo Segan <danilo@kvota.net>.
Copyright (c) 2005,2006 Steven Armstrong <sa@c-area.ch>
This file is part of PHP-gettext.
PHP-gettext is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
PHP-gettext is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PHP-gettext; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
error_reporting(E_ALL | E_STRICT);
// define constants
define('PROJECT_DIR', realpath('./'));
define('LOCALE_DIR', PROJECT_DIR .'/locale');
define('DEFAULT_LOCALE', 'en_US');
require_once('../gettext.inc');
$supported_locales = array('en_US', 'sr_CS', 'de_CH');
$encoding = 'UTF-8';
$locale = (isset($_GET['lang']))? $_GET['lang'] : DEFAULT_LOCALE;
// gettext setup
T_setlocale(LC_MESSAGES, $locale);
// Set the text domain as 'messages'
$domain = 'messages';
bindtextdomain($domain, LOCALE_DIR);
// bind_textdomain_codeset is supported only in PHP 4.2.0+
if (function_exists('bind_textdomain_codeset')) {
bind_textdomain_codeset($domain, $encoding);
}
textdomain($domain);
header("Content-type: text/html; charset=$encoding");
?>
<html>
<head>
<title>PHP-gettext dropin example</title>
</head>
<body>
<h1>PHP-gettext as a dropin replacement</h1>
<p>Example showing how to use PHP-gettext as a dropin replacement for the native gettext library.</p>
<?php
print "<p>";
foreach ($supported_locales as $l) {
print "[<a href=\"?lang=$l\">$l</a>] ";
}
print "</p>\n";
if (!locale_emulation()) {
print "<p>locale '$locale' is supported by your system, using native gettext implementation.</p>\n";
} else {
print "<p>locale '$locale' is _not_ supported on your system, using the default locale '". DEFAULT_LOCALE ."'.</p>\n";
}
?>
<hr />
<?php
// using PHP-gettext
print "<pre>";
print _("This is how the story goes.\n\n");
for ($number=6; $number>=0; $number--) {
print sprintf(
T_ngettext(
"%d pig went to the market\n",
"%d pigs went to the market\n",
$number
),
$number
);
}
print "</pre>\n";
?>
<hr />
<p>&laquo; <a href="./">back</a></p>
</body>
</html>

View File

@ -0,0 +1,92 @@
<?php
/*
Copyright (c) 2003,2004,2005,2009 Danilo Segan <danilo@kvota.net>.
Copyright (c) 2005,2006 Steven Armstrong <sa@c-area.ch>
This file is part of PHP-gettext.
PHP-gettext is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
PHP-gettext is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PHP-gettext; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
error_reporting(E_ALL | E_STRICT);
// define constants
define('PROJECT_DIR', realpath('./'));
define('LOCALE_DIR', PROJECT_DIR .'/locale');
define('DEFAULT_LOCALE', 'en_US');
require_once('../gettext.inc');
$supported_locales = array('en_US', 'sr_CS', 'de_CH');
$encoding = 'UTF-8';
$locale = (isset($_GET['lang']))? $_GET['lang'] : DEFAULT_LOCALE;
// gettext setup
T_setlocale(LC_MESSAGES, $locale);
// Set the text domain as 'messages'
$domain = 'messages';
T_bindtextdomain($domain, LOCALE_DIR);
T_bind_textdomain_codeset($domain, $encoding);
T_textdomain($domain);
header("Content-type: text/html; charset=$encoding");
?>
<html>
<head>
<title>PHP-gettext fallback example</title>
</head>
<body>
<h1>PHP-gettext as a fallback solution</h1>
<p>Example showing how to use PHP-gettext as a fallback solution if the native gettext library is not available or the system does not support the requested locale.</p>
<?php
print "<p>";
foreach ($supported_locales as $l) {
print "[<a href=\"?lang=$l\">$l</a>] ";
}
print "</p>\n";
if (!locale_emulation()) {
print "<p>locale '$locale' is supported by your system, using native gettext implementation.</p>\n";
} else {
print "<p>locale '$locale' is <strong>not</strong> supported on your system, using custom gettext implementation.</p>\n";
}
?>
<hr />
<?php
// using PHP-gettext
print "<pre>";
print T_("This is how the story goes.\n\n");
for ($number=6; $number>=0; $number--) {
print sprintf(
T_ngettext(
"%d pig went to the market\n",
"%d pigs went to the market\n",
$number
),
$number
);
}
print "</pre>\n";
?>
<hr />
<p>&laquo; <a href="./">back</a></p>
</body>
</html>

View File

@ -0,0 +1,14 @@
#!/bin/sh
TEMPLATE=pigs.pot
xgettext -kT_ngettext:1,2 -kT_ -L PHP -o $TEMPLATE pigs_dropin.php
if [ "x$1" = "x-p" ]; then
msgfmt --statistics $TEMPLATE
else
if [ -f $1.po ]; then
msgmerge -o .tmp$1.po $1.po $TEMPLATE
mv .tmp$1.po $1.po
msgfmt --statistics $1.po
else
echo "Usage: $0 [-p|<basename>]"
fi
fi

713
extlib/php-gettext/gettext.php Normal file → Executable file
View File

@ -33,259 +33,278 @@
* second parameter in the constructor (e.g. whenusing very large MO files * second parameter in the constructor (e.g. whenusing very large MO files
* that you don't want to keep in memory) * that you don't want to keep in memory)
*/ */
class gettext_reader { class gettext_reader
//public: {
var $error = 0; // public variable that holds error code (0 if no error) //public:
public $error = 0; // public variable that holds error code (0 if no error)
//private: //private:
var $BYTEORDER = 0; // 0: low endian, 1: big endian public $BYTEORDER = 0; // 0: low endian, 1: big endian
var $STREAM = NULL; public $STREAM = null;
var $short_circuit = false; public $short_circuit = false;
var $enable_cache = false; public $enable_cache = false;
var $originals = NULL; // offset of original table public $originals = null; // offset of original table
var $translations = NULL; // offset of translation table public $translations = null; // offset of translation table
var $pluralheader = NULL; // cache header field for plural forms public $pluralheader = null; // cache header field for plural forms
var $total = 0; // total string count public $total = 0; // total string count
var $table_originals = NULL; // table for original strings (offsets) public $table_originals = null; // table for original strings (offsets)
var $table_translations = NULL; // table for translated strings (offsets) public $table_translations = null; // table for translated strings (offsets)
var $cache_translations = NULL; // original -> translation mapping public $cache_translations = null; // original -> translation mapping
/* Methods */ /* Methods */
/** /**
* Reads a 32bit Integer from the Stream * Reads a 32bit Integer from the Stream
* *
* @access private * @access private
* @return Integer from the Stream * @return Integer from the Stream
*/ */
function readint() { public function readint()
if ($this->BYTEORDER == 0) { {
// low endian if ($this->BYTEORDER == 0) {
$input=unpack('V', $this->STREAM->read(4)); // low endian
return array_shift($input); $input=unpack('V', $this->STREAM->read(4));
} else { return array_shift($input);
// big endian } else {
$input=unpack('N', $this->STREAM->read(4)); // big endian
return array_shift($input); $input=unpack('N', $this->STREAM->read(4));
} return array_shift($input);
}
} }
function read($bytes) { public function read($bytes)
return $this->STREAM->read($bytes); {
} return $this->STREAM->read($bytes);
/**
* Reads an array of Integers from the Stream
*
* @param int count How many elements should be read
* @return Array of Integers
*/
function readintarray($count) {
if ($this->BYTEORDER == 0) {
// low endian
return unpack('V'.$count, $this->STREAM->read(4 * $count));
} else {
// big endian
return unpack('N'.$count, $this->STREAM->read(4 * $count));
}
}
/**
* Constructor
*
* @param object Reader the StreamReader object
* @param boolean enable_cache Enable or disable caching of strings (default on)
*/
function gettext_reader($Reader, $enable_cache = true) {
// If there isn't a StreamReader, turn on short circuit mode.
if (! $Reader || isset($Reader->error) ) {
$this->short_circuit = true;
return;
} }
// Caching can be turned off /**
$this->enable_cache = $enable_cache; * Reads an array of Integers from the Stream
*
$MAGIC1 = "\x95\x04\x12\xde"; * @param int count How many elements should be read
$MAGIC2 = "\xde\x12\x04\x95"; * @return Array of Integers
*/
$this->STREAM = $Reader; public function readintarray($count)
$magic = $this->read(4); {
if ($magic == $MAGIC1) { if ($this->BYTEORDER == 0) {
$this->BYTEORDER = 1; // low endian
} elseif ($magic == $MAGIC2) { return unpack('V'.$count, $this->STREAM->read(4 * $count));
$this->BYTEORDER = 0; } else {
} else { // big endian
$this->error = 1; // not MO file return unpack('N'.$count, $this->STREAM->read(4 * $count));
return false; }
} }
// FIXME: Do we care about revision? We should. /**
$revision = $this->readint(); * Constructor
*
* @param object Reader the StreamReader object
* @param boolean enable_cache Enable or disable caching of strings (default on)
*/
public function gettext_reader($Reader, $enable_cache = true)
{
// If there isn't a StreamReader, turn on short circuit mode.
if (! $Reader || isset($Reader->error)) {
$this->short_circuit = true;
return;
}
$this->total = $this->readint(); // Caching can be turned off
$this->originals = $this->readint(); $this->enable_cache = $enable_cache;
$this->translations = $this->readint();
}
/** $MAGIC1 = "\x95\x04\x12\xde";
* Loads the translation tables from the MO file into the cache $MAGIC2 = "\xde\x12\x04\x95";
* If caching is enabled, also loads all strings into a cache
* to speed up translation lookups $this->STREAM = $Reader;
* $magic = $this->read(4);
* @access private if ($magic == $MAGIC1) {
*/ $this->BYTEORDER = 1;
function load_tables() { } elseif ($magic == $MAGIC2) {
if (is_array($this->cache_translations) && $this->BYTEORDER = 0;
} else {
$this->error = 1; // not MO file
return false;
}
// FIXME: Do we care about revision? We should.
$revision = $this->readint();
$this->total = $this->readint();
$this->originals = $this->readint();
$this->translations = $this->readint();
}
/**
* Loads the translation tables from the MO file into the cache
* If caching is enabled, also loads all strings into a cache
* to speed up translation lookups
*
* @access private
*/
public function load_tables()
{
if (is_array($this->cache_translations) &&
is_array($this->table_originals) && is_array($this->table_originals) &&
is_array($this->table_translations)) is_array($this->table_translations)) {
return; return;
}
/* get original and translations tables */ /* get original and translations tables */
if (!is_array($this->table_originals)) { if (!is_array($this->table_originals)) {
$this->STREAM->seekto($this->originals); $this->STREAM->seekto($this->originals);
$this->table_originals = $this->readintarray($this->total * 2); $this->table_originals = $this->readintarray($this->total * 2);
} }
if (!is_array($this->table_translations)) { if (!is_array($this->table_translations)) {
$this->STREAM->seekto($this->translations); $this->STREAM->seekto($this->translations);
$this->table_translations = $this->readintarray($this->total * 2); $this->table_translations = $this->readintarray($this->total * 2);
}
if ($this->enable_cache) {
$this->cache_translations = array();
/* read all strings in the cache */
for ($i = 0; $i < $this->total; $i++) {
$this->STREAM->seekto($this->table_originals[$i * 2 + 2]);
$original = $this->STREAM->read($this->table_originals[$i * 2 + 1]);
$this->STREAM->seekto($this->table_translations[$i * 2 + 2]);
$translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]);
$this->cache_translations[$original] = $translation;
}
}
} }
if ($this->enable_cache) { /**
$this->cache_translations = array (); * Returns a string from the "originals" table
/* read all strings in the cache */ *
for ($i = 0; $i < $this->total; $i++) { * @access private
$this->STREAM->seekto($this->table_originals[$i * 2 + 2]); * @param int num Offset number of original string
$original = $this->STREAM->read($this->table_originals[$i * 2 + 1]); * @return string Requested string if found, otherwise ''
$this->STREAM->seekto($this->table_translations[$i * 2 + 2]); */
$translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]); public function get_original_string($num)
$this->cache_translations[$original] = $translation; {
} $length = $this->table_originals[$num * 2 + 1];
$offset = $this->table_originals[$num * 2 + 2];
if (! $length) {
return '';
}
$this->STREAM->seekto($offset);
$data = $this->STREAM->read($length);
return (string)$data;
} }
}
/** /**
* Returns a string from the "originals" table * Returns a string from the "translations" table
* *
* @access private * @access private
* @param int num Offset number of original string * @param int num Offset number of original string
* @return string Requested string if found, otherwise '' * @return string Requested string if found, otherwise ''
*/ */
function get_original_string($num) { public function get_translation_string($num)
$length = $this->table_originals[$num * 2 + 1]; {
$offset = $this->table_originals[$num * 2 + 2]; $length = $this->table_translations[$num * 2 + 1];
if (! $length) $offset = $this->table_translations[$num * 2 + 2];
return ''; if (! $length) {
$this->STREAM->seekto($offset); return '';
$data = $this->STREAM->read($length); }
return (string)$data; $this->STREAM->seekto($offset);
} $data = $this->STREAM->read($length);
return (string)$data;
/**
* Returns a string from the "translations" table
*
* @access private
* @param int num Offset number of original string
* @return string Requested string if found, otherwise ''
*/
function get_translation_string($num) {
$length = $this->table_translations[$num * 2 + 1];
$offset = $this->table_translations[$num * 2 + 2];
if (! $length)
return '';
$this->STREAM->seekto($offset);
$data = $this->STREAM->read($length);
return (string)$data;
}
/**
* Binary search for string
*
* @access private
* @param string string
* @param int start (internally used in recursive function)
* @param int end (internally used in recursive function)
* @return int string number (offset in originals table)
*/
function find_string($string, $start = -1, $end = -1) {
if (($start == -1) or ($end == -1)) {
// find_string is called with only one parameter, set start end end
$start = 0;
$end = $this->total;
} }
if (abs($start - $end) <= 1) {
// We're done, now we either found the string, or it doesn't exist /**
$txt = $this->get_original_string($start); * Binary search for string
if ($string == $txt) *
return $start; * @access private
else * @param string string
return -1; * @param int start (internally used in recursive function)
} else if ($start > $end) { * @param int end (internally used in recursive function)
// start > end -> turn around and start over * @return int string number (offset in originals table)
return $this->find_string($string, $end, $start); */
} else { public function find_string($string, $start = -1, $end = -1)
// Divide table in two parts {
$half = (int)(($start + $end) / 2); if (($start == -1) or ($end == -1)) {
$cmp = strcmp($string, $this->get_original_string($half)); // find_string is called with only one parameter, set start end end
if ($cmp == 0) $start = 0;
// string is exactly in the middle => return it $end = $this->total;
return $half; }
else if ($cmp < 0) if (abs($start - $end) <= 1) {
// The string is in the upper half // We're done, now we either found the string, or it doesn't exist
return $this->find_string($string, $start, $half); $txt = $this->get_original_string($start);
else if ($string == $txt) {
// The string is in the lower half return $start;
return $this->find_string($string, $half, $end); } else {
return -1;
}
} elseif ($start > $end) {
// start > end -> turn around and start over
return $this->find_string($string, $end, $start);
} else {
// Divide table in two parts
$half = (int)(($start + $end) / 2);
$cmp = strcmp($string, $this->get_original_string($half));
if ($cmp == 0) {
// string is exactly in the middle => return it
return $half;
} elseif ($cmp < 0) {
// The string is in the upper half
return $this->find_string($string, $start, $half);
} else {
// The string is in the lower half
return $this->find_string($string, $half, $end);
}
}
} }
}
/** /**
* Translates a string * Translates a string
* *
* @access public * @access public
* @param string string to be translated * @param string string to be translated
* @return string translated string (or original, if not found) * @return string translated string (or original, if not found)
*/ */
function translate($string) { public function translate($string)
if ($this->short_circuit) {
return $string; if ($this->short_circuit) {
$this->load_tables(); return $string;
}
$this->load_tables();
if ($this->enable_cache) { if ($this->enable_cache) {
// Caching enabled, get translated string from cache // Caching enabled, get translated string from cache
if (array_key_exists($string, $this->cache_translations)) if (array_key_exists($string, $this->cache_translations)) {
return $this->cache_translations[$string]; return $this->cache_translations[$string];
else } else {
return $string; return $string;
} else { }
// Caching not enabled, try to find string } else {
$num = $this->find_string($string); // Caching not enabled, try to find string
if ($num == -1) $num = $this->find_string($string);
return $string; if ($num == -1) {
else return $string;
return $this->get_translation_string($num); } else {
return $this->get_translation_string($num);
}
}
} }
}
/** /**
* Sanitize plural form expression for use in PHP eval call. * Sanitize plural form expression for use in PHP eval call.
* *
* @access private * @access private
* @return string sanitized plural form expression * @return string sanitized plural form expression
*/ */
function sanitize_plural_expression($expr) { public function sanitize_plural_expression($expr)
// Get rid of disallowed characters. {
$expr = preg_replace('@[^a-zA-Z0-9_:;\(\)\?\|\&=!<>+*/\%-]@', '', $expr); // Get rid of disallowed characters.
$expr = preg_replace('@[^a-zA-Z0-9_:;\(\)\?\|\&=!<>+*/\%-]@', '', $expr);
// Add parenthesis for tertiary '?' operator. // Add parenthesis for tertiary '?' operator.
$expr .= ';'; $expr .= ';';
$res = ''; $res = '';
$p = 0; $p = 0;
for ($i = 0; $i < strlen($expr); $i++) { for ($i = 0; $i < strlen($expr); $i++) {
$ch = $expr[$i]; $ch = $expr[$i];
switch ($ch) { switch ($ch) {
case '?': case '?':
$res .= ' ? ('; $res .= ' ? (';
$p++; $p++;
@ -294,143 +313,151 @@ class gettext_reader {
$res .= ') : ('; $res .= ') : (';
break; break;
case ';': case ';':
$res .= str_repeat( ')', $p) . ';'; $res .= str_repeat(')', $p) . ';';
$p = 0; $p = 0;
break; break;
default: default:
$res .= $ch; $res .= $ch;
} }
}
return $res;
} }
return $res;
}
/** /**
* Parse full PO header and extract only plural forms line. * Parse full PO header and extract only plural forms line.
* *
* @access private * @access private
* @return string verbatim plural form header field * @return string verbatim plural form header field
*/ */
function extract_plural_forms_header_from_po_header($header) { public function extract_plural_forms_header_from_po_header($header)
if (preg_match("/(^|\n)plural-forms: ([^\n]*)\n/i", $header, $regs)) {
$expr = $regs[2]; if (preg_match("/(^|\n)plural-forms: ([^\n]*)\n/i", $header, $regs)) {
else $expr = $regs[2];
$expr = "nplurals=2; plural=n == 1 ? 0 : 1;"; } else {
return $expr; $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
} }
return $expr;
/**
* Get possible plural forms from MO header
*
* @access private
* @return string plural form header
*/
function get_plural_forms() {
// lets assume message number 0 is header
// this is true, right?
$this->load_tables();
// cache header field for plural forms
if (! is_string($this->pluralheader)) {
if ($this->enable_cache) {
$header = $this->cache_translations[""];
} else {
$header = $this->get_translation_string(0);
}
$expr = $this->extract_plural_forms_header_from_po_header($header);
$this->pluralheader = $this->sanitize_plural_expression($expr);
} }
return $this->pluralheader;
}
/** /**
* Detects which plural form to take * Get possible plural forms from MO header
* *
* @access private * @access private
* @param n count * @return string plural form header
* @return int array index of the right plural form */
*/ public function get_plural_forms()
function select_string($n) { {
if (!is_int($n)) { // lets assume message number 0 is header
throw new InvalidArgumentException( // this is true, right?
"Select_string only accepts integers: " . $n); $this->load_tables();
// cache header field for plural forms
if (! is_string($this->pluralheader)) {
if ($this->enable_cache) {
$header = $this->cache_translations[""];
} else {
$header = $this->get_translation_string(0);
}
$expr = $this->extract_plural_forms_header_from_po_header($header);
$this->pluralheader = $this->sanitize_plural_expression($expr);
}
return $this->pluralheader;
} }
$string = $this->get_plural_forms();
$string = str_replace('nplurals',"\$total",$string);
$string = str_replace("n",$n,$string);
$string = str_replace('plural',"\$plural",$string);
$total = 0; /**
$plural = 0; * Detects which plural form to take
*
* @access private
* @param n count
* @return int array index of the right plural form
*/
public function select_string($n)
{
if (!is_int($n)) {
throw new InvalidArgumentException(
"Select_string only accepts integers: " . $n
);
}
$string = $this->get_plural_forms();
$string = str_replace('nplurals', "\$total", $string);
$string = str_replace("n", $n, $string);
$string = str_replace('plural', "\$plural", $string);
eval("$string"); $total = 0;
if ($plural >= $total) $plural = $total - 1; $plural = 0;
return $plural;
}
/** eval("$string");
* Plural version of gettext if ($plural >= $total) {
* $plural = $total - 1;
* @access public }
* @param string single
* @param string plural
* @param string number
* @return translated plural form
*/
function ngettext($single, $plural, $number) {
if ($this->short_circuit) {
if ($number != 1)
return $plural; return $plural;
else
return $single;
} }
// find out the appropriate form /**
$select = $this->select_string($number); * Plural version of gettext
*
* @access public
* @param string single
* @param string plural
* @param string number
* @return translated plural form
*/
public function ngettext($single, $plural, $number)
{
if ($this->short_circuit) {
if ($number != 1) {
return $plural;
} else {
return $single;
}
}
// this should contains all strings separated by NULLs // find out the appropriate form
$key = $single . chr(0) . $plural; $select = $this->select_string($number);
// this should contains all strings separated by NULLs
$key = $single . chr(0) . $plural;
if ($this->enable_cache) { if ($this->enable_cache) {
if (! array_key_exists($key, $this->cache_translations)) { if (! array_key_exists($key, $this->cache_translations)) {
return ($number != 1) ? $plural : $single; return ($number != 1) ? $plural : $single;
} else { } else {
$result = $this->cache_translations[$key]; $result = $this->cache_translations[$key];
$list = explode(chr(0), $result); $list = explode(chr(0), $result);
return $list[$select]; return $list[$select];
} }
} else { } else {
$num = $this->find_string($key); $num = $this->find_string($key);
if ($num == -1) { if ($num == -1) {
return ($number != 1) ? $plural : $single; return ($number != 1) ? $plural : $single;
} else { } else {
$result = $this->get_translation_string($num); $result = $this->get_translation_string($num);
$list = explode(chr(0), $result); $list = explode(chr(0), $result);
return $list[$select]; return $list[$select];
} }
} }
}
function pgettext($context, $msgid) {
$key = $context . chr(4) . $msgid;
$ret = $this->translate($key);
if (strpos($ret, "\004") !== FALSE) {
return $msgid;
} else {
return $ret;
}
}
function npgettext($context, $singular, $plural, $number) {
$key = $context . chr(4) . $singular;
$ret = $this->ngettext($key, $plural, $number);
if (strpos($ret, "\004") !== FALSE) {
return $singular;
} else {
return $ret;
} }
} public function pgettext($context, $msgid)
{
$key = $context . chr(4) . $msgid;
$ret = $this->translate($key);
if (strpos($ret, "\004") !== false) {
return $msgid;
} else {
return $ret;
}
}
public function npgettext($context, $singular, $plural, $number)
{
$key = $context . chr(4) . $singular;
$ret = $this->ngettext($key, $plural, $number);
if (strpos($ret, "\004") !== false) {
return $singular;
} else {
return $ret;
}
}
} }
?>

View File

@ -23,145 +23,161 @@
// Simple class to wrap file streams, string streams, etc. // Simple class to wrap file streams, string streams, etc.
// seek is essential, and it should be byte stream // seek is essential, and it should be byte stream
class StreamReader { class StreamReader
// should return a string [FIXME: perhaps return array of bytes?] {
function read($bytes) { // should return a string [FIXME: perhaps return array of bytes?]
return false; public function read($bytes)
} {
// should return new position
function seekto($position) {
return false;
}
// returns current position
function currentpos() {
return false;
}
// returns length of entire stream (limit for seekto()s)
function length() {
return false;
}
};
class StringReader {
var $_pos;
var $_str;
function StringReader($str='') {
$this->_str = $str;
$this->_pos = 0;
}
function read($bytes) {
$data = substr($this->_str, $this->_pos, $bytes);
$this->_pos += $bytes;
if (strlen($this->_str)<$this->_pos)
$this->_pos = strlen($this->_str);
return $data;
}
function seekto($pos) {
$this->_pos = $pos;
if (strlen($this->_str)<$this->_pos)
$this->_pos = strlen($this->_str);
return $this->_pos;
}
function currentpos() {
return $this->_pos;
}
function length() {
return strlen($this->_str);
}
};
class FileReader {
var $_pos;
var $_fd;
var $_length;
function FileReader($filename) {
if (file_exists($filename)) {
$this->_length=filesize($filename);
$this->_pos = 0;
$this->_fd = fopen($filename,'rb');
if (!$this->_fd) {
$this->error = 3; // Cannot read file, probably permissions
return false; return false;
}
} else {
$this->error = 2; // File doesn't exist
return false;
} }
}
function read($bytes) { // should return new position
if ($bytes) { public function seekto($position)
fseek($this->_fd, $this->_pos); {
return false;
}
// PHP 5.1.1 does not read more than 8192 bytes in one fread() // returns current position
// the discussions at PHP Bugs suggest it's the intended behaviour public function currentpos()
$data = ''; {
while ($bytes > 0) { return false;
$chunk = fread($this->_fd, $bytes); }
$data .= $chunk;
$bytes -= strlen($chunk);
}
$this->_pos = ftell($this->_fd);
return $data; // returns length of entire stream (limit for seekto()s)
} else return ''; public function length()
} {
return false;
}
};
function seekto($pos) { class StringReader
fseek($this->_fd, $pos); {
$this->_pos = ftell($this->_fd); public $_pos;
return $this->_pos; public $_str;
}
function currentpos() { public function StringReader($str='')
return $this->_pos; {
} $this->_str = $str;
$this->_pos = 0;
}
function length() { public function read($bytes)
return $this->_length; {
} $data = substr($this->_str, $this->_pos, $bytes);
$this->_pos += $bytes;
if (strlen($this->_str)<$this->_pos) {
$this->_pos = strlen($this->_str);
}
function close() { return $data;
fclose($this->_fd); }
}
public function seekto($pos)
{
$this->_pos = $pos;
if (strlen($this->_str)<$this->_pos) {
$this->_pos = strlen($this->_str);
}
return $this->_pos;
}
public function currentpos()
{
return $this->_pos;
}
public function length()
{
return strlen($this->_str);
}
};
class FileReader
{
public $_pos;
public $_fd;
public $_length;
public function FileReader($filename)
{
if (file_exists($filename)) {
$this->_length=filesize($filename);
$this->_pos = 0;
$this->_fd = fopen($filename, 'rb');
if (!$this->_fd) {
$this->error = 3; // Cannot read file, probably permissions
return false;
}
} else {
$this->error = 2; // File doesn't exist
return false;
}
}
public function read($bytes)
{
if ($bytes) {
fseek($this->_fd, $this->_pos);
// PHP 5.1.1 does not read more than 8192 bytes in one fread()
// the discussions at PHP Bugs suggest it's the intended behaviour
$data = '';
while ($bytes > 0) {
$chunk = fread($this->_fd, $bytes);
$data .= $chunk;
$bytes -= strlen($chunk);
}
$this->_pos = ftell($this->_fd);
return $data;
} else {
return '';
}
}
public function seekto($pos)
{
fseek($this->_fd, $pos);
$this->_pos = ftell($this->_fd);
return $this->_pos;
}
public function currentpos()
{
return $this->_pos;
}
public function length()
{
return $this->_length;
}
public function close()
{
fclose($this->_fd);
}
}; };
// Preloads entire file in memory first, then creates a StringReader // Preloads entire file in memory first, then creates a StringReader
// over it (it assumes knowledge of StringReader internals) // over it (it assumes knowledge of StringReader internals)
class CachedFileReader extends StringReader { class CachedFileReader extends StringReader
function CachedFileReader($filename) { {
if (file_exists($filename)) { public function CachedFileReader($filename)
{
if (file_exists($filename)) {
$length=filesize($filename);
$fd = fopen($filename, 'rb');
$length=filesize($filename); if (!$fd) {
$fd = fopen($filename,'rb'); $this->error = 3; // Cannot read file, probably permissions
return false;
if (!$fd) { }
$this->error = 3; // Cannot read file, probably permissions $this->_str = fread($fd, $length);
return false; fclose($fd);
} } else {
$this->_str = fread($fd, $length); $this->error = 2; // File doesn't exist
fclose($fd); return false;
}
} else {
$this->error = 2; // File doesn't exist
return false;
} }
}
}; };
?>

View File

@ -0,0 +1,88 @@
<?php
require_once('gettext.inc');
class LocaleTest extends PHPUnit_Framework_TestCase
{
public function test_setlocale()
{
putenv("LC_ALL=");
// _setlocale defaults to a locale name from environment variable LANG.
putenv("LANG=sr_RS");
$this->assertEquals('sr_RS', _setlocale(LC_MESSAGES, 0));
}
public function test_setlocale_system()
{
putenv("LC_ALL=");
// For an existing locale, it never needs emulation.
putenv("LANG=C");
_setlocale(LC_MESSAGES, "");
$this->assertEquals(0, locale_emulation());
}
public function test_setlocale_emulation()
{
putenv("LC_ALL=");
// If we set it to a non-existent locale, it still works, but uses
// emulation.
_setlocale(LC_MESSAGES, "xxx_XXX");
$this->assertEquals('xxx_XXX', _setlocale(LC_MESSAGES, 0));
$this->assertEquals(1, locale_emulation());
}
public function test_get_list_of_locales()
{
// For a locale containing country code, we prefer
// full locale name, but if that's not found, fall back
// to the language only locale name.
$this->assertEquals(
array("sr_RS", "sr"),
get_list_of_locales("sr_RS")
);
// If language code is used, it's the only thing returned.
$this->assertEquals(
array("sr"),
get_list_of_locales("sr")
);
// There is support for language and charset only.
$this->assertEquals(
array("sr.UTF-8", "sr"),
get_list_of_locales("sr.UTF-8")
);
// It can also split out character set from the full locale name.
$this->assertEquals(
array("sr_RS.UTF-8", "sr_RS", "sr"),
get_list_of_locales("sr_RS.UTF-8")
);
// There is support for @modifier in locale names as well.
$this->assertEquals(
array("sr_RS.UTF-8@latin", "sr_RS@latin", "sr@latin",
"sr_RS.UTF-8", "sr_RS", "sr"),
get_list_of_locales("sr_RS.UTF-8@latin")
);
// We can pass in only language and modifier.
$this->assertEquals(
array("sr@latin", "sr"),
get_list_of_locales("sr@latin")
);
// If locale name is not following the regular POSIX pattern,
// it's used verbatim.
$this->assertEquals(
array("something"),
get_list_of_locales("something")
);
// Passing in an empty string returns an empty array.
$this->assertEquals(
array(),
get_list_of_locales("")
);
}
}

View File

@ -0,0 +1,97 @@
<?php
class ParsingTest extends PHPUnit_Framework_TestCase
{
public function test_extract_plural_forms_header_from_po_header()
{
$parser = new gettext_reader(null);
// It defaults to a "Western-style" plural header.
$this->assertEquals(
'nplurals=2; plural=n == 1 ? 0 : 1;',
$parser->extract_plural_forms_header_from_po_header("")
);
// Extracting it from the middle of the header works.
$this->assertEquals(
'nplurals=1; plural=0;',
$parser->extract_plural_forms_header_from_po_header(
"Content-type: text/html; charset=UTF-8\n"
."Plural-Forms: nplurals=1; plural=0;\n"
."Last-Translator: nobody\n"
)
);
// It's also case-insensitive.
$this->assertEquals(
'nplurals=1; plural=0;',
$parser->extract_plural_forms_header_from_po_header(
"PLURAL-forms: nplurals=1; plural=0;\n"
)
);
// It falls back to default if it's not on a separate line.
$this->assertEquals(
'nplurals=2; plural=n == 1 ? 0 : 1;',
$parser->extract_plural_forms_header_from_po_header(
"Content-type: text/html; charset=UTF-8" // note the missing \n here
."Plural-Forms: nplurals=1; plural=0;\n"
."Last-Translator: nobody\n"
)
);
}
/**
* @expectedException InvalidArgumentException
*/
public function test_select_string_disallows_nonint_numbers()
{
$pofile_data = ''
."msgid \"\"\n"
."msgstr \"\"\n"
."\"Content-Type: text/plain; charset=utf-8\\n\"\n"
."\"Plural-Forms: nplurals=2; plural= n == 1 ? 0 : 1;\\n\"\n";
$mofile = tempnam(sys_get_temp_dir(), "pg");
$msgfmt = popen("msgfmt -o $mofile -", "w");
fwrite($msgfmt, $pofile_data);
pclose($msgfmt);
$modata = new CachedFileReader($mofile);
unlink($mofile);
$parser = new gettext_reader($modata);
// It defaults to a "Western-style" plural header.
$this->assertEquals(
'nplurals=2; plural=n == 1 ? 0 : 1;',
$parser->extract_plural_forms_header_from_po_header("")
);
$new_tempfile = tempnam(sys_get_temp_dir(), "pg");
$parser->select_string(
"(file_put_contents('$new_tempfile', 'boom'))"
);
$this->assertEquals("", file_get_contents($new_tempfile));
unlink($new_tempfile);
}
/**
* @dataProvider data_provider_test_npgettext
*/
public function test_npgettext($number, $expected)
{
$parser = new gettext_reader(null);
$result = $parser->npgettext(
"context",
"%d pig went to the market\n",
"%d pigs went to the market\n",
$number
);
$this->assertSame($expected, $result);
}
public static function data_provider_test_npgettext()
{
return array(
array(1, "%d pig went to the market\n"),
array(2, "%d pigs went to the market\n"),
);
}
}