Просмотр исходного кода

- 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

El RIDO 11 лет назад
Родитель
Сommit
b060d57524
9 измененных файлов с 154 добавлено и 50 удалено
  1. 0 12
      cfg/conf.ini
  2. 10 1
      i18n/fr.json
  3. 1 1
      js/zerobin.js
  4. 32 3
      lib/filter.php
  5. 53 4
      lib/i18n.php
  6. 2 6
      lib/zerobin.php
  7. 20 3
      tst/filter.php
  8. 29 1
      tst/i18n.php
  9. 7 19
      tst/zerobin.php

+ 0 - 12
cfg/conf.ini

@@ -57,18 +57,6 @@ default = "1week"
 1year = 31536000
 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]
 ; time limit between calls from the same IP address in seconds
 ; Set this to 0 to disable rate limiting.

+ 10 - 1
i18n/fr.json

@@ -123,5 +123,14 @@
     "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?)",
+    "B": "o",
+    "KiB": "Kio",
+    "MiB": "Mio",
+    "GiB": "Gio",
+    "TiB": "Tio",
+    "PiB": "Pio",
+    "EiB": "Eio",
+    "ZiB": "Zio",
+    "YiB": "Yio"
 }

+ 1 - 1
js/zerobin.js

@@ -647,7 +647,7 @@ $(function() {
         {
             event.preventDefault();
             var source = $(event.target),
-                commentid = event.data.commentid
+                commentid = event.data.commentid,
                 hint = i18n._('Optional nickname...');
 
             // Remove any other reply area.

+ 32 - 3
lib/filter.php

@@ -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
      * @static
@@ -42,13 +71,13 @@ class filter
      */
     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;
         while ( ( $size / 1024 ) >= 1 ) {
                 $size = $size / 1024;
                 $i++;
         }
-        return number_format($size, ($i ? 2 : 0), '.', ' ') . ' ' . $iec[$i];
+        return number_format($size, ($i ? 2 : 0), '.', ' ') . ' ' . i18n::_($iec[$i]);
     }
 
     /**

+ 53 - 4
lib/i18n.php

@@ -17,14 +17,23 @@
  */
 class i18n
 {
+    /**
+     * language
+     *
+     * @access protected
+     * @static
+     * @var    string
+     */
+    protected static $_language = 'en';
+
     /**
      * translation cache
      *
-     * @access private
+     * @access protected
      * @static
      * @var    array
      */
-    private static $_translations = array();
+    protected static $_translations = array();
 
     /**
      * translate a string, alias for translate()
@@ -53,12 +62,30 @@ class i18n
     {
         if (empty($messageId)) return $messageId;
         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))
         {
-            self::$_translations[$messageId] = $messageId;
+            self::$_translations[$messageId] = $messages;
         }
         $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);
     }
 
@@ -91,6 +118,7 @@ class i18n
         // load translations
         if ($match != 'en')
         {
+            self::$_language = $match;
             self::$_translations = json_decode(
                 file_get_contents($path . DIRECTORY_SEPARATOR . $match . '.json'),
                 true
@@ -137,6 +165,27 @@ class i18n
         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
      *

+ 2 - 6
lib/zerobin.php

@@ -579,12 +579,8 @@ class zerobin
 
         // label all the expiration options
         $expire = array();
-        foreach ($this->_conf['expire_options'] as $key => $value) {
-            $expire[$key] = i18n::_(
-                array_key_exists($key, $this->_conf['expire_labels']) ?
-                $this->_conf['expire_labels'][$key] :
-                $key
-            );
+        foreach ($this->_conf['expire_options'] as $time => $seconds) {
+            $expire[$time] = ($seconds == 0) ? i18n::_(ucfirst($time)): filter::time_humanreadable($time);
         }
 
         $page = new RainTPL;

+ 20 - 3
tst/filter.php

@@ -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()
     {
         $this->assertEquals('1 B', filter::size_humanreadable(1));
         $this->assertEquals('1 000 B', filter::size_humanreadable(1000));
-        $this->assertEquals('1.00 kiB', filter::size_humanreadable(1024));
-        $this->assertEquals('1.21 kiB', filter::size_humanreadable(1234));
+        $this->assertEquals('1.00 KiB', filter::size_humanreadable(1024));
+        $this->assertEquals('1.21 KiB', filter::size_humanreadable(1234));
         $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.21 MiB', filter::size_humanreadable(1234 * $exponent));
         $exponent *= 1024;

+ 29 - 1
tst/i18n.php

@@ -25,11 +25,39 @@ class i18nTest extends PHPUnit_Framework_TestCase
         $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';
         i18n::loadTranslations();
         $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()

+ 7 - 19
tst/zerobin.php

@@ -108,23 +108,6 @@ class zerobinTest extends PHPUnit_Framework_TestCase
         $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
      */
@@ -461,7 +444,9 @@ class zerobinTest extends PHPUnit_Framework_TestCase
         if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
             rename($this->_conf, $this->_conf . '.bak');
         helper::createIniFile($this->_conf, $options);
+        $this->_model->create(self::$pasteid, self::$paste);
         $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['pasteid'] = self::$pasteid;
         $_POST['parentid'] = self::$pasteid;
@@ -747,9 +732,12 @@ class zerobinTest extends PHPUnit_Framework_TestCase
     {
         $this->reset();
         $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);
-        $_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();
         new zerobin;
         $content = ob_get_contents();