Development

HowToGenerateI18NFiles: sfMessageSource_XLIFF.class.php_patch01.php

You must first sign up to be able to contribute.

HowToGenerateI18NFiles: sfMessageSource_XLIFF.class.php_patch01.php

File sfMessageSource_XLIFF.class.php_patch01.php, 13.5 kB (added by mailto.ill@gmail.com, 11 years ago)

PHP 5.1 only! prevent PHP strict message and empty tag collapsing (i.e. <target></target> became <target />)

Line 
1 <?php
2
3 /**
4  * sfMessageSource_XLIFF 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: sfMessageSource_XLIFF.class.php 835 2006-02-15 20:57:57Z fabien $
17  * @package    symfony
18  * @subpackage i18n
19  */
20
21 /**
22  * sfMessageSource_XLIFF class.
23  *
24  * Using XML XLIFF format as the message source for translation.
25  * Details and example of XLIFF can be found in the following URLs.
26  *
27  * # http://www.opentag.com/xliff.htm
28  * # http://www-106.ibm.com/developerworks/xml/library/x-localis2/
29  *
30  * See the MessageSource::factory() method to instantiate this class.
31  *
32  * @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
33  * @version v1.0, last update on Fri Dec 24 16:18:44 EST 2004
34  * @package System.I18N.core
35  */
36 class sfMessageSource_XLIFF extends sfMessageSource
37 {
38   /**
39    * Message data filename extension.
40    * @var string
41    */
42   protected $dataExt = '.xml'
43  
44   /**
45    * Separator between culture name and source.
46    * @var string
47    */
48   protected $dataSeparator = '.';
49  
50   /**
51    * Constructor.
52    * @param string the directory where the messages are stored.
53    * @see MessageSource::factory();
54    */
55   function __construct($source)
56   {
57     $this->source = (string)$source
58   }
59
60   /**
61    * Load the messages from a XLIFF file.
62    * @param string XLIFF file.
63    * @return array of messages.
64    */
65   protected function &loadData($filename)
66   {
67     //load it.
68     
69     $XML = simplexml_load_file($filename);
70
71     if(!$XML) return false;
72
73     $translationUnit = $XML->xpath('//trans-unit');
74       
75     $translations = array();
76       
77     foreach($translationUnit as $unit)
78     {
79       $source = (string)$unit->source;
80       $translations[$source][] = (string)$unit->target;
81       $translations[$source][]= (string)$unit['id'];   
82       $translations[$source][]= (string)$unit->note;   
83     }   
84     return $translations;
85   }
86  
87   /**
88    * Get the last modified unix-time for this particular catalogue+variant.
89    * Just use the file modified time.
90    * @param string catalogue+variant
91    * @return int last modified in unix-time format.
92    */
93   protected function getLastModified($source)
94   {
95     if(is_file($source))
96       return filemtime($source);
97     else
98       return 0;
99   }
100  
101   /**
102    * Get the XLIFF file for a specific message catalogue and cultural
103    * vairant.
104    * @param string message catalogue
105    * @return string full path to the XLIFF file.
106    */
107   protected function getSource($variant)
108   {
109     return $this->source.'/'.$variant;
110   }
111  
112   /**
113    * Determin if the XLIFF file source is valid.
114    * @param string XLIFF file
115    * @return boolean true if valid, false otherwise.
116    */
117   protected function isValidSource($source)
118   {
119     return is_file($source);
120   }
121  
122   /**
123    * Get all the variants of a particular catalogue.
124    * @param string catalogue name
125    * @return array list of all variants for this catalogue.
126    */
127   protected function getCatalogueList($catalogue)
128   {
129     $variants = explode('_',$this->culture);
130     $source = $catalogue.$this->dataExt;
131     
132     $catalogues = array($source);
133
134     $variant = null;
135         
136     for($i = 0; $i < count($variants); $i++)
137     {           
138       if(strlen($variants[$i])>0)
139       {
140         $variant .= ($variant)?'_'.$variants[$i]:$variants[$i];
141         $catalogues[] = $catalogue.$this->dataSeparator.
142                 $variant.$this->dataExt;
143       }
144     }
145     
146     $byDir = $this->getCatalogueByDir($catalogue);
147     $catalogues = array_merge($byDir,array_reverse($catalogues));   
148     return $catalogues;
149   }
150  
151   /**
152    * Traverse through the directory structure to find the catalogues.
153    * This should only be called by getCatalogueList()
154    * @param string a particular catalogue.
155    * @return array a list of catalogues.
156    * @see getCatalogueList()
157    */
158   private function getCatalogueByDir($catalogue)
159   {
160     $variants = explode('_',$this->culture);
161     $catalogues = array();
162     
163     $variant = null;
164
165     for($i = 0; $i < count($variants); $i++)
166     {
167       if(strlen($variants[$i])>0)
168       {
169         $variant .= ($variant)?'_'.$variants[$i]:$variants[$i];
170         $catalogues[] = $variant.'/'.$catalogue.$this->dataExt;
171       }
172     }
173     return array_reverse($catalogues);
174   }
175  
176   /**
177    * Returns a list of catalogue and its culture ID.
178    * E.g. array('messages','en_AU')
179    * @return array list of catalogues
180    * @see getCatalogues()
181    */
182   public function catalogues()
183   {
184     return $this->getCatalogues();
185   }
186  
187   /**
188    * Returns a list of catalogue and its culture ID. This takes care
189    * of directory structures.
190    * E.g. array('messages','en_AU')
191    * @return array list of catalogues
192    */
193   protected function getCatalogues($dir=null,$variant=null)
194   {
195     $dir = $dir?$dir:$this->source;
196     $files = scandir($dir);
197     
198     $catalogue = array();
199     
200     foreach($files as $file)
201     {
202       if(is_dir($dir.'/'.$file)
203         && preg_match('/^[a-z]{2}(_[A-Z]{2,3})?$/',$file))
204       {
205         $catalogue = array_merge($catalogue,
206                 $this->getCatalogues($dir.'/'.$file, $file));
207       }
208       
209       $pos = strpos($file,$this->dataExt);     
210       if($pos >0
211         && substr($file,-1*strlen($this->dataExt)) == $this->dataExt)
212       {
213         $name = substr($file,0,$pos);
214         $dot = strrpos($name,$this->dataSeparator);
215         $culture = $variant;
216         $cat = $name;
217         if(is_int($dot))
218         {
219           $culture = substr($name, $dot+1,strlen($name));
220           $cat = substr($name,0,$dot);
221         }
222         $details[0] = $cat;
223         $details[1] = $culture;
224         
225         
226         $catalogue[] = $details;
227       }
228     }
229     sort($catalogue);
230     return $catalogue;
231   }
232  
233   /**
234    * Get the variant for a catalogue depending on the current culture.
235    * @param string catalogue
236    * @return string the variant.
237    * @see save()
238    * @see update()
239    * @see delete()
240    */
241   private function getVariants($catalogue='messages')
242   {
243     if(is_null($catalogue))
244       $catalogue = 'messages';
245     
246     foreach($this->getCatalogueList($catalogue) as $variant)
247     {
248       $file = $this->getSource($variant);
249       if(is_file($file))
250         return array($variant, $file);
251     }
252     return false;
253   }
254
255   /**
256    * Save the list of untranslated blocks to the translation source.
257    * If the translation was not found, you should add those
258    * strings to the translation source via the <b>append()</b> method.
259    * @param string the catalogue to add to
260    * @return boolean true if saved successfuly, false otherwise.
261    */
262   public function save($catalogue='messages')
263   {
264     $messages = $this->untranslated
265     if(count($messages) <= 0) return false;   
266
267     $variants = $this->getVariants($catalogue);
268     if($variants)
269       list($variant, $filename) = $variants;
270     else
271       return false;
272     if(is_writable($filename) == false)
273       throw new sfException("Unable to save to file {$filename}, file must be writable.");
274     
275     //create a new dom, import the existing xml
276     $dom = new DOMDocument();
277         $dom->load($filename);
278     
279     //find the body element
280     $xpath = new DomXPath($dom);
281       $body = $xpath->query('//body')->item(0);
282       
283     $count = $xpath->query('//trans-unit')->length;
284       
285     //for each message add it to the XML file using DOM
286       foreach($messages as $message)
287       {
288       $unit = $dom->createElement('trans-unit');
289       $unit->setAttribute('id',++$count);
290     
291       $source = $dom->createElement('source', $message);       
292       $target = $dom->createElement('target','');
293     
294       $unit->appendChild($dom->createTextNode("\n"));
295       $unit->appendChild($source);
296       $unit->appendChild($dom->createTextNode("\n"));
297       $unit->appendChild($target);
298       $unit->appendChild($dom->createTextNode("\n"));
299
300       $body->appendChild($dom->createTextNode("\n"));
301       $body->appendChild($unit);
302       $body->appendChild($dom->createTextNode("\n"));
303       }
304     
305       
306       $fileNode = $xpath->query('//file')->item(0);
307       $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
308       
309       //save it and clear the cache for this variant
310       $dom->save($filename, LIBXML_NOEMPTYTAG);
311     if(!empty($this->cache))
312         $this->cache->clean($variant, $this->culture);
313       
314       return true;
315   }
316  
317   /**
318    * Update the translation.
319    * @param string the source string.
320    * @param string the new translation string.
321    * @param string comments
322    * @param string the catalogue to save to.
323    * @return boolean true if translation was updated, false otherwise.
324    */
325   public function update($text, $target, $comments, $catalogue='messages')
326   {   
327     $variants = $this->getVariants($catalogue);
328     if($variants)
329       list($variant, $filename) = $variants;
330     else
331       return false;   
332       
333     if(is_writable($filename) == false)
334       throw new sfException("Unable to update file {$filename}, file must be writable.");
335     
336     //create a new dom, import the existing xml
337     $dom = DOMDocument::load($filename);
338     
339     //find the body element
340     $xpath = new DomXPath($dom);   
341     $units = $xpath->query('//trans-unit');
342     
343     //for each of the existin units
344     foreach($units as $unit)
345     {
346       $found = false;
347       $targetted = false;
348       $commented = false;
349       
350       //in each unit, need to find the source, target and comment nodes
351       //it will assume that the source is before the target.
352       foreach($unit->childNodes as $node)
353       {
354         //source node
355         if($node->nodeName == 'source'
356           && $node->firstChild->wholeText == $text)
357         {
358             $found = true;
359         }
360         
361         //found source, get the target and notes
362         if($found)
363         {
364           //set the new translated string
365           if($node->nodeName == 'target')
366           {
367             $node->nodeValue = $target;
368             $targetted = true;
369           }   
370           //set the notes     
371           if(!empty($comments) && $node->nodeName == 'note')
372           {
373             $node->nodeValue = $comments;
374             $commented = true;
375           }
376         }
377       }
378       
379       //append a target
380       if($found && !$targetted)
381         $unit->appendChild($dom->createElement('target',$target));
382         
383       //append a note
384       if($found && !$commented && !empty($comments))
385         $unit->appendChild($dom->createElement('note',$comments));
386       
387       //finished searching
388       if($found) break;
389     }
390     
391       $fileNode = $xpath->query('//file')->item(0);
392       $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
393           
394     if($dom->save($filename) >0)
395     {
396       if(!empty($this->cache))
397         $this->cache->clean($variant, $this->culture);   
398       return true;
399     }
400     
401     return false;
402   }
403  
404   /**
405    * Delete a particular message from the specified catalogue.
406    * @param string the source message to delete.
407    * @param string the catalogue to delete from.
408    * @return boolean true if deleted, false otherwise.
409    */
410   public function delete($message, $catalogue='messages')
411   {
412     $variants = $this->getVariants($catalogue);
413     if($variants)
414       list($variant, $filename) = $variants;
415     else
416       return false;
417           
418     if(is_writable($filename) == false)
419       throw new sfException("Unable to modify file {$filename}, file must be writable.");
420     
421     //create a new dom, import the existing xml
422     $dom = DOMDocument::load($filename);
423     
424     //find the body element
425     $xpath = new DomXPath($dom);   
426     $units = $xpath->query('//trans-unit');
427       
428     //for each of the existin units
429     foreach($units as $unit)
430     {
431       //in each unit, need to find the source, target and comment nodes
432       //it will assume that the source is before the target.
433       foreach($unit->childNodes as $node)
434       {
435         //source node
436         if($node->nodeName == 'source'
437           && $node->firstChild->wholeText == $message)
438         {
439           
440           //we found it, remove and save the xml file.
441           $unit->parentNode->removeChild($unit);
442           
443             $fileNode = $xpath->query('//file')->item(0);
444             $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
445           
446           if($dom->save($filename) >0)
447           {
448             if(!empty($this->cache))
449               $this->cache->clean($variant, $this->culture);   
450             return true;
451           }
452           else return false;
453                   
454         }
455       }
456       
457     }
458
459     return false;
460   }
461  
462   /**
463   * Create an empty XLIFF file
464   * @param string variant to create
465   * @param string the default language (source)
466   * @param string encoding for output file
467   * @return the number of byte written
468   */
469   function init($variant, $defaultCuluture, $encoding, $catalogue='messages')
470   {
471     $result = 0;
472     
473     $fileName = $this->getSource($catalogue.$this->dataSeparator.$variant.$this->dataExt);
474
475     if (!$this->isValidSource($fileName) )
476     {
477       $out = '<?xml version="1.0" encoding="';
478       $out .= ($encoding)?'utf-8"?>'."\n":$encoding.'" ?>'."\n";
479       $out .= '<xliff version="1.0">'."\n";
480       $out .= '  <file orginal="global" source-language="'.$defaultCuluture.'" datatype="plaintext" date="2006-03-22T00:15:43Z">'."\n";
481       $out .= '    <body>'."\n";
482       $out .= '    </body>'."\n";
483       $out .= '  </file>'."\n";
484       $out .= '</xliff>'."\n";
485       
486       $result = file_put_contents($fileName, $out);
487
488     }
489     
490     return $result;
491   }
492  
493 }
494
495 ?>