TightURL

TightURL Git Source Tree

Root/gettext.php

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

Archive Download this file

Branches