[ Index ]

WordPress Source Cross Reference

title

Body

[close]

/wp-includes/ -> gettext.php (source)

   1  <?php
   2  /*
   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
  21  
  22  */
  23   
  24  /**
  25   * Provides a simple gettext replacement that works independently from
  26   * the system's gettext abilities.
  27   * It can read MO files and use them for translating strings.
  28   * The files are passed to gettext_reader as a Stream (see streams.php)
  29   * 
  30   * This version has the ability to cache all strings and translations to
  31   * speed up the string lookup.
  32   * While the cache is enabled by default, it can be switched off with the
  33   * second parameter in the constructor (e.g. whenusing very large MO files
  34   * that you don't want to keep in memory)
  35   */
  36  class 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          return array_shift(unpack('V', $this->STREAM->read(4)));
  67        } else {
  68          // big endian
  69          return array_shift(unpack('N', $this->STREAM->read(4)));
  70        }
  71      }
  72  
  73    /**
  74     * Reads an array of Integers from the Stream
  75     * 
  76     * @param int count How many elements should be read
  77     * @return Array of Integers
  78     */
  79    function readintarray($count) {
  80      if ($this->BYTEORDER == 0) {
  81          // low endian
  82          return unpack('V'.$count, $this->STREAM->read(4 * $count));
  83        } else {
  84          // big endian
  85          return unpack('N'.$count, $this->STREAM->read(4 * $count));
  86        }
  87    }
  88    
  89    /**
  90     * Constructor
  91     * 
  92     * @param object Reader the StreamReader object
  93     * @param boolean enable_cache Enable or disable caching of strings (default on)
  94     */
  95    function gettext_reader($Reader, $enable_cache = true) {
  96      // If there isn't a StreamReader, turn on short circuit mode.
  97      if (! $Reader || isset($Reader->error) ) {
  98        $this->short_circuit = true;
  99        return;
 100      }
 101      
 102      // Caching can be turned off
 103      $this->enable_cache = $enable_cache;
 104  
 105      // $MAGIC1 = (int)0x950412de; //bug in PHP 5.0.2, see https://savannah.nongnu.org/bugs/?func=detailitem&item_id=10565
 106      $MAGIC1 = (int) - 1794895138;
 107      // $MAGIC2 = (int)0xde120495; //bug
 108      $MAGIC2 = (int) - 569244523;
 109  
 110      $this->STREAM = $Reader;
 111      $magic = $this->readint();
 112      if ($magic == ($MAGIC1 & 0xFFFFFFFF)) { // to make sure it works for 64-bit platforms
 113        $this->BYTEORDER = 0;
 114      } elseif ($magic == ($MAGIC2 & 0xFFFFFFFF)) {
 115        $this->BYTEORDER = 1;
 116      } else {
 117        $this->error = 1; // not MO file
 118        return false;
 119      }
 120      
 121      // FIXME: Do we care about revision? We should.
 122      $revision = $this->readint();
 123      
 124      $this->total = $this->readint();
 125      $this->originals = $this->readint();
 126      $this->translations = $this->readint();
 127    }
 128    
 129    /**
 130     * Loads the translation tables from the MO file into the cache
 131     * If caching is enabled, also loads all strings into a cache
 132     * to speed up translation lookups
 133     * 
 134     * @access private
 135     */
 136    function load_tables() {
 137      if (is_array($this->cache_translations) &&
 138        is_array($this->table_originals) &&
 139        is_array($this->table_translations))
 140        return;
 141      
 142      /* get original and translations tables */
 143      $this->STREAM->seekto($this->originals);
 144      $this->table_originals = $this->readintarray($this->total * 2);
 145      $this->STREAM->seekto($this->translations);
 146      $this->table_translations = $this->readintarray($this->total * 2);
 147      
 148      if ($this->enable_cache) {
 149        $this->cache_translations = array ();
 150        /* read all strings in the cache */
 151        for ($i = 0; $i < $this->total; $i++) {
 152          $this->STREAM->seekto($this->table_originals[$i * 2 + 2]);
 153          $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]);
 154          $this->STREAM->seekto($this->table_translations[$i * 2 + 2]);
 155          $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]);
 156          $this->cache_translations[$original] = $translation;
 157        }
 158      }
 159    }
 160    
 161    /**
 162     * Returns a string from the "originals" table
 163     * 
 164     * @access private
 165     * @param int num Offset number of original string
 166     * @return string Requested string if found, otherwise ''
 167     */
 168    function get_original_string($num) {
 169      $length = $this->table_originals[$num * 2 + 1];
 170      $offset = $this->table_originals[$num * 2 + 2];
 171      if (! $length)
 172        return '';
 173      $this->STREAM->seekto($offset);
 174      $data = $this->STREAM->read($length);
 175      return (string)$data;
 176    }
 177    
 178    /**
 179     * Returns a string from the "translations" table
 180     * 
 181     * @access private
 182     * @param int num Offset number of original string
 183     * @return string Requested string if found, otherwise ''
 184     */
 185    function get_translation_string($num) {
 186      $length = $this->table_translations[$num * 2 + 1];
 187      $offset = $this->table_translations[$num * 2 + 2];
 188      if (! $length)
 189        return '';
 190      $this->STREAM->seekto($offset);
 191      $data = $this->STREAM->read($length);
 192      return (string)$data;
 193    }
 194    
 195    /**
 196     * Binary search for string
 197     * 
 198     * @access private
 199     * @param string string
 200     * @param int start (internally used in recursive function)
 201     * @param int end (internally used in recursive function)
 202     * @return int string number (offset in originals table)
 203     */
 204    function find_string($string, $start = -1, $end = -1) {
 205      if (($start == -1) or ($end == -1)) {
 206        // find_string is called with only one parameter, set start end end
 207        $start = 0;
 208        $end = $this->total;
 209      }
 210      if (abs($start - $end) <= 1) {
 211        // We're done, now we either found the string, or it doesn't exist
 212        $txt = $this->get_original_string($start);
 213        if ($string == $txt)
 214          return $start;
 215        else
 216          return -1;
 217      } else if ($start > $end) {
 218        // start > end -> turn around and start over
 219        return $this->find_string($string, $end, $start);
 220      } else {
 221        // Divide table in two parts
 222        $half = (int)(($start + $end) / 2);
 223        $cmp = strcmp($string, $this->get_original_string($half));
 224        if ($cmp == 0)
 225          // string is exactly in the middle => return it
 226          return $half;
 227        else if ($cmp < 0)
 228          // The string is in the upper half
 229          return $this->find_string($string, $start, $half);
 230        else
 231          // The string is in the lower half
 232          return $this->find_string($string, $half, $end);
 233      }
 234    }
 235    
 236    /**
 237     * Translates a string
 238     * 
 239     * @access public
 240     * @param string string to be translated
 241     * @return string translated string (or original, if not found)
 242     */
 243    function translate($string) {
 244      if ($this->short_circuit)
 245        return $string;
 246      $this->load_tables();     
 247      
 248      if ($this->enable_cache) {
 249        // Caching enabled, get translated string from cache
 250        if (array_key_exists($string, $this->cache_translations))
 251          return $this->cache_translations[$string];
 252        else
 253          return $string;
 254      } else {
 255        // Caching not enabled, try to find string
 256        $num = $this->find_string($string);
 257        if ($num == -1)
 258          return $string;
 259        else
 260          return $this->get_translation_string($num);
 261      }
 262    }
 263  
 264    /**
 265     * Get possible plural forms from MO header
 266     * 
 267     * @access private
 268     * @return string plural form header
 269     */
 270    function get_plural_forms() {
 271      // lets assume message number 0 is header  
 272      // this is true, right?
 273      $this->load_tables();
 274      
 275      // cache header field for plural forms
 276      if (! is_string($this->pluralheader)) {
 277        if ($this->enable_cache) {
 278          $header = $this->cache_translations[""];
 279        } else {
 280          $header = $this->get_translation_string(0);
 281        }
 282        if (eregi("plural-forms: ([^\n]*)\n", $header, $regs))
 283          $expr = $regs[1];
 284        else
 285          $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
 286        $this->pluralheader = $expr;
 287      }
 288      return $this->pluralheader;
 289    }
 290  
 291    /**
 292     * Detects which plural form to take
 293     * 
 294     * @access private
 295     * @param n count
 296     * @return int array index of the right plural form
 297     */
 298    function select_string($n) {
 299      $string = $this->get_plural_forms();
 300      $string = str_replace('nplurals',"\$total",$string);
 301      $string = str_replace("n",$n,$string);
 302      $string = str_replace('plural',"\$plural",$string);
 303      
 304      $total = 0;
 305      $plural = 0;
 306  
 307      eval("$string");
 308      if ($plural >= $total) $plural = $total - 1;
 309      return $plural;
 310    }
 311  
 312    /**
 313     * Plural version of gettext
 314     * 
 315     * @access public
 316     * @param string single
 317     * @param string plural
 318     * @param string number
 319     * @return translated plural form
 320     */
 321    function ngettext($single, $plural, $number) {
 322      if ($this->short_circuit) {
 323        if ($number != 1)
 324          return $plural;
 325        else
 326          return $single;
 327      }
 328  
 329      // find out the appropriate form
 330      $select = $this->select_string($number); 
 331      
 332      // this should contains all strings separated by NULLs
 333      $key = $single.chr(0).$plural;
 334      
 335      
 336      if ($this->enable_cache) {
 337        if (! array_key_exists($key, $this->cache_translations)) {
 338          return ($number != 1) ? $plural : $single;
 339        } else {
 340          $result = $this->cache_translations[$key];
 341          $list = explode(chr(0), $result);
 342          return $list[$select];
 343        }
 344      } else {
 345        $num = $this->find_string($key);
 346        if ($num == -1) {
 347          return ($number != 1) ? $plural : $single;
 348        } else {
 349          $result = $this->get_translation_string($num);
 350          $list = explode(chr(0), $result);
 351          return $list[$select];
 352        }
 353      }
 354    }
 355  
 356  }
 357  
 358  ?>

Your comment here...

Name: Location:
Comments:


List: Classes | Functions | Variables | Constants | Tables

Generated: Sat Jul 15 11:57:04 2006 Courtesy of Taragana