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