- implemented php side of plural translation
- using it to generate labels dynamically for the expire options (deprecating the [expire_labels] configuration). - added translation of the human readable data sizes to support the french octet - fixed IEC label for kibibytes
This commit is contained in:
parent
c83ba8256f
commit
b060d57524
9 changed files with 154 additions and 50 deletions
12
cfg/conf.ini
12
cfg/conf.ini
|
@ -57,18 +57,6 @@ default = "1week"
|
||||||
1year = 31536000
|
1year = 31536000
|
||||||
never = 0
|
never = 0
|
||||||
|
|
||||||
[expire_labels]
|
|
||||||
; descriptive labels for the expiration times
|
|
||||||
; must match those in [expire_options]
|
|
||||||
5min = "5 minutes"
|
|
||||||
10min = "10 minutes"
|
|
||||||
1hour = "1 hour"
|
|
||||||
1day = "1 day"
|
|
||||||
1week = "1 week"
|
|
||||||
1month = "1 month"
|
|
||||||
1year = "1 year"
|
|
||||||
never = "Never"
|
|
||||||
|
|
||||||
[traffic]
|
[traffic]
|
||||||
; time limit between calls from the same IP address in seconds
|
; time limit between calls from the same IP address in seconds
|
||||||
; Set this to 0 to disable rate limiting.
|
; Set this to 0 to disable rate limiting.
|
||||||
|
|
11
i18n/fr.json
11
i18n/fr.json
|
@ -123,5 +123,14 @@
|
||||||
"Could not create paste: %s":
|
"Could not create paste: %s":
|
||||||
"Could not create paste: %s",
|
"Could not create paste: %s",
|
||||||
"Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)":
|
"Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)":
|
||||||
"Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)"
|
"Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)",
|
||||||
|
"B": "o",
|
||||||
|
"KiB": "Kio",
|
||||||
|
"MiB": "Mio",
|
||||||
|
"GiB": "Gio",
|
||||||
|
"TiB": "Tio",
|
||||||
|
"PiB": "Pio",
|
||||||
|
"EiB": "Eio",
|
||||||
|
"ZiB": "Zio",
|
||||||
|
"YiB": "Yio"
|
||||||
}
|
}
|
||||||
|
|
|
@ -647,7 +647,7 @@ $(function() {
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
var source = $(event.target),
|
var source = $(event.target),
|
||||||
commentid = event.data.commentid
|
commentid = event.data.commentid,
|
||||||
hint = i18n._('Optional nickname...');
|
hint = i18n._('Optional nickname...');
|
||||||
|
|
||||||
// Remove any other reply area.
|
// Remove any other reply area.
|
||||||
|
|
|
@ -33,7 +33,36 @@ class filter
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* format a given number of bytes
|
* format a given time string into a human readable label (localized)
|
||||||
|
*
|
||||||
|
* accepts times in the format "[integer][time unit]"
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @static
|
||||||
|
* @param string $time
|
||||||
|
* @throws Exception
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function time_humanreadable($time)
|
||||||
|
{
|
||||||
|
if (preg_match('/^(\d+) *(\w+)$/', $time, $matches) !== 1) {
|
||||||
|
throw new Exception("Error parsing time format '$time'", 30);
|
||||||
|
}
|
||||||
|
switch ($matches[2]) {
|
||||||
|
case 'sec':
|
||||||
|
$unit = 'second';
|
||||||
|
break;
|
||||||
|
case 'min':
|
||||||
|
$unit = 'minute';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$unit = rtrim($matches[2], 's');
|
||||||
|
}
|
||||||
|
return i18n::_(array('%d ' . $unit, '%d ' . $unit . 's'), (int) $matches[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* format a given number of bytes in IEC 80000-13:2008 notation (localized)
|
||||||
*
|
*
|
||||||
* @access public
|
* @access public
|
||||||
* @static
|
* @static
|
||||||
|
@ -42,13 +71,13 @@ class filter
|
||||||
*/
|
*/
|
||||||
public static function size_humanreadable($size)
|
public static function size_humanreadable($size)
|
||||||
{
|
{
|
||||||
$iec = array('B', 'kiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB');
|
$iec = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB');
|
||||||
$i = 0;
|
$i = 0;
|
||||||
while ( ( $size / 1024 ) >= 1 ) {
|
while ( ( $size / 1024 ) >= 1 ) {
|
||||||
$size = $size / 1024;
|
$size = $size / 1024;
|
||||||
$i++;
|
$i++;
|
||||||
}
|
}
|
||||||
return number_format($size, ($i ? 2 : 0), '.', ' ') . ' ' . $iec[$i];
|
return number_format($size, ($i ? 2 : 0), '.', ' ') . ' ' . i18n::_($iec[$i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
57
lib/i18n.php
57
lib/i18n.php
|
@ -17,14 +17,23 @@
|
||||||
*/
|
*/
|
||||||
class i18n
|
class i18n
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* language
|
||||||
|
*
|
||||||
|
* @access protected
|
||||||
|
* @static
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected static $_language = 'en';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* translation cache
|
* translation cache
|
||||||
*
|
*
|
||||||
* @access private
|
* @access protected
|
||||||
* @static
|
* @static
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private static $_translations = array();
|
protected static $_translations = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* translate a string, alias for translate()
|
* translate a string, alias for translate()
|
||||||
|
@ -53,12 +62,30 @@ class i18n
|
||||||
{
|
{
|
||||||
if (empty($messageId)) return $messageId;
|
if (empty($messageId)) return $messageId;
|
||||||
if (count(self::$_translations) === 0) self::loadTranslations();
|
if (count(self::$_translations) === 0) self::loadTranslations();
|
||||||
|
$messages = $messageId;
|
||||||
|
if (is_array($messageId))
|
||||||
|
{
|
||||||
|
$messageId = count($messageId) > 1 ? $messageId[1] : $messageId[0];
|
||||||
|
}
|
||||||
if (!array_key_exists($messageId, self::$_translations))
|
if (!array_key_exists($messageId, self::$_translations))
|
||||||
{
|
{
|
||||||
self::$_translations[$messageId] = $messageId;
|
self::$_translations[$messageId] = $messages;
|
||||||
}
|
}
|
||||||
$args = func_get_args();
|
$args = func_get_args();
|
||||||
$args[0] = self::$_translations[$messageId];
|
if (is_array(self::$_translations[$messageId]))
|
||||||
|
{
|
||||||
|
$number = (int) $args[1];
|
||||||
|
$key = self::_getPluralForm($number);
|
||||||
|
$max = count(self::$_translations[$messageId]) - 1;
|
||||||
|
if ($key > $max) $key = $max;
|
||||||
|
|
||||||
|
$args[0] = self::$_translations[$messageId][$key];
|
||||||
|
$args[1] = $number;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$args[0] = self::$_translations[$messageId];
|
||||||
|
}
|
||||||
return call_user_func_array('sprintf', $args);
|
return call_user_func_array('sprintf', $args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,6 +118,7 @@ class i18n
|
||||||
// load translations
|
// load translations
|
||||||
if ($match != 'en')
|
if ($match != 'en')
|
||||||
{
|
{
|
||||||
|
self::$_language = $match;
|
||||||
self::$_translations = json_decode(
|
self::$_translations = json_decode(
|
||||||
file_get_contents($path . DIRECTORY_SEPARATOR . $match . '.json'),
|
file_get_contents($path . DIRECTORY_SEPARATOR . $match . '.json'),
|
||||||
true
|
true
|
||||||
|
@ -137,6 +165,27 @@ class i18n
|
||||||
return $languages;
|
return $languages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* determines the plural form to use based on current language and given number
|
||||||
|
*
|
||||||
|
* From: http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html
|
||||||
|
*
|
||||||
|
* @param int $n
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected static function _getPluralForm($n)
|
||||||
|
{
|
||||||
|
switch (self::$_language) {
|
||||||
|
case 'fr':
|
||||||
|
return ($n > 1 ? 1 : 0);
|
||||||
|
case 'pl':
|
||||||
|
return ($n == 1 ? 0 : $n%10 >= 2 && $n %10 <=4 && ($n%100 < 10 || $n%100 >= 20) ? 1 : 2);
|
||||||
|
// en, de
|
||||||
|
default:
|
||||||
|
return ($n != 1 ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* compares two language preference arrays and returns the preferred match
|
* compares two language preference arrays and returns the preferred match
|
||||||
*
|
*
|
||||||
|
|
|
@ -579,12 +579,8 @@ class zerobin
|
||||||
|
|
||||||
// label all the expiration options
|
// label all the expiration options
|
||||||
$expire = array();
|
$expire = array();
|
||||||
foreach ($this->_conf['expire_options'] as $key => $value) {
|
foreach ($this->_conf['expire_options'] as $time => $seconds) {
|
||||||
$expire[$key] = i18n::_(
|
$expire[$time] = ($seconds == 0) ? i18n::_(ucfirst($time)): filter::time_humanreadable($time);
|
||||||
array_key_exists($key, $this->_conf['expire_labels']) ?
|
|
||||||
$this->_conf['expire_labels'][$key] :
|
|
||||||
$key
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$page = new RainTPL;
|
$page = new RainTPL;
|
||||||
|
|
|
@ -9,14 +9,31 @@ class filterTest extends PHPUnit_Framework_TestCase
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testFilterMakesTimesHumanlyReadable()
|
||||||
|
{
|
||||||
|
$this->assertEquals('5 minutes', filter::time_humanreadable('5min'));
|
||||||
|
$this->assertEquals('90 seconds', filter::time_humanreadable('90sec'));
|
||||||
|
$this->assertEquals('1 week', filter::time_humanreadable('1week'));
|
||||||
|
$this->assertEquals('6 months', filter::time_humanreadable('6months'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException Exception
|
||||||
|
* @expectedExceptionCode 30
|
||||||
|
*/
|
||||||
|
public function testFilterFailTimesHumanlyReadable()
|
||||||
|
{
|
||||||
|
filter::time_humanreadable('five_minutes');
|
||||||
|
}
|
||||||
|
|
||||||
public function testFilterMakesSizesHumanlyReadable()
|
public function testFilterMakesSizesHumanlyReadable()
|
||||||
{
|
{
|
||||||
$this->assertEquals('1 B', filter::size_humanreadable(1));
|
$this->assertEquals('1 B', filter::size_humanreadable(1));
|
||||||
$this->assertEquals('1 000 B', filter::size_humanreadable(1000));
|
$this->assertEquals('1 000 B', filter::size_humanreadable(1000));
|
||||||
$this->assertEquals('1.00 kiB', filter::size_humanreadable(1024));
|
$this->assertEquals('1.00 KiB', filter::size_humanreadable(1024));
|
||||||
$this->assertEquals('1.21 kiB', filter::size_humanreadable(1234));
|
$this->assertEquals('1.21 KiB', filter::size_humanreadable(1234));
|
||||||
$exponent = 1024;
|
$exponent = 1024;
|
||||||
$this->assertEquals('1 000.00 kiB', filter::size_humanreadable(1000 * $exponent));
|
$this->assertEquals('1 000.00 KiB', filter::size_humanreadable(1000 * $exponent));
|
||||||
$this->assertEquals('1.00 MiB', filter::size_humanreadable(1024 * $exponent));
|
$this->assertEquals('1.00 MiB', filter::size_humanreadable(1024 * $exponent));
|
||||||
$this->assertEquals('1.21 MiB', filter::size_humanreadable(1234 * $exponent));
|
$this->assertEquals('1.21 MiB', filter::size_humanreadable(1234 * $exponent));
|
||||||
$exponent *= 1024;
|
$exponent *= 1024;
|
||||||
|
|
30
tst/i18n.php
30
tst/i18n.php
|
@ -25,11 +25,39 @@ class i18nTest extends PHPUnit_Framework_TestCase
|
||||||
$this->assertEquals($messageId, i18n::_($messageId), 'fallback to en');
|
$this->assertEquals($messageId, i18n::_($messageId), 'fallback to en');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testBrowserLanguageDetection()
|
public function testBrowserLanguageDeDetection()
|
||||||
{
|
{
|
||||||
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'de-CH,de;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
|
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'de-CH,de;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
|
||||||
i18n::loadTranslations();
|
i18n::loadTranslations();
|
||||||
$this->assertEquals($this->_translations['en'], i18n::_('en'), 'browser language de');
|
$this->assertEquals($this->_translations['en'], i18n::_('en'), 'browser language de');
|
||||||
|
$this->assertEquals('0 Stunden', i18n::_('%d hours', 0), '0 hours in german');
|
||||||
|
$this->assertEquals('1 Stunde', i18n::_('%d hours', 1), '1 hour in german');
|
||||||
|
$this->assertEquals('2 Stunden', i18n::_('%d hours', 2), '2 hours in french');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBrowserLanguageFrDetection()
|
||||||
|
{
|
||||||
|
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'fr-CH,fr;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
|
||||||
|
i18n::loadTranslations();
|
||||||
|
$this->assertEquals('fr', i18n::_('en'), 'browser language fr');
|
||||||
|
$this->assertEquals('0 heure', i18n::_('%d hours', 0), '0 hours in french');
|
||||||
|
$this->assertEquals('1 heure', i18n::_('%d hours', 1), '1 hour in french');
|
||||||
|
$this->assertEquals('2 heures', i18n::_('%d hours', 2), '2 hours in french');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBrowserLanguagePlDetection()
|
||||||
|
{
|
||||||
|
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'pl;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
|
||||||
|
i18n::loadTranslations();
|
||||||
|
$this->assertEquals('pl', i18n::_('en'), 'browser language pl');
|
||||||
|
$this->assertEquals('2 godzina', i18n::_('%d hours', 2), 'hours in polish');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBrowserLanguageAnyDetection()
|
||||||
|
{
|
||||||
|
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = '*';
|
||||||
|
i18n::loadTranslations();
|
||||||
|
$this->assertTrue(strlen(i18n::_('en')) == 2, 'browser language any');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testVariableInjection()
|
public function testVariableInjection()
|
||||||
|
|
|
@ -108,23 +108,6 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||||
$content = ob_get_contents();
|
$content = ob_get_contents();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @runInSeparateProcess
|
|
||||||
*/
|
|
||||||
public function testConfMissingExpireLabel()
|
|
||||||
{
|
|
||||||
$this->reset();
|
|
||||||
$options = parse_ini_file($this->_conf, true);
|
|
||||||
$options['expire_options']['foobar123'] = 10;
|
|
||||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
|
||||||
rename($this->_conf, $this->_conf . '.bak');
|
|
||||||
helper::createIniFile($this->_conf, $options);
|
|
||||||
ini_set('magic_quotes_gpc', 1);
|
|
||||||
ob_start();
|
|
||||||
new zerobin;
|
|
||||||
$content = ob_get_contents();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @runInSeparateProcess
|
* @runInSeparateProcess
|
||||||
*/
|
*/
|
||||||
|
@ -461,7 +444,9 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||||
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
|
||||||
rename($this->_conf, $this->_conf . '.bak');
|
rename($this->_conf, $this->_conf . '.bak');
|
||||||
helper::createIniFile($this->_conf, $options);
|
helper::createIniFile($this->_conf, $options);
|
||||||
|
$this->_model->create(self::$pasteid, self::$paste);
|
||||||
$this->_model->createComment(self::$pasteid, self::$pasteid, self::$commentid, self::$comment);
|
$this->_model->createComment(self::$pasteid, self::$pasteid, self::$commentid, self::$comment);
|
||||||
|
$this->assertTrue($this->_model->existsComment(self::$pasteid, self::$pasteid, self::$commentid), 'comment exists before posting data');
|
||||||
$_POST = self::$comment;
|
$_POST = self::$comment;
|
||||||
$_POST['pasteid'] = self::$pasteid;
|
$_POST['pasteid'] = self::$pasteid;
|
||||||
$_POST['parentid'] = self::$pasteid;
|
$_POST['parentid'] = self::$pasteid;
|
||||||
|
@ -747,9 +732,12 @@ class zerobinTest extends PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
$this->reset();
|
$this->reset();
|
||||||
$expiredPaste = self::$paste;
|
$expiredPaste = self::$paste;
|
||||||
$expiredPaste['meta']['expire_date'] = $expiredPaste['meta']['postdate'];
|
$expiredPaste['meta']['expire_date'] = 1000;
|
||||||
|
$this->assertFalse($this->_model->exists(self::$pasteid), 'paste does not exist before being created');
|
||||||
$this->_model->create(self::$pasteid, $expiredPaste);
|
$this->_model->create(self::$pasteid, $expiredPaste);
|
||||||
$_SERVER['QUERY_STRING'] = self::$pasteid;
|
$this->assertTrue($this->_model->exists(self::$pasteid), 'paste exists before deleting data');
|
||||||
|
$_GET['pasteid'] = self::$pasteid;
|
||||||
|
$_GET['deletetoken'] = 'does not matter in this context, but has to be set';
|
||||||
ob_start();
|
ob_start();
|
||||||
new zerobin;
|
new zerobin;
|
||||||
$content = ob_get_contents();
|
$content = ob_get_contents();
|
||||||
|
|
Loading…
Reference in a new issue