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:
48    var $BYTEORDER = 0; // 0: low endian, 1: big endian
49    var $STREAM = NULL;
50    var $short_circuit = false;
51    var $enable_cache = false;
52    var $originals = NULL; // offset of original table
53    var $translations = NULL; // offset of translation table
54    var $pluralheader = NULL; // cache header field for plural forms
55    var $select_string_function = NULL; // cache function, which chooses plural forms
56    var $total = 0; // total string count
57    var $table_originals = NULL; // table for original strings (offsets)
58    var $table_translations = NULL; // table for translated strings (offsets)
59    var $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     */
71    function readint() {
72        if ($this->BYTEORDER == 0) {
73            // low endian
74            $low_end = unpack('V', $this->STREAM->read(4));
75            return array_shift($low_end);
76        } else {
77            // big endian
78            $big_end = unpack('N', $this->STREAM->read(4));
79            return 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     */
89    function readintarray($count) {
90    if ($this->BYTEORDER == 0) {
91            // low endian
92            return unpack('V'.$count, $this->STREAM->read(4 * $count));
93        } else {
94            // big endian
95            return 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     */
105    function gettext_reader($Reader, $enable_cache = true) {
106        // If there isn't a StreamReader, turn on short circuit mode.
107        if (! $Reader || isset($Reader->error) ) {
108            $this->short_circuit = true;
109            return;
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();
124        if ($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
130            return 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     */
148    function load_tables() {
149        if (is_array($this->cache_translations) &&
150            is_array($this->table_originals) &&
151            is_array($this->table_translations))
152            return;
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
160        if ($this->enable_cache) {
161            $this->cache_translations = array ();
162            /* read all strings in the cache */
163            for ($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     */
180    function get_original_string($num) {
181        $length = $this->table_originals[$num * 2 + 1];
182        $offset = $this->table_originals[$num * 2 + 2];
183        if (! $length)
184            return '';
185        $this->STREAM->seekto($offset);
186        $data = $this->STREAM->read($length);
187        return (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     */
197    function get_translation_string($num) {
198        $length = $this->table_translations[$num * 2 + 1];
199        $offset = $this->table_translations[$num * 2 + 2];
200        if (! $length)
201            return '';
202        $this->STREAM->seekto($offset);
203        $data = $this->STREAM->read($length);
204        return (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     */
216    function find_string($string, $start = -1, $end = -1) {
217        if (($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        }
222        if (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);
225            if ($string == $txt)
226                return $start;
227            else
228                return -1;
229        } else if ($start > $end) {
230            // start > end -> turn around and start over
231            return $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));
236            if ($cmp == 0)
237                // string is exactly in the middle => return it
238                return $half;
239            else if ($cmp < 0)
240                // The string is in the upper half
241                return $this->find_string($string, $start, $half);
242            else
243                // The string is in the lower half
244                return $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     */
255    function translate($string) {
256        if ($this->short_circuit)
257            return $string;
258        $this->load_tables();
259
260        if ($this->enable_cache) {
261            // Caching enabled, get translated string from cache
262            if (array_key_exists($string, $this->cache_translations))
263                return $this->cache_translations[$string];
264            else
265                return $string;
266        } else {
267            // Caching not enabled, try to find string
268            $num = $this->find_string($string);
269            if ($num == -1)
270                return $string;
271            else
272                return $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     */
282    function 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
288        if (! is_string($this->pluralheader)) {
289            if ($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
295            if (eregi("plural-forms: ([^\n]*)\n", $header, $regs))
296                $expr = $regs[1];
297            else
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];
307                switch ($ch) {
308                    case '?':
309                        $res.= ' ? (';
310                        $p++;
311                        break;
312                    case ':':
313                        $res.= ') : (';
314                        break;
315                    case ';':
316                        $res.= str_repeat( ')', $p) . ';';
317                        $p= 0;
318                        break;
319                    default:
320                        $res.= $ch;
321                }
322            }
323            $this->pluralheader = $res;
324        }
325
326        return $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     */
336    function select_string($n) {
337        if (is_null($this->select_string_function)) {
338            $string = $this->get_plural_forms();
339            if (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);
349                return (\$plural <= $nplurals)? \$plural : \$plural - 1;";
350            $this->select_string_function = create_function('$n', $func_body);
351        }
352        return 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     */
364    function ngettext($single, $plural, $number) {
365        if ($this->short_circuit) {
366            if ($number != 1)
367                return $plural;
368            else
369                return $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
379        if ($this->enable_cache) {
380            if (! array_key_exists($key, $this->cache_translations)) {
381                return ($number != 1) ? $plural : $single;
382            } else {
383                $result = $this->cache_translations[$key];
384                $list = explode(chr(0), $result);
385                return $list[$select];
386            }
387        } else {
388            $num = $this->find_string($key);
389            if ($num == -1) {
390                return ($number != 1) ? $plural : $single;
391            } else {
392                $result = $this->get_translation_string($num);
393                $list = explode(chr(0), $result);
394                return $list[$select];
395            }
396        }
397    }
398
399}
400
401?>
402

Archive Download this file

Branches