| [ Index ] |
WordPress Source Cross Reference |
[Summary view] [Print] [Text view]
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 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Sat Jul 15 11:57:04 2006 | Courtesy of Taragana |