Development

/branches/1.0/lib/i18n/sfMessageSource_gettext.class.php

You must first sign up to be able to contribute.

root/branches/1.0/lib/i18n/sfMessageSource_gettext.class.php

Revision 6806, 10.9 kB (checked in by fabien, 7 years ago)

fixed phpdoc package/subpackage entries

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Rev Date
Line 
1 <?php
2
3 /**
4  * sfMessageSource_gettext class file.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the BSD License.
8  *
9  * Copyright(c) 2004 by Qiang Xue. All rights reserved.
10  *
11  * To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
12  * The latest version of PRADO can be obtained from:
13  * {@link http://prado.sourceforge.net/}
14  *
15  * @author     Wei Zhuo <weizhuo[at]gmail[dot]com>
16  * @version    $Id$
17  * @package    symfony
18  * @subpackage i18n
19  */
20
21 /**
22  * sfMessageSource_gettext class.
23  *
24  * Using Gettext MO format as the message source for translation.
25  * The gettext classes are based on PEAR's gettext MO and PO classes.
26  *
27  * See the MessageSource::factory() method to instantiate this class.
28  *
29  * @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
30  * @version v1.0, last update on Fri Dec 24 16:18:44 EST 2004
31  * @package    symfony
32  * @subpackage i18n
33  */
34 class sfMessageSource_gettext extends sfMessageSource
35 {
36   /**
37    * Message data filename extension.
38    * @var string
39    */
40   protected $dataExt = '.mo';
41
42   /**
43    * PO data filename extension
44    * @var string
45    */
46   protected $poExt = '.po';
47
48   /**
49    * Separator between culture name and source.
50    * @var string
51    */
52   protected $dataSeparator = '.';
53
54   function __construct($source)
55   {
56     $this->source = (string) $source;
57   }
58
59   /**
60    * Loads the messages from a MO file.
61    *
62    * @param string MO file.
63    * @return array of messages.
64    */
65   protected function &loadData($filename)
66   {
67     $mo = TGettext::factory('MO',$filename);
68     $mo->load();
69     $result = $mo->toArray();
70
71     $results = array();
72     $count = 0;
73     foreach ($result['strings'] as $source => $target)
74     {
75       $results[$source][] = $target//target
76       $results[$source][] = $count++; //id
77       $results[$source][] = '';       //comments
78     }
79
80     return $results;
81   }
82
83   /**
84    * Determines if the MO file source is valid.
85    *
86    * @param string MO file
87    * @return boolean true if valid, false otherwise.
88    */
89   protected function isValidSource($filename)
90   {
91     return is_file($filename);
92   }
93
94   /**
95    * Gets the MO file for a specific message catalogue and cultural variant.
96    *
97    * @param string message catalogue
98    * @return string full path to the MO file.
99    */
100   protected function getSource($variant)
101   {
102     return $this->source.'/'.$variant;
103   }
104
105   /**
106    * Gets the last modified unix-time for this particular catalogue+variant.
107    * Just use the file modified time.
108    *
109    * @param string catalogue+variant
110    * @return int last modified in unix-time format.
111    */
112   protected function getLastModified($source)
113   {
114     return is_file($source) ? filemtime($source) : 0;
115   }
116
117   /**
118    * Gets all the variants of a particular catalogue.
119    *
120    * @param string catalogue name
121    * @return array list of all variants for this catalogue.
122    */
123   protected function getCatalogueList($catalogue)
124   {
125     $variants = explode('_', $this->culture);
126     $source = $catalogue.$this->dataExt;
127
128     $catalogues = array($source);
129
130     $variant = null;
131
132     for ($i = 0, $max = count($variants); $i < $max; $i++)
133     {
134       if (strlen($variants[$i]) > 0)
135       {
136         $variant .= $variant ? '_'.$variants[$i] : $variants[$i];
137         $catalogues[] = $catalogue.$this->dataSeparator.$variant.$this->dataExt;
138       }
139     }
140     $byDir = $this->getCatalogueByDir($catalogue);
141     $catalogues = array_merge($byDir,array_reverse($catalogues));
142
143     return $catalogues;
144   }
145
146   /**
147    * Traverses through the directory structure to find the catalogues.
148    * This should only be called by getCatalogueList()
149    *
150    * @param string a particular catalogue.
151    * @return array a list of catalogues.
152    * @see getCatalogueList()
153    */
154   protected function getCatalogueByDir($catalogue)
155   {
156     $variants = explode('_', $this->culture);
157     $catalogues = array();
158
159     $variant = null;
160
161     for($i = 0, $max = count($variants); $i < $max; $i++)
162     {
163       if (strlen($variants[$i]) > 0)
164       {
165         $variant .= $variant ? '_'.$variants[$i] : $variants[$i];
166         $catalogues[] = $variant.'/'.$catalogue.$this->dataExt;
167       }
168     }
169
170     return array_reverse($catalogues);
171   }
172
173   /**
174    * Gets the variant for a catalogue depending on the current culture.
175    *
176    * @param string catalogue
177    * @return string the variant.
178    * @see save()
179    * @see update()
180    * @see delete()
181    */
182   protected function getVariants($catalogue = 'messages')
183   {
184     if (empty($catalogue))
185     {
186       $catalogue = 'messages';
187     }
188
189     foreach ($this->getCatalogueList($catalogue) as $variant)
190     {
191       $file = $this->getSource($variant);
192       $po = $this->getPOFile($file);
193       if (is_file($file) || is_file($po))
194       {
195         return array($variant, $file, $po);
196       }
197     }
198
199     return false;
200   }
201
202   protected function getPOFile($MOFile)
203   {
204     return substr($MOFile, 0, strlen($MOFile) - strlen($this->dataExt)).$this->poExt;
205   }
206
207   /**
208    * Saves the list of untranslated blocks to the translation source.
209    * If the translation was not found, you should add those
210    * strings to the translation source via the <b>append()</b> method.
211    *
212    * @param string the catalogue to add to
213    * @return boolean true if saved successfuly, false otherwise.   
214    */
215   function save($catalogue = 'messages')
216   {
217     $messages = $this->untranslated;
218
219     if (count($messages) <= 0)
220     {
221       return false;
222     }
223
224     $variants = $this->getVariants($catalogue);
225
226     if ($variants)
227     {
228       list($variant, $MOFile, $POFile) = $variants;
229     }
230     else
231     {
232       return false;
233     }
234
235     if (is_writable($MOFile) == false)
236     {
237       throw new sfException(sprintf("Unable to save to file %s, file must be writable.", $MOFile));
238     }
239     if (is_writable($POFile) == false)
240     {
241       throw new sfException(sprintf("Unable to save to file %s, file must be writable.", $POFile));
242     }
243
244     // set the strings as untranslated.
245     $strings = array();
246     foreach ($messages as $message)
247     {
248       $strings[$message] = '';
249     }
250
251     // load the PO
252     $po = TGettext::factory('PO',$POFile);
253     $po->load();
254     $result = $po->toArray();
255
256     $existing = count($result['strings']);
257
258     // add to strings to the existing message list
259     $result['strings'] = array_merge($result['strings'],$strings);
260
261     $new = count($result['strings']);
262
263     if ($new > $existing)
264     {
265       // change the date 2004-12-25 12:26
266       $result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s');
267
268       $po->fromArray($result);
269       $mo = $po->toMO();
270       if ($po->save() && $mo->save($MOFile))
271       {
272         if (!empty($this->cache))
273         {
274           $this->cache->clean($variant, $this->culture);
275         }
276
277         return true;
278       }
279       else
280       {
281         return false;
282       }
283     }
284
285     return false;
286   }
287
288   /**
289    * Deletes a particular message from the specified catalogue.
290    *
291    * @param string the source message to delete.
292    * @param string the catalogue to delete from.
293    * @return boolean true if deleted, false otherwise.
294    */
295   function delete($message, $catalogue = 'messages')
296   {
297     $variants = $this->getVariants($catalogue);
298     if ($variants)
299     {
300       list($variant, $MOFile, $POFile) = $variants;
301     }
302     else
303     {
304       return false;
305     }
306
307     if (is_writable($MOFile) == false)
308     {
309       throw new sfException(sprintf("Unable to modify file %s, file must be writable.", $MOFile));
310     }
311
312     if (is_writable($POFile) == false)
313     {
314       throw new sfException(sprintf("Unable to modify file %s, file must be writable.", $POFile));
315     }
316
317     $po = TGettext::factory('PO', $POFile);
318     $po->load();
319     $result = $po->toArray();
320
321     foreach ($result['strings'] as $string => $value)
322     {
323       if ($string == $message)
324       {
325         $result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s');
326         unset($result['strings'][$string]);
327
328         $po->fromArray($result);
329         $mo = $po->toMO();
330         if ($po->save() && $mo->save($MOFile))
331         {
332           if (!empty($this->cache))
333           {
334             $this->cache->clean($variant, $this->culture);
335           }
336
337           return true;
338         }
339         else
340         {
341           return false;
342         }
343       }
344     }
345
346     return false;
347   }
348
349   /**
350    * Updates the translation.
351    *
352    * @param string the source string.
353    * @param string the new translation string.
354    * @param string comments
355    * @param string the catalogue of the translation.
356    * @return boolean true if translation was updated, false otherwise.
357    */
358   function update($text, $target, $comments, $catalogue = 'messages')
359   {
360     $variants = $this->getVariants($catalogue);
361     if ($variants)
362     {
363       list($variant, $MOFile, $POFile) = $variants;
364     }
365     else
366     {
367       return false;
368     }
369
370     if (is_writable($MOFile) == false)
371     {
372       throw new sfException(sprintf("Unable to update file %s, file must be writable.", $MOFile));
373     }
374
375     if (is_writable($POFile) == false)
376     {
377       throw new sfException(sprintf("Unable to update file %s, file must be writable.", $POFile));
378     }
379
380     $po = TGettext::factory('PO',$POFile);
381     $po->load();
382     $result = $po->toArray();
383
384     foreach ($result['strings'] as $string => $value)
385     {
386       if ($string == $text)
387       {
388         $result['strings'][$string] = $target;
389         $result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s');
390
391         $po->fromArray($result);
392         $mo = $po->toMO();
393
394         if ($po->save() && $mo->save($MOFile))
395         {
396           if (!empty($this->cache))
397           {
398             $this->cache->clean($variant, $this->culture);
399           }
400
401           return true;
402         }
403         else
404         {
405           return false;
406         }
407       }
408     }
409
410     return false;
411   }
412
413   /**
414    * Returns a list of catalogue as key and all it variants as value.
415    *
416    * @return array list of catalogues
417    */
418   function catalogues()
419   {
420     return $this->getCatalogues();
421   }
422
423   /**
424    * Returns a list of catalogue and its culture ID. This takes care
425    * of directory structures.
426    * E.g. array('messages','en_AU')
427    *
428    * @return array list of catalogues
429    */
430   protected function getCatalogues($dir = null, $variant = null)
431   {
432     $dir = $dir ? $dir : $this->source;
433     $files = scandir($dir);
434
435     $catalogue = array();
436
437     foreach ($files as $file)
438     {
439       if (is_dir($dir.'/'.$file) && preg_match('/^[a-z]{2}(_[A-Z]{2,3})?$/', $file))
440       {
441         $catalogue = array_merge($catalogue, $this->getCatalogues($dir.'/'.$file, $file));
442       }
443
444       $pos = strpos($file, $this->dataExt);
445
446       if ($pos > 0 && substr($file, -1 * strlen($this->dataExt)) == $this->dataExt)
447       {
448         $name = substr($file, 0, $pos);
449         $dot = strrpos($name, $this->dataSeparator);
450         $culture = $variant;
451         $cat = $name;
452         if (is_int($dot))
453         {
454           $culture = substr($name, $dot + 1, strlen($name));
455           $cat = substr($name, 0, $dot);
456         }
457         $details[0] = $cat;
458         $details[1] = $culture;
459
460         $catalogue[] = $details;
461       }
462     }
463     sort($catalogue);
464
465     return $catalogue;
466   }
467 }
468
Note: See TracBrowser for help on using the browser.