WordPress.org

Make WordPress Core

Changeset 5266


Ignore:
Timestamp:
04/13/07 23:29:47 (8 years ago)
Author:
rob1n
Message:

Fix gettext's plural forms for more than 2 plural forms. Props moeffju and nbachiyski. fixes #4005

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/wp-includes/gettext.php

    r4953 r5266  
    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. 
    7  
    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. 
    12  
    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. 
    17  
    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 
     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. 
     7 
     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. 
     12 
     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. 
     17 
     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 
    2121 
    2222*/ 
    23   
     23 
    2424/** 
    2525 * Provides a simple gettext replacement that works independently from 
     
    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. 
     
    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 
    52  
    53  
    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     } 
    74  
    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; 
    106  
    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; 
    113  
    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   } 
    267  
    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   } 
    294  
    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); 
    307  
    308     # poEdit doesn't put any semicolons, which 
    309     # results in parse error in eval 
    310     $string .= ';'; 
    311  
    312     $total = 0; 
    313     $plural = 0; 
    314  
    315     eval("$string"); 
    316     if ($plural >= $total) $plural = $total - 1; 
    317     return $plural; 
    318   } 
    319  
    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     } 
    336  
    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   } 
     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 $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 
     53 
     54 
     55    /* Methods */ 
     56 
     57 
     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    } 
     75 
     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    } 
     91 
     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        } 
     104 
     105        // Caching can be turned off 
     106        $this->enable_cache = $enable_cache; 
     107 
     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; 
     114 
     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        } 
     125 
     126        // FIXME: Do we care about revision? We should. 
     127        $revision = $this->readint(); 
     128 
     129        $this->total = $this->readint(); 
     130        $this->originals = $this->readint(); 
     131        $this->translations = $this->readint(); 
     132    } 
     133 
     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    } 
    363391 
    364392} 
Note: See TracChangeset for help on using the changeset viewer.