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