WordPress.org

Make WordPress Core

Ticket #4005: gettext-plurals.diff

File gettext-plurals.diff, 22.2 KB (added by nbachiyski, 7 years ago)

moeffju's fixes + select_string enhancements + indent + none whitespace at the end of lines

  • wp-includes/gettext.php

     
    11<?php 
    22/* 
    3    Copyright (c) 2003 Danilo Segan <danilo@kvota.net>. 
    4    Copyright (c) 2005 Nico Kaiser <nico@siriux.net> 
    5     
    6    This file is part of PHP-gettext. 
     3         Copyright (c) 2003 Danilo Segan <danilo@kvota.net>. 
     4         Copyright (c) 2005 Nico Kaiser <nico@siriux.net> 
    75 
    8    PHP-gettext is free software; you can redistribute it and/or modify 
    9    it under the terms of the GNU General Public License as published by 
    10    the Free Software Foundation; either version 2 of the License, or 
    11    (at your option) any later version. 
     6         This file is part of PHP-gettext. 
    127 
    13    PHP-gettext is distributed in the hope that it will be useful, 
    14    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
    16    GNU General Public License for more details. 
     8         PHP-gettext is free software; you can redistribute it and/or modify 
     9         it under the terms of the GNU General Public License as published by 
     10         the Free Software Foundation; either version 2 of the License, or 
     11         (at your option) any later version. 
    1712 
    18    You should have received a copy of the GNU General Public License 
    19    along with PHP-gettext; if not, write to the Free Software 
    20    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
     13         PHP-gettext is distributed in the hope that it will be useful, 
     14         but WITHOUT ANY WARRANTY; without even the implied warranty of 
     15         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
     16         GNU General Public License for more details. 
    2117 
     18         You should have received a copy of the GNU General Public License 
     19         along with PHP-gettext; if not, write to the Free Software 
     20         Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
     21 
    2222*/ 
    23   
     23 
    2424/** 
    2525 * Provides a simple gettext replacement that works independently from 
    2626 * the system's gettext abilities. 
    2727 * It can read MO files and use them for translating strings. 
    2828 * The files are passed to gettext_reader as a Stream (see streams.php) 
    29  *  
     29 * 
    3030 * This version has the ability to cache all strings and translations to 
    3131 * speed up the string lookup. 
    3232 * While the cache is enabled by default, it can be switched off with the 
     
    3434 * that you don't want to keep in memory) 
    3535 */ 
    3636class gettext_reader { 
    37   //public: 
    38    var $error = 0; // public variable that holds error code (0 if no error) 
    39     
    40    //private: 
    41   var $BYTEORDER = 0;        // 0: low endian, 1: big endian 
    42   var $STREAM = NULL; 
    43   var $short_circuit = false; 
    44   var $enable_cache = false; 
    45   var $originals = NULL;      // offset of original table 
    46   var $translations = NULL;    // offset of translation table 
    47   var $pluralheader = NULL;    // cache header field for plural forms 
    48   var $total = 0;          // total string count 
    49   var $table_originals = NULL;  // table for original strings (offsets) 
    50   var $table_translations = NULL;  // table for translated strings (offsets) 
    51   var $cache_translations = NULL;  // original -> translation mapping 
     37        //public: 
     38         var $error = 0; // public variable that holds error code (0 if no error) 
    5239 
     40         //private: 
     41        var $BYTEORDER = 0;        // 0: low endian, 1: big endian 
     42        var $STREAM = NULL; 
     43        var $short_circuit = false; 
     44        var $enable_cache = false; 
     45        var $originals = NULL;      // offset of original table 
     46        var $translations = NULL;    // offset of translation table 
     47        var $pluralheader = NULL;    // cache header field for plural forms 
     48        var $select_string_function = NULL; // cache function, which chooses plural forms 
     49        var $total = 0;          // total string count 
     50        var $table_originals = NULL;  // table for original strings (offsets) 
     51        var $table_translations = NULL;  // table for translated strings (offsets) 
     52        var $cache_translations = NULL;  // original -> translation mapping 
    5353 
    54   /* Methods */ 
    55    
    56      
    57   /** 
    58    * Reads a 32bit Integer from the Stream 
    59    *  
    60    * @access private 
    61    * @return Integer from the Stream 
    62    */ 
    63   function readint() { 
    64       if ($this->BYTEORDER == 0) { 
    65         // low endian 
    66         $low_end = unpack('V', $this->STREAM->read(4)); 
    67         return array_shift($low_end); 
    68       } else { 
    69         // big endian 
    70         $big_end = unpack('N', $this->STREAM->read(4)); 
    71         return array_shift($big_end); 
    72       } 
    73     } 
    7454 
    75   /** 
    76    * Reads an array of Integers from the Stream 
    77    *  
    78    * @param int count How many elements should be read 
    79    * @return Array of Integers 
    80    */ 
    81   function readintarray($count) { 
    82     if ($this->BYTEORDER == 0) { 
    83         // low endian 
    84         return unpack('V'.$count, $this->STREAM->read(4 * $count)); 
    85       } else { 
    86         // big endian 
    87         return unpack('N'.$count, $this->STREAM->read(4 * $count)); 
    88       } 
    89   } 
    90    
    91   /** 
    92    * Constructor 
    93    *  
    94    * @param object Reader the StreamReader object 
    95    * @param boolean enable_cache Enable or disable caching of strings (default on) 
    96    */ 
    97   function gettext_reader($Reader, $enable_cache = true) { 
    98     // If there isn't a StreamReader, turn on short circuit mode. 
    99     if (! $Reader || isset($Reader->error) ) { 
    100       $this->short_circuit = true; 
    101       return; 
    102     } 
    103      
    104     // Caching can be turned off 
    105     $this->enable_cache = $enable_cache; 
     55        /* Methods */ 
    10656 
    107     // $MAGIC1 = (int)0x950412de; //bug in PHP 5.0.2, see https://savannah.nongnu.org/bugs/?func=detailitem&item_id=10565 
    108     $MAGIC1 = (int) - 1794895138; 
    109     // $MAGIC2 = (int)0xde120495; //bug 
    110     $MAGIC2 = (int) - 569244523; 
    111     // 64-bit fix 
    112     $MAGIC3 = (int) 2500072158; 
    11357 
    114     $this->STREAM = $Reader; 
    115     $magic = $this->readint(); 
    116     if ($magic == ($MAGIC1 & 0xFFFFFFFF) || $magic == ($MAGIC3 & 0xFFFFFFFF)) { // to make sure it works for 64-bit platforms 
    117       $this->BYTEORDER = 0; 
    118     } elseif ($magic == ($MAGIC2 & 0xFFFFFFFF)) { 
    119       $this->BYTEORDER = 1; 
    120     } else { 
    121       $this->error = 1; // not MO file 
    122       return false; 
    123     } 
    124      
    125     // FIXME: Do we care about revision? We should. 
    126     $revision = $this->readint(); 
    127      
    128     $this->total = $this->readint(); 
    129     $this->originals = $this->readint(); 
    130     $this->translations = $this->readint(); 
    131   } 
    132    
    133   /** 
    134    * Loads the translation tables from the MO file into the cache 
    135    * If caching is enabled, also loads all strings into a cache 
    136    * to speed up translation lookups 
    137    *  
    138    * @access private 
    139    */ 
    140   function load_tables() { 
    141     if (is_array($this->cache_translations) && 
    142       is_array($this->table_originals) && 
    143       is_array($this->table_translations)) 
    144       return; 
    145      
    146     /* get original and translations tables */ 
    147     $this->STREAM->seekto($this->originals); 
    148     $this->table_originals = $this->readintarray($this->total * 2); 
    149     $this->STREAM->seekto($this->translations); 
    150     $this->table_translations = $this->readintarray($this->total * 2); 
    151      
    152     if ($this->enable_cache) { 
    153       $this->cache_translations = array (); 
    154       /* read all strings in the cache */ 
    155       for ($i = 0; $i < $this->total; $i++) { 
    156         $this->STREAM->seekto($this->table_originals[$i * 2 + 2]); 
    157         $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]); 
    158         $this->STREAM->seekto($this->table_translations[$i * 2 + 2]); 
    159         $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]); 
    160         $this->cache_translations[$original] = $translation; 
    161       } 
    162     } 
    163   } 
    164    
    165   /** 
    166    * Returns a string from the "originals" table 
    167    *  
    168    * @access private 
    169    * @param int num Offset number of original string 
    170    * @return string Requested string if found, otherwise '' 
    171    */ 
    172   function get_original_string($num) { 
    173     $length = $this->table_originals[$num * 2 + 1]; 
    174     $offset = $this->table_originals[$num * 2 + 2]; 
    175     if (! $length) 
    176       return ''; 
    177     $this->STREAM->seekto($offset); 
    178     $data = $this->STREAM->read($length); 
    179     return (string)$data; 
    180   } 
    181    
    182   /** 
    183    * Returns a string from the "translations" table 
    184    *  
    185    * @access private 
    186    * @param int num Offset number of original string 
    187    * @return string Requested string if found, otherwise '' 
    188    */ 
    189   function get_translation_string($num) { 
    190     $length = $this->table_translations[$num * 2 + 1]; 
    191     $offset = $this->table_translations[$num * 2 + 2]; 
    192     if (! $length) 
    193       return ''; 
    194     $this->STREAM->seekto($offset); 
    195     $data = $this->STREAM->read($length); 
    196     return (string)$data; 
    197   } 
    198    
    199   /** 
    200    * Binary search for string 
    201    *  
    202    * @access private 
    203    * @param string string 
    204    * @param int start (internally used in recursive function) 
    205    * @param int end (internally used in recursive function) 
    206    * @return int string number (offset in originals table) 
    207    */ 
    208   function find_string($string, $start = -1, $end = -1) { 
    209     if (($start == -1) or ($end == -1)) { 
    210       // find_string is called with only one parameter, set start end end 
    211       $start = 0; 
    212       $end = $this->total; 
    213     } 
    214     if (abs($start - $end) <= 1) { 
    215       // We're done, now we either found the string, or it doesn't exist 
    216       $txt = $this->get_original_string($start); 
    217       if ($string == $txt) 
    218         return $start; 
    219       else 
    220         return -1; 
    221     } else if ($start > $end) { 
    222       // start > end -> turn around and start over 
    223       return $this->find_string($string, $end, $start); 
    224     } else { 
    225       // Divide table in two parts 
    226       $half = (int)(($start + $end) / 2); 
    227       $cmp = strcmp($string, $this->get_original_string($half)); 
    228       if ($cmp == 0) 
    229         // string is exactly in the middle => return it 
    230         return $half; 
    231       else if ($cmp < 0) 
    232         // The string is in the upper half 
    233         return $this->find_string($string, $start, $half); 
    234       else 
    235         // The string is in the lower half 
    236         return $this->find_string($string, $half, $end); 
    237     } 
    238   } 
    239    
    240   /** 
    241    * Translates a string 
    242    *  
    243    * @access public 
    244    * @param string string to be translated 
    245    * @return string translated string (or original, if not found) 
    246    */ 
    247   function translate($string) { 
    248     if ($this->short_circuit) 
    249       return $string; 
    250     $this->load_tables();      
    251      
    252     if ($this->enable_cache) { 
    253       // Caching enabled, get translated string from cache 
    254       if (array_key_exists($string, $this->cache_translations)) 
    255         return $this->cache_translations[$string]; 
    256       else 
    257         return $string; 
    258     } else { 
    259       // Caching not enabled, try to find string 
    260       $num = $this->find_string($string); 
    261       if ($num == -1) 
    262         return $string; 
    263       else 
    264         return $this->get_translation_string($num); 
    265     } 
    266   } 
     58        /** 
     59         * Reads a 32bit Integer from the Stream 
     60         * 
     61         * @access private 
     62         * @return Integer from the Stream 
     63         */ 
     64        function readint() { 
     65                if ($this->BYTEORDER == 0) { 
     66                        // low endian 
     67                        $low_end = unpack('V', $this->STREAM->read(4)); 
     68                        return array_shift($low_end); 
     69                } else { 
     70                        // big endian 
     71                        $big_end = unpack('N', $this->STREAM->read(4)); 
     72                        return array_shift($big_end); 
     73                } 
     74        } 
    26775 
    268   /** 
    269    * Get possible plural forms from MO header 
    270    *  
    271    * @access private 
    272    * @return string plural form header 
    273    */ 
    274   function get_plural_forms() { 
    275     // lets assume message number 0 is header   
    276     // this is true, right? 
    277     $this->load_tables(); 
    278      
    279     // cache header field for plural forms 
    280     if (! is_string($this->pluralheader)) { 
    281       if ($this->enable_cache) { 
    282         $header = $this->cache_translations[""]; 
    283       } else { 
    284         $header = $this->get_translation_string(0); 
    285       } 
    286       if (eregi("plural-forms: ([^\n]*)\n", $header, $regs)) 
    287         $expr = $regs[1]; 
    288       else 
    289         $expr = "nplurals=2; plural=n == 1 ? 0 : 1;"; 
    290       $this->pluralheader = $expr; 
    291     } 
    292     return $this->pluralheader; 
    293   } 
     76        /** 
     77         * Reads an array of Integers from the Stream 
     78         * 
     79         * @param int count How many elements should be read 
     80         * @return Array of Integers 
     81         */ 
     82        function readintarray($count) { 
     83        if ($this->BYTEORDER == 0) { 
     84                        // low endian 
     85                        return unpack('V'.$count, $this->STREAM->read(4 * $count)); 
     86                } else { 
     87                        // big endian 
     88                        return unpack('N'.$count, $this->STREAM->read(4 * $count)); 
     89                } 
     90        } 
    29491 
    295   /** 
    296    * Detects which plural form to take 
    297    *  
    298    * @access private 
    299    * @param n count 
    300    * @return int array index of the right plural form 
    301    */ 
    302   function select_string($n) { 
    303     $string = $this->get_plural_forms(); 
    304     $string = str_replace('nplurals',"\$total",$string); 
    305     $string = str_replace("n",$n,$string); 
    306     $string = str_replace('plural',"\$plural",$string); 
     92        /** 
     93         * Constructor 
     94         * 
     95         * @param object Reader the StreamReader object 
     96         * @param boolean enable_cache Enable or disable caching of strings (default on) 
     97         */ 
     98        function gettext_reader($Reader, $enable_cache = true) { 
     99                // If there isn't a StreamReader, turn on short circuit mode. 
     100                if (! $Reader || isset($Reader->error) ) { 
     101                        $this->short_circuit = true; 
     102                        return; 
     103                } 
    307104 
    308     # poEdit doesn't put any semicolons, which 
    309     # results in parse error in eval 
    310     $string .= ';'; 
     105                // Caching can be turned off 
     106                $this->enable_cache = $enable_cache; 
    311107 
    312     $total = 0; 
    313     $plural = 0; 
     108                // $MAGIC1 = (int)0x950412de; //bug in PHP 5.0.2, see https://savannah.nongnu.org/bugs/?func=detailitem&item_id=10565 
     109                $MAGIC1 = (int) - 1794895138; 
     110                // $MAGIC2 = (int)0xde120495; //bug 
     111                $MAGIC2 = (int) - 569244523; 
     112                // 64-bit fix 
     113                $MAGIC3 = (int) 2500072158; 
    314114 
    315     eval("$string"); 
    316     if ($plural >= $total) $plural = $total - 1; 
    317     return $plural; 
    318   } 
     115                $this->STREAM = $Reader; 
     116                $magic = $this->readint(); 
     117                if ($magic == ($MAGIC1 & 0xFFFFFFFF) || $magic == ($MAGIC3 & 0xFFFFFFFF)) { // to make sure it works for 64-bit platforms 
     118                        $this->BYTEORDER = 0; 
     119                } elseif ($magic == ($MAGIC2 & 0xFFFFFFFF)) { 
     120                        $this->BYTEORDER = 1; 
     121                } else { 
     122                        $this->error = 1; // not MO file 
     123                        return false; 
     124                } 
    319125 
    320   /** 
    321    * Plural version of gettext 
    322    *  
    323    * @access public 
    324    * @param string single 
    325    * @param string plural 
    326    * @param string number 
    327    * @return translated plural form 
    328    */ 
    329   function ngettext($single, $plural, $number) { 
    330     if ($this->short_circuit) { 
    331       if ($number != 1) 
    332         return $plural; 
    333       else 
    334         return $single; 
    335     } 
     126                // FIXME: Do we care about revision? We should. 
     127                $revision = $this->readint(); 
    336128 
    337     // find out the appropriate form 
    338     $select = $this->select_string($number);  
    339      
    340     // this should contains all strings separated by NULLs 
    341     $key = $single.chr(0).$plural; 
    342      
    343      
    344     if ($this->enable_cache) { 
    345       if (! array_key_exists($key, $this->cache_translations)) { 
    346         return ($number != 1) ? $plural : $single; 
    347       } else { 
    348         $result = $this->cache_translations[$key]; 
    349         $list = explode(chr(0), $result); 
    350         return $list[$select]; 
    351       } 
    352     } else { 
    353       $num = $this->find_string($key); 
    354       if ($num == -1) { 
    355         return ($number != 1) ? $plural : $single; 
    356       } else { 
    357         $result = $this->get_translation_string($num); 
    358         $list = explode(chr(0), $result); 
    359         return $list[$select]; 
    360       } 
    361     } 
    362   } 
     129                $this->total = $this->readint(); 
     130                $this->originals = $this->readint(); 
     131                $this->translations = $this->readint(); 
     132        } 
    363133 
     134        /** 
     135         * Loads the translation tables from the MO file into the cache 
     136         * If caching is enabled, also loads all strings into a cache 
     137         * to speed up translation lookups 
     138         * 
     139         * @access private 
     140         */ 
     141        function load_tables() { 
     142                if (is_array($this->cache_translations) && 
     143                        is_array($this->table_originals) && 
     144                        is_array($this->table_translations)) 
     145                        return; 
     146 
     147                /* get original and translations tables */ 
     148                $this->STREAM->seekto($this->originals); 
     149                $this->table_originals = $this->readintarray($this->total * 2); 
     150                $this->STREAM->seekto($this->translations); 
     151                $this->table_translations = $this->readintarray($this->total * 2); 
     152 
     153                if ($this->enable_cache) { 
     154                        $this->cache_translations = array (); 
     155                        /* read all strings in the cache */ 
     156                        for ($i = 0; $i < $this->total; $i++) { 
     157                                $this->STREAM->seekto($this->table_originals[$i * 2 + 2]); 
     158                                $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]); 
     159                                $this->STREAM->seekto($this->table_translations[$i * 2 + 2]); 
     160                                $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]); 
     161                                $this->cache_translations[$original] = $translation; 
     162                        } 
     163                } 
     164        } 
     165 
     166        /** 
     167         * Returns a string from the "originals" table 
     168         * 
     169         * @access private 
     170         * @param int num Offset number of original string 
     171         * @return string Requested string if found, otherwise '' 
     172         */ 
     173        function get_original_string($num) { 
     174                $length = $this->table_originals[$num * 2 + 1]; 
     175                $offset = $this->table_originals[$num * 2 + 2]; 
     176                if (! $length) 
     177                        return ''; 
     178                $this->STREAM->seekto($offset); 
     179                $data = $this->STREAM->read($length); 
     180                return (string)$data; 
     181        } 
     182 
     183        /** 
     184         * Returns a string from the "translations" table 
     185         * 
     186         * @access private 
     187         * @param int num Offset number of original string 
     188         * @return string Requested string if found, otherwise '' 
     189         */ 
     190        function get_translation_string($num) { 
     191                $length = $this->table_translations[$num * 2 + 1]; 
     192                $offset = $this->table_translations[$num * 2 + 2]; 
     193                if (! $length) 
     194                        return ''; 
     195                $this->STREAM->seekto($offset); 
     196                $data = $this->STREAM->read($length); 
     197                return (string)$data; 
     198        } 
     199 
     200        /** 
     201         * Binary search for string 
     202         * 
     203         * @access private 
     204         * @param string string 
     205         * @param int start (internally used in recursive function) 
     206         * @param int end (internally used in recursive function) 
     207         * @return int string number (offset in originals table) 
     208         */ 
     209        function find_string($string, $start = -1, $end = -1) { 
     210                if (($start == -1) or ($end == -1)) { 
     211                        // find_string is called with only one parameter, set start end end 
     212                        $start = 0; 
     213                        $end = $this->total; 
     214                } 
     215                if (abs($start - $end) <= 1) { 
     216                        // We're done, now we either found the string, or it doesn't exist 
     217                        $txt = $this->get_original_string($start); 
     218                        if ($string == $txt) 
     219                                return $start; 
     220                        else 
     221                                return -1; 
     222                } else if ($start > $end) { 
     223                        // start > end -> turn around and start over 
     224                        return $this->find_string($string, $end, $start); 
     225                } else { 
     226                        // Divide table in two parts 
     227                        $half = (int)(($start + $end) / 2); 
     228                        $cmp = strcmp($string, $this->get_original_string($half)); 
     229                        if ($cmp == 0) 
     230                                // string is exactly in the middle => return it 
     231                                return $half; 
     232                        else if ($cmp < 0) 
     233                                // The string is in the upper half 
     234                                return $this->find_string($string, $start, $half); 
     235                        else 
     236                                // The string is in the lower half 
     237                                return $this->find_string($string, $half, $end); 
     238                } 
     239        } 
     240 
     241        /** 
     242         * Translates a string 
     243         * 
     244         * @access public 
     245         * @param string string to be translated 
     246         * @return string translated string (or original, if not found) 
     247         */ 
     248        function translate($string) { 
     249                if ($this->short_circuit) 
     250                        return $string; 
     251                $this->load_tables(); 
     252 
     253                if ($this->enable_cache) { 
     254                        // Caching enabled, get translated string from cache 
     255                        if (array_key_exists($string, $this->cache_translations)) 
     256                                return $this->cache_translations[$string]; 
     257                        else 
     258                                return $string; 
     259                } else { 
     260                        // Caching not enabled, try to find string 
     261                        $num = $this->find_string($string); 
     262                        if ($num == -1) 
     263                                return $string; 
     264                        else 
     265                                return $this->get_translation_string($num); 
     266                } 
     267        } 
     268 
     269        /** 
     270         * Get possible plural forms from MO header 
     271         * 
     272         * @access private 
     273         * @return string plural form header 
     274         */ 
     275        function get_plural_forms() { 
     276                // lets assume message number 0 is header 
     277                // this is true, right? 
     278                $this->load_tables(); 
     279 
     280                // cache header field for plural forms 
     281                if (! is_string($this->pluralheader)) { 
     282                        if ($this->enable_cache) { 
     283                                $header = $this->cache_translations[""]; 
     284                        } else { 
     285                                $header = $this->get_translation_string(0); 
     286                        } 
     287                        $header .= "\n"; //make sure our regex matches 
     288                        if (eregi("plural-forms: ([^\n]*)\n", $header, $regs)) 
     289                                $expr = $regs[1]; 
     290                        else 
     291                                $expr = "nplurals=2; plural=n == 1 ? 0 : 1;"; 
     292 
     293                        // add parentheses 
     294                        // important since PHP's ternary evaluates from left to right 
     295                        $expr.= ';'; 
     296                        $res= ''; 
     297                        $p= 0; 
     298                        for ($i= 0; $i < strlen($expr); $i++) { 
     299                                $ch= $expr[$i]; 
     300                                switch ($ch) { 
     301                                        case '?': 
     302                                                $res.= ' ? ('; 
     303                                                $p++; 
     304                                                break; 
     305                                        case ':': 
     306                                                $res.= ') : ('; 
     307                                                break; 
     308                                        case ';': 
     309                                                $res.= str_repeat( ')', $p) . ';'; 
     310                                                $p= 0; 
     311                                                break; 
     312                                        default: 
     313                                                $res.= $ch; 
     314                                } 
     315                        } 
     316                        $this->pluralheader = $res; 
     317                } 
     318 
     319                return $this->pluralheader; 
     320        } 
     321 
     322        /** 
     323         * Detects which plural form to take 
     324         * 
     325         * @access private 
     326         * @param n count 
     327         * @return int array index of the right plural form 
     328         */ 
     329        function select_string($n) { 
     330                if (is_null($this->select_string_function)) { 
     331                        $string = $this->get_plural_forms(); 
     332                        if (preg_match("/nplurals\s*=\s*(\d+)\s*\;\s*plural\s*=\s*(.*?)\;+/", $string, $matches)) { 
     333                                $nplurals = $matches[1]; 
     334                                $expression = $matches[2]; 
     335                                $expression = str_replace("n", '$n', $expression); 
     336                        } else { 
     337                                $nplurals = 2; 
     338                                $expression = ' $n == 1 ? 0 : 1 '; 
     339                        } 
     340                        $func_body = " 
     341                                \$plural = ($expression); 
     342                                return (\$plural <= $nplurals)? \$plural : \$plural - 1;"; 
     343                        $this->select_string_function = create_function('$n', $func_body); 
     344                } 
     345                return call_user_func($this->select_string_function, $n); 
     346        } 
     347 
     348        /** 
     349         * Plural version of gettext 
     350         * 
     351         * @access public 
     352         * @param string single 
     353         * @param string plural 
     354         * @param string number 
     355         * @return translated plural form 
     356         */ 
     357        function ngettext($single, $plural, $number) { 
     358                if ($this->short_circuit) { 
     359                        if ($number != 1) 
     360                                return $plural; 
     361                        else 
     362                                return $single; 
     363                } 
     364 
     365                // find out the appropriate form 
     366                $select = $this->select_string($number); 
     367 
     368                // this should contains all strings separated by NULLs 
     369                $key = $single.chr(0).$plural; 
     370 
     371 
     372                if ($this->enable_cache) { 
     373                        if (! array_key_exists($key, $this->cache_translations)) { 
     374                                return ($number != 1) ? $plural : $single; 
     375                        } else { 
     376                                $result = $this->cache_translations[$key]; 
     377                                $list = explode(chr(0), $result); 
     378                                return $list[$select]; 
     379                        } 
     380                } else { 
     381                        $num = $this->find_string($key); 
     382                        if ($num == -1) { 
     383                                return ($number != 1) ? $plural : $single; 
     384                        } else { 
     385                                $result = $this->get_translation_string($num); 
     386                                $list = explode(chr(0), $result); 
     387                                return $list[$select]; 
     388                        } 
     389                } 
     390        } 
     391 
    364392} 
    365393 
    366394?>