f2115a9aa95a17b46932072b77faab517d6554e8
[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         error_log($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
77                 // cover a couple of country/encoding variants 
78                 $up = strtoupper($encoding);
79                 $low = strtolower($encoding);
80                 $lshort = strtr($up, '-','');
81                 $ushort = strtr($low, '-','');
82
83         $set = setlocale(LC_ALL, $lang_code.".".$encoding, 
84                         $lang_code.".".$up, $lang_code.".".$low,
85                         $lang_code.".".$ushort, $lang_code.".".$lshort);
86                         
87         setlocale(LC_NUMERIC, 'C'); // important for numeric presentation etc.
88         if ($set === false) 
89         {
90             $str = sprintf('language code "%s", encoding "%s" not supported by your system',
91                 $lang_code, $encoding);
92             //$err = new GetText_Error($str);
93             //return PEAR::raise_error($err);
94                         return raise_error("1 " . $str);
95         }
96                 //return 0;
97     }
98     /**
99          *      Check system support for given language nedded for gettext.
100          */
101         function check_support($lang_code, $encoding)
102     {
103                 $old = setlocale(LC_MESSAGES, '0');
104                 $up = strtoupper($encoding);
105                 $low = strtolower($encoding);
106                 $lshort = strtr($up, '-','');
107                 $ushort = strtr($low, '-','');
108
109         $test = setlocale(LC_MESSAGES,
110                         $lang_code.".".$encoding, 
111                         $lang_code.".".$up,
112                         $lang_code.".".$low,
113                         $lang_code.".".$ushort,
114                         $lang_code.".".$lshort) !== false;
115
116                 setlocale(LC_MESSAGES, $old);
117                 return $test;
118         }
119     /**
120      * Add a translation domain.
121      */
122     function add_domain($domain, $path=false, $version='')
123     {
124         if ($path === false) 
125                 $path = "./locale";
126                 if ($version) {
127         // To avoid need for apache server restart after change of *.mo file
128         // we have to include file version as part of filename.
129         // This is alternative naming convention: $domain = $version.'/'.$domain;
130                         $domain .= '-'.$version;
131                 }
132         bindtextdomain($domain, $path);
133         //bind_textdomain_codeset($domain, $encoding);
134         textdomain($domain);
135     }
136
137     /**
138      * Retrieve translation for specified key.
139      *
140      * @access private
141      */
142     function _get_translation($key)
143     {
144         return gettext($key);
145     }
146     
147
148     /**
149      * Reset interpolation variables.
150      */
151     function reset()
152     {
153         $this->_interpolation_vars = array();
154     }
155     
156     /**
157      * Set an interpolation variable.
158      */
159     function set_var($key, $value)
160     {
161         $this->_interpolation_vars[$key] = $value;
162     }
163
164     /**
165      * Set an associative array of interpolation variables.
166      */
167     function set_vars($hash)
168     {
169         $this->_interpolation_vars = array_merge($this->_interpolation_vars,
170                                                 $hash);
171     }
172     
173     /**
174      * Retrieve translation for specified key.
175      *
176      * @param  string $key  -- gettext msgid
177      * @throws GetText_Error
178      */
179     function gettext($key)
180     {
181         $value = $this->_get_translation($key);
182         if ($value === false) {
183             $str = sprintf('Unable to locate gettext key "%s"', $key);
184             //$err = new GetText_Error($str);
185             //return PEAR::raise_error($err);
186                         return raise_error("2 " . $str);
187         }
188         
189         while (preg_match('/\$\{(.*?)\}/sm', $value, $m)) {
190             list($src, $var) = $m;
191
192             // retrieve variable to interpolate in context, throw an exception
193             // if not found.
194             $var2 = $this->_get_var($var);
195             if ($var2 === false) {
196                 $str = sprintf('Interpolation error, var "%s" not set', $var);
197                 //$err = new GetText_Error($str);
198                 //return PEAR::raise_error($err);
199                 return raise_error("3 " . $str);
200             }
201             $value = str_replace($src, $var2, $value);
202         }
203         return $value;
204     }
205
206     /**
207      * Retrieve an interpolation variable value.
208      * 
209      * @return mixed
210      * @access private
211      */
212     function _get_var($name)
213     {
214         if (!array_key_exists($name, $this->_interpolation_vars)) {
215             return false;
216         }
217         return $this->_interpolation_vars[$name];
218     }
219 }
220
221
222 /**
223 * Implementation of get_text support for PHP.
224 *
225 * This implementation is abble to cache .po files into php files returning the
226 * domain translation hashtable.
227 *
228 * @access private
229 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
230 */
231 class gettext_php_support extends gettext_native_support
232 {
233     var $_path     = 'locale/';
234     var $_lang_code = false;
235     var $_domains  = array();
236     var $_end      = -1;
237     var $_jobs     = array();
238
239     /**
240      * Set the translation domain.
241      *
242      * @param  string $lang_code -- language code
243      * @throws GetText_Error
244      */
245     function set_language($lang_code, $encoding)
246     {
247         // if language already set, try to reload domains
248         if ($this->_lang_code !== false and $this->_lang_code != $lang_code) 
249         {
250             foreach ($this->_domains as $domain) 
251             {
252                 $this->_jobs[] = array($domain->name, $domain->path);
253             }
254             $this->_domains = array();
255             $this->_end = -1;
256         }
257         
258         $this->_lang_code = $lang_code;
259
260         // this allow us to set the language code after 
261         // domain list.
262         while (count($this->_jobs) > 0) 
263         {
264             list($domain, $path) = array_shift($this->_jobs);
265             $err = $this->add_domain($domain, $path);
266             // error raised, break jobs
267             /*if (PEAR::is_error($err)) {
268                 return $err;
269             }*/
270                         if (is_error($err)) 
271                         {
272                 return $err;
273             }            
274         }
275     }
276     /**
277          *      Check system support for given language (dummy).
278          */
279         function check_support($lang_code, $encoding)
280     {
281                 return true;
282     }
283     /**
284      * Add a translation domain.
285      *
286      * @param string $domain        -- Domain name
287      * @param string $path optional -- Repository path
288      * @throws GetText_Error
289      */
290     function add_domain($domain, $path = "./locale/", $version ='')
291     {   
292         if ($version) {
293                         $domain .= '-'.$version;
294                 }
295
296         if (array_key_exists($domain, $this->_domains)) 
297         { 
298             return; 
299         }
300         
301         if (!$this->_lang_code) 
302         { 
303             $this->_jobs[] = array($domain, $path); 
304             return;
305         }
306
307         $err = $this->_load_domain($domain, $path);
308         if ($err != 0) 
309         {
310             return $err;
311         }
312
313         $this->_end++;
314     }
315
316     /**
317      * Load a translation domain file.
318      *
319      * This method cache the translation hash into a php file unless
320      * GETTEXT_NO_CACHE is defined.
321      * 
322      * @param  string $domain        -- Domain name
323      * @param  string $path optional -- Repository
324      * @throws GetText_Error
325      * @access private
326      */
327     function _load_domain($domain, $path = "./locale")
328     {
329         $src_domain = $path . "/$this->_lang_code/LC_MESSAGES/$domain.po";
330         $php_domain = $path . "/$this->_lang_code/LC_MESSAGES/$domain.php";
331         
332         if (!file_exists($src_domain)) 
333         {
334             $str = sprintf('Domain file "%s" not found.', $src_domain);
335             //$err = new GetText_Error($str);
336             //return PEAR::raise_error($err);
337                         return raise_error("4 " . $str);
338         }
339         
340         $d = new gettext_domain();
341         $d->name = $domain;
342         $d->path = $path;
343         
344         if (!file_exists($php_domain) || (filemtime($php_domain) < filemtime($src_domain))) 
345         {
346             
347             // parse and compile translation table
348             $parser = new gettext_php_support_parser();
349             $hash   = $parser->parse($src_domain);
350             if (!defined('GETTEXT_NO_CACHE')) 
351             {
352                 $comp = new gettext_php_support_compiler();
353                 $err  = $comp->compile($hash, $src_domain);
354                 /*if (PEAR::is_error($err)) { 
355                     return $err; 
356                 }*/
357                         if (is_error($err)) 
358                         {
359                     return $err;
360                 } 
361             }
362             $d->_keys = $hash;
363         } 
364         else 
365         {
366             $d->_keys = include $php_domain;
367         }
368         $this->_domains[] = &$d;
369     }
370     
371     /**
372      * Implementation of gettext message retrieval.
373      */
374     function _get_translation($key)
375     {
376         for ($i = $this->_end; $i >= 0; $i--) 
377         {
378             if ($this->_domains[$i]->has_key($key)) 
379             {
380                 return $this->_domains[$i]->get($key);
381             }
382         }
383         return $key;
384     }
385 }
386
387 /**
388 * Class representing a domain file for a specified language.
389 *
390 * @access private
391 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
392 */
393 class gettext_domain
394 {
395     var $name;
396     var $path;
397
398     var $_keys = array();
399
400     function has_key($key)
401     {
402         return array_key_exists($key, $this->_keys);
403     }
404
405     function get($key)
406     {
407         return $this->_keys[$key];
408     }
409 }
410
411 /**
412 * This class is used to parse gettext '.po' files into php associative arrays.
413 *
414 * @access private
415 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
416 */
417 class gettext_php_support_parser 
418 {
419     var $_hash = array();
420     var $_current_key;
421     var $_current_value;
422     
423     /**
424      * Parse specified .po file.
425      *
426      * @return hashtable
427      * @throws GetText_Error
428      */
429     function parse($file)
430     {
431         $this->_hash = array();
432         $this->_current_key = false;
433         $this->_current_value = "";
434         
435         if (!file_exists($file)) 
436         {
437             $str = sprintf('Unable to locate file "%s"', $file);
438             //$err = new GetText_Error($str);
439             //return PEAR::raise_error($err);
440                         return raise_error($str);
441         }
442         $i = 0;
443         $lines = file($file);
444         foreach ($lines as $line) 
445         {
446             $this->_parse_line($line, ++$i);
447         }
448         $this->_store_key();
449
450         return $this->_hash;
451     }
452
453     /**
454      * Parse one po line.
455      *
456      * @access private
457      */
458     function _parse_line($line, $nbr)
459     {
460         if (preg_match('/^\s*?#/', $line)) { return; }
461         if (preg_match('/^\s*?msgid \"(.*?)(?!<\\\)\"/', $line, $m)) {
462             $this->_store_key();
463             $this->_current_key = $m[1];
464             return;
465         }
466         if (preg_match('/^\s*?msgstr \"(.*?)(?!<\\\)\"/', $line, $m)) {
467             $this->_current_value .= $m[1];
468             return;
469         }
470         if (preg_match('/^\s*?\"(.*?)(?!<\\\)\"/', $line, $m)) {
471             $this->_current_value .= $m[1];
472             return;
473         }
474     }
475
476     /**
477      * Store last key/value pair into building hashtable.
478      *
479      * @access private
480      */
481     function _store_key()
482     {
483         if ($this->_current_key === false) return;
484         $this->_current_value = str_replace('\\n', "\n", $this->_current_value);
485         $this->_hash[$this->_current_key] = $this->_current_value;
486         $this->_current_key = false;
487         $this->_current_value = "";
488     }
489 }
490
491
492 /**
493 * This class write a php file from a gettext hashtable.
494 *
495 * The produced file return the translation hashtable on include.
496
497 * @throws GetText_Error
498 * @access private
499 * @author Laurent Bedubourg <laurent.bedubourg@free.fr>
500 */
501 class gettext_php_support_compiler 
502 {
503     /**
504      * Write hash in an includable php file.
505      */
506     function compile(&$hash, $source_path)
507     {
508         $dest_path = preg_replace('/\.po$/', '.php', $source_path);
509         $fp = @fopen($dest_path, "w");
510         if (!$fp) 
511         {
512             $str = sprintf('Unable to open "%s" in write mode.', $dest_path);
513             //$err = new GetText_Error($str);
514             //return PEAR::raise_error($err);
515                         return raise_error($str);
516         }
517         fwrite($fp, '<?php' . "\n");
518         fwrite($fp, 'return array(' . "\n");
519         foreach ($hash as $key => $value) 
520         {
521             $key   = str_replace("'", "\\'", $key);
522             $value = str_replace("'", "\\'", $value);
523             fwrite($fp, '    \'' . $key . '\' => \'' . $value . "',\n");
524         }
525         fwrite($fp, ');' . "\n");
526         fwrite($fp, '?>');
527         fclose($fp);
528     }
529 }
530
531 /**
532 * get_text related error.
533 */
534 //class GetText_Error extends PEAR_Error {}
535
536 ?>