Allow more advanced element focusing
[squirrelmail.git] / class / l10n / gettext.class.php
1 <?php
2
3 /**
4 * Copyright (c) 2003 Danilo Segan <danilo@kvota.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 * @copyright 2004-2018 The SquirrelMail Project Team
23 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
24 * @version $Id$
25 * @package squirrelmail
26 * @subpackage i18n
27 */
28
29 /**
30 * Class that uses parsed translation input objects
31 * @package squirrelmail
32 * @subpackage i18n
33 */
34 class gettext_reader {
35 /**
36 * holds error code (0 if no error)
37 * @var integer
38 * @access public
39 */
40 var $error = 0;
41 /**
42 * specifies the byte order: 0 low endian, 1 big endian
43 * @var integer
44 * @access private
45 */
46 var $BYTEORDER = 0;
47 /**
48 * input object data
49 * @var object
50 * @access private
51 */
52 var $STREAM = NULL;
53
54 /**
55 *
56 */
57 function readint() {
58 // Reads 4 byte value from $FD and puts it in int
59 // $BYTEORDER specifies the byte order: 0 low endian, 1 big endian
60 for ($i=0; $i<4; $i++) {
61 $byte[$i]=ord($this->STREAM->read(1));
62 }
63 //print sprintf("pos: %d\n",$this->STREAM->currentpos());
64 if ($this->BYTEORDER == 0)
65 return (int)(($byte[0]) | ($byte[1]<<8) | ($byte[2]<<16) | ($byte[3]<<24));
66 else
67 return (int)(($byte[3]) | ($byte[2]<<8) | ($byte[1]<<16) | ($byte[0]<<24));
68 }
69
70 /**
71 * constructor that requires StreamReader object
72 * @param object $Reader
73 * @return boolean false, if some error with stream
74 */
75 function gettext_reader($Reader) {
76 $MAGIC1 = (int) ((222) | (18<<8) | (4<<16) | (149<<24));
77 $MAGIC2 = (int) ((149) | (4<<8) | (18<<16) | (222<<24));
78
79 $this->STREAM = $Reader;
80 if ($this->STREAM->error>0) {
81 $this->error=1;
82 return false;
83 }
84 $magic = $this->readint();
85 if ($magic == $MAGIC1) {
86 $this->BYTEORDER = 0;
87 } elseif ($magic == $MAGIC2) {
88 $this->BYTEORDER = 1;
89 } else {
90 $this->error = 1; // not MO file
91 return false;
92 }
93
94 // FIXME: Do we care about revision? We should.
95 $revision = $this->readint();
96
97 $total = $this->readint();
98 $originals = $this->readint();
99 $translations = $this->readint();
100
101 $this->total = $total;
102 $this->originals = $originals;
103 $this->translations = $translations;
104
105 // Here we store already found translations
106 $this->_HASHED = array();
107 }
108
109 /**
110 * @param boolean $translations do translation have to be loaded
111 */
112 function load_tables($translations=false) {
113 // if tables are loaded do not load them again
114 if (!isset($this->ORIGINALS)) {
115 $this->ORIGINALS = array();
116 $this->STREAM->seekto($this->originals);
117 for ($i=0; $i<$this->total; $i++) {
118 $len = $this->readint();
119 $ofs = $this->readint();
120 $this->ORIGINALS[] = array($len,$ofs);
121 }
122 }
123
124 // similar for translations
125 if ($translations and !isset($this->TRANSLATIONS)) {
126 $this->TRANSLATIONS = array();
127 $this->STREAM->seekto($this->translations);
128 for ($i=0; $i<$this->total; $i++) {
129 $len = $this->readint();
130 $ofs = $this->readint();
131 $this->TRANSLATIONS[] = array($len,$ofs);
132 }
133 }
134 }
135
136 /**
137 * get a string with particular number
138 * @param integer $num
139 * @return string untranslated string
140 */
141 function get_string_number($num) {
142 // TODO: Add simple hashing [check array, add if not already there]
143 $this->load_tables();
144 $meta = $this->ORIGINALS[$num];
145 $length = $meta[0];
146 $offset = $meta[1];
147 $this->STREAM->seekto($offset);
148 $data = $this->STREAM->read($length);
149 return (string)$data;
150 }
151
152 /**
153 * get translated string with particular number
154 * @param integer $num
155 * @return string translated string
156 */
157 function get_translation_number($num) {
158 // get a string with particular number
159 // TODO: Add simple hashing [check array, add if not already there]
160 $this->load_tables(true);
161 $meta = $this->TRANSLATIONS[$num];
162 $length = $meta[0];
163 $offset = $meta[1];
164 $this->STREAM->seekto($offset);
165 $data = $this->STREAM->read($length);
166 return (string)$data;
167 }
168
169 /**
170 * binary search for string
171 * @param string $string
172 * @param integer $start
173 * @param integer $end
174 */
175 function find_string($string, $start,$end) {
176 //print "start: $start, end: $end\n";
177 // Simple hashing to improve speed
178 if (isset($this->_HASHED[$string])) return $this->_HASHED[$string];
179
180 if (abs($start-$end)<=1) {
181 // we're done, if it's not it, bye bye
182 $txt = $this->get_string_number($start);
183 if ($string == $txt) {
184 $this->_HASHED[$string] = $start;
185 return $start;
186 } else
187 return -1;
188 } elseif ($start>$end) {
189 return $this->find_string($string,$end,$start);
190 } else {
191 $half = (int)(($start+$end)/2);
192 $tst = $this->get_string_number($half);
193 $cmp = strcmp($string,$tst);
194 if ($cmp == 0) {
195 $this->_HASHED[$string] = $half;
196 return $half;
197 } elseif ($cmp<0)
198 return $this->find_string($string,$start,$half);
199 else
200 return $this->find_string($string,$half,$end);
201 }
202 }
203
204 /**
205 * translate string
206 * @param string $string English string
207 * @return string translated string
208 */
209 function translate($string) {
210 if ($this->error > 0) return $string;
211 $num = $this->find_string($string, 0, $this->total);
212 if ($num == -1)
213 return $string;
214 else
215 return $this->get_translation_number($num);
216 }
217
218 /**
219 * extract plural forms header
220 * @return string plural-forms header string
221 */
222 function get_plural_forms() {
223 // lets assume message number 0 is header
224 // this is true, right?
225
226 // cache header field for plural forms
227 if (isset($this->pluralheader) && is_string($this->pluralheader))
228 return $this->pluralheader;
229 else {
230 $header = $this->get_translation_number(0);
231
232 if (preg_match('/plural-forms: (.*)\n/i',$header,$regs)) {
233 $expr = $regs[1];
234 } else {
235 $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
236 }
237 $this->pluralheader = $expr;
238 return $expr;
239 }
240 }
241
242 /**
243 * find out the appropriate form number
244 * @param integer $n count
245 * @return integer
246 */
247 function select_string($n) {
248 $string = $this->get_plural_forms();
249 $string = str_replace('nplurals',"\$total",$string);
250 $string = str_replace("n",$n,$string);
251 $string = str_replace('plural',"\$plural",$string);
252
253 $total = 0;
254 $plural = 0;
255
256 eval("$string");
257 if ($plural>=$total) $plural = 0;
258 return $plural;
259 }
260
261 /**
262 * translate string with singular/plural forms
263 * @param string $single English singural form of translation
264 * @param string $plural English plural form of translation
265 * @param string $number count
266 * @return string
267 */
268 function ngettext($single, $plural, $number) {
269 if ($this->error > 0) {
270 $result=-1;
271 } else {
272 // find out the appropriate form
273 $select = $this->select_string($number);
274
275 // this should contains all strings separated by NULLs
276 $result = $this->find_string($single.chr(0).$plural,0,$this->total);
277 }
278 if ($result == -1) {
279 if ($number != 1) return $plural;
280 else return $single;
281 } else {
282 $result = $this->get_translation_number($result);
283
284 // lets try to parse all the NUL staff
285 //$result = "proba0".chr(0)."proba1".chr(0)."proba2";
286 $list = explode (chr(0), $result);
287 return $list[$select];
288 }
289 }
290 }