Rerun
[fa-stable.git] / includes / lang / gettext.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4: */
3 //  
4 //  Copyright (c) 2003 Laurent Bedubourg
5 //  
6 //  This library is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU Lesser General Public
8 //  License as published by the Free Software Foundation; either
9 //  version 2.1 of the License, or (at your option) any later version.
10 //  
11 //  This library is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 //  Lesser General Public License for more details.
15 //  
16 //  You should have received a copy of the GNU Lesser General Public
17 //  License along with this library; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 //  
20 //  Authors: Laurent Bedubourg <laurent.bedubourg@free.fr>
21 //  
22
23 //require_once "PEAR.php";
24
25 define('GETTEXT_NATIVE', 1);
26 define('GETTEXT_PHP', 2);
27
28 function get_text_init($managerType = GETTEXT_NATIVE) {
29
30         if (!isset($_SESSION['get_text'])) {
31
32         if ($managerType == GETTEXT_NATIVE) 
33         {
34             if (function_exists('gettext')) 
35             {
36                 $_SESSION['get_text'] = new gettext_native_support();
37                 return;
38             }
39         }
40         // fail back to php support 
41                 $_SESSION['get_text'] = new gettext_php_support();
42         }
43 }
44
45 function raise_error($str) {
46 //      echo "$str";
47         return 1;
48 }
49
50 function is_error($err) {
51     return $err > 0;
52 }
53
54 /**
55 * Interface to gettext native support.
56 *
57 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
58 * @access private
59 */
60 class gettext_native_support 
61 {
62     var $_interpolation_vars = array();
63
64     /**
65      * Set gettext language code.
66      * @throws GetText_Error
67      */
68     function set_language($lang_code, $encoding)
69     {
70         putenv("LANG=$lang_code");
71         putenv("LC_ALL=$lang_code");
72         putenv("LANGUAGE=$lang_code");
73
74         //$set = setlocale(LC_ALL, "$lang_code");
75         //$set = setlocale(LC_ALL, "$encoding");
76         $set = setlocale(LC_ALL, $lang_code.".".$encoding);
77         setlocale(LC_NUMERIC, 'C'); // important for numeric presentation etc.
78         if ($set === false) 
79         {
80             $str = sprintf('language code "%s", encoding "%s" not supported by your system',
81                 $lang_code, $encoding);
82             //$err = new GetText_Error($str);
83             //return PEAR::raise_error($err);
84                         return raise_error("1 " . $str);
85         }
86                 //return 0;
87     }
88     
89     /**
90      * Add a translation domain.
91      */
92     function add_domain($domain, $path=false)
93     {
94         if ($path === false) 
95         {
96             bindtextdomain($domain, "./locale/");
97         } 
98         else 
99         { 
100             bindtextdomain($domain, $path);
101         }
102         //bind_textdomain_codeset($domain, $encoding);
103         textdomain($domain);
104     }
105     
106     /**
107      * Retrieve translation for specified key.
108      *
109      * @access private
110      */
111     function _get_translation($key)
112     {
113         return gettext($key);
114     }
115     
116
117     /**
118      * Reset interpolation variables.
119      */
120     function reset()
121     {
122         $this->_interpolation_vars = array();
123     }
124     
125     /**
126      * Set an interpolation variable.
127      */
128     function set_var($key, $value)
129     {
130         $this->_interpolation_vars[$key] = $value;
131     }
132
133     /**
134      * Set an associative array of interpolation variables.
135      */
136     function set_vars($hash)
137     {
138         $this->_interpolation_vars = array_merge($this->_interpolation_vars,
139                                                 $hash);
140     }
141     
142     /**
143      * Retrieve translation for specified key.
144      *
145      * @param  string $key  -- gettext msgid
146      * @throws GetText_Error
147      */
148     function gettext($key)
149     {
150         $value = $this->_get_translation($key);
151         if ($value === false) {
152             $str = sprintf('Unable to locate gettext key "%s"', $key);
153             //$err = new GetText_Error($str);
154             //return PEAR::raise_error($err);
155                         return raise_error("2 " . $str);
156         }
157         
158         while (preg_match('/\$\{(.*?)\}/sm', $value, $m)) {
159             list($src, $var) = $m;
160
161             // retrieve variable to interpolate in context, throw an exception
162             // if not found.
163             $var2 = $this->_get_var($var);
164             if ($var2 === false) {
165                 $str = sprintf('Interpolation error, var "%s" not set', $var);
166                 //$err = new GetText_Error($str);
167                 //return PEAR::raise_error($err);
168                 return raise_error("3 " . $str);
169             }
170             $value = str_replace($src, $var2, $value);
171         }
172         return $value;
173     }
174
175     /**
176      * Retrieve an interpolation variable value.
177      * 
178      * @return mixed
179      * @access private
180      */
181     function _get_var($name)
182     {
183         if (!array_key_exists($name, $this->_interpolation_vars)) {
184             return false;
185         }
186         return $this->_interpolation_vars[$name];
187     }
188 }
189
190
191 /**
192 * Implementation of get_text support for PHP.
193 *
194 * This implementation is abble to cache .po files into php files returning the
195 * domain translation hashtable.
196 *
197 * @access private
198 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
199 */
200 class gettext_php_support extends gettext_native_support
201 {
202     var $_path     = 'locale/';
203     var $_lang_code = false;
204     var $_domains  = array();
205     var $_end      = -1;
206     var $_jobs     = array();
207
208     /**
209      * Set the translation domain.
210      *
211      * @param  string $lang_code -- language code
212      * @throws GetText_Error
213      */
214     function set_language($lang_code, $encoding)
215     {
216         // if language already set, try to reload domains
217         if ($this->_lang_code !== false and $this->_lang_code != $lang_code) 
218         {
219             foreach ($this->_domains as $domain) 
220             {
221                 $this->_jobs[] = array($domain->name, $domain->path);
222             }
223             $this->_domains = array();
224             $this->_end = -1;
225         }
226         
227         $this->_lang_code = $lang_code;
228
229         // this allow us to set the language code after 
230         // domain list.
231         while (count($this->_jobs) > 0) 
232         {
233             list($domain, $path) = array_shift($this->_jobs);
234             $err = $this->add_domain($domain, $path);
235             // error raised, break jobs
236             /*if (PEAR::is_error($err)) {
237                 return $err;
238             }*/
239                         if (is_error($err)) 
240                         {
241                 return $err;
242             }            
243         }
244     }
245     
246     /**
247      * Add a translation domain.
248      *
249      * @param string $domain        -- Domain name
250      * @param string $path optional -- Repository path
251      * @throws GetText_Error
252      */
253     function add_domain($domain, $path = "./locale/")
254     {   
255         if (array_key_exists($domain, $this->_domains)) 
256         { 
257             return; 
258         }
259         
260         if (!$this->_lang_code) 
261         { 
262             $this->_jobs[] = array($domain, $path); 
263             return;
264         }
265
266         $err = $this->_load_domain($domain, $path);
267         if ($err != 0) 
268         {
269             return $err;
270         }
271
272         $this->_end++;
273     }
274
275     /**
276      * Load a translation domain file.
277      *
278      * This method cache the translation hash into a php file unless
279      * GETTEXT_NO_CACHE is defined.
280      * 
281      * @param  string $domain        -- Domain name
282      * @param  string $path optional -- Repository
283      * @throws GetText_Error
284      * @access private
285      */
286     function _load_domain($domain, $path = "./locale")
287     {
288         $src_domain = $path . "/$this->_lang_code/LC_MESSAGES/$domain.po";
289         $php_domain = $path . "/$this->_lang_code/LC_MESSAGES/$domain.php";
290         
291         if (!file_exists($src_domain)) 
292         {
293             $str = sprintf('Domain file "%s" not found.', $src_domain);
294             //$err = new GetText_Error($str);
295             //return PEAR::raise_error($err);
296                         return raise_error("4 " . $str);
297         }
298         
299         $d = new gettext_domain();
300         $d->name = $domain;
301         $d->path = $path;
302         
303         if (!file_exists($php_domain) || (filemtime($php_domain) < filemtime($src_domain))) 
304         {
305             
306             // parse and compile translation table
307             $parser = new gettext_php_support_parser();
308             $hash   = $parser->parse($src_domain);
309             if (!defined('GETTEXT_NO_CACHE')) 
310             {
311                 $comp = new gettext_php_support_compiler();
312                 $err  = $comp->compile($hash, $src_domain);
313                 /*if (PEAR::is_error($err)) { 
314                     return $err; 
315                 }*/
316                         if (is_error($err)) 
317                         {
318                     return $err;
319                 } 
320             }
321             $d->_keys = $hash;
322         } 
323         else 
324         {
325             $d->_keys = include $php_domain;
326         }
327         $this->_domains[] = &$d;
328     }
329     
330     /**
331      * Implementation of gettext message retrieval.
332      */
333     function _get_translation($key)
334     {
335         for ($i = $this->_end; $i >= 0; $i--) 
336         {
337             if ($this->_domains[$i]->has_key($key)) 
338             {
339                 return $this->_domains[$i]->get($key);
340             }
341         }
342         return $key;
343     }
344 }
345
346 /**
347 * Class representing a domain file for a specified language.
348 *
349 * @access private
350 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
351 */
352 class gettext_domain
353 {
354     var $name;
355     var $path;
356
357     var $_keys = array();
358
359     function has_key($key)
360     {
361         return array_key_exists($key, $this->_keys);
362     }
363
364     function get($key)
365     {
366         return $this->_keys[$key];
367     }
368 }
369
370 /**
371 * This class is used to parse gettext '.po' files into php associative arrays.
372 *
373 * @access private
374 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
375 */
376 class gettext_php_support_parser 
377 {
378     var $_hash = array();
379     var $_current_key;
380     var $_current_value;
381     
382     /**
383      * Parse specified .po file.
384      *
385      * @return hashtable
386      * @throws GetText_Error
387      */
388     function parse($file)
389     {
390         $this->_hash = array();
391         $this->_current_key = false;
392         $this->_current_value = "";
393         
394         if (!file_exists($file)) 
395         {
396             $str = sprintf('Unable to locate file "%s"', $file);
397             //$err = new GetText_Error($str);
398             //return PEAR::raise_error($err);
399                         return raise_error($str);
400         }
401         $i = 0;
402         $lines = file($file);
403         foreach ($lines as $line) 
404         {
405             $this->_parse_line($line, ++$i);
406         }
407         $this->_store_key();
408
409         return $this->_hash;
410     }
411
412     /**
413      * Parse one po line.
414      *
415      * @access private
416      */
417     function _parse_line($line, $nbr)
418     {
419         if (preg_match('/^\s*?#/', $line)) { return; }
420         if (preg_match('/^\s*?msgid \"(.*?)(?!<\\\)\"/', $line, $m)) {
421             $this->_store_key();
422             $this->_current_key = $m[1];
423             return;
424         }
425         if (preg_match('/^\s*?msgstr \"(.*?)(?!<\\\)\"/', $line, $m)) {
426             $this->_current_value .= $m[1];
427             return;
428         }
429         if (preg_match('/^\s*?\"(.*?)(?!<\\\)\"/', $line, $m)) {
430             $this->_current_value .= $m[1];
431             return;
432         }
433     }
434
435     /**
436      * Store last key/value pair into building hashtable.
437      *
438      * @access private
439      */
440     function _store_key()
441     {
442         if ($this->_current_key === false) return;
443         $this->_current_value = str_replace('\\n', "\n", $this->_current_value);
444         $this->_hash[$this->_current_key] = $this->_current_value;
445         $this->_current_key = false;
446         $this->_current_value = "";
447     }
448 }
449
450
451 /**
452 * This class write a php file from a gettext hashtable.
453 *
454 * The produced file return the translation hashtable on include.
455
456 * @throws GetText_Error
457 * @access private
458 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
459 */
460 class gettext_php_support_compiler 
461 {
462     /**
463      * Write hash in an includable php file.
464      */
465     function compile(&$hash, $source_path)
466     {
467         $dest_path = preg_replace('/\.po$/', '.php', $source_path);
468         $fp = @fopen($dest_path, "w");
469         if (!$fp) 
470         {
471             $str = sprintf('Unable to open "%s" in write mode.', $dest_path);
472             //$err = new GetText_Error($str);
473             //return PEAR::raise_error($err);
474                         return raise_error($str);
475         }
476         fwrite($fp, '<?php' . "\n");
477         fwrite($fp, 'return array(' . "\n");
478         foreach ($hash as $key => $value) 
479         {
480             $key   = str_replace("'", "\\'", $key);
481             $value = str_replace("'", "\\'", $value);
482             fwrite($fp, '    \'' . $key . '\' => \'' . $value . "',\n");
483         }
484         fwrite($fp, ');' . "\n");
485         fwrite($fp, '?>');
486         fclose($fp);
487     }
488 }
489
490 /**
491 * get_text related error.
492 */
493 //class GetText_Error extends PEAR_Error {}
494
495 ?>