]> CyberLeo.Net >> Repos - Github/YOURLS.git/blob - includes/pomo/translations.php
Translation API! zomigod. First pass. See Issue 52.
[Github/YOURLS.git] / includes / pomo / translations.php
1 <?php\r
2 /**\r
3  * Class for a set of entries for translation and their associated headers\r
4  *\r
5  * @version $Id: translations.php 718 2012-10-31 00:32:02Z nbachiyski $\r
6  * @package pomo\r
7  * @subpackage translations\r
8  */\r
9 \r
10 require_once dirname(__FILE__) . '/entry.php';\r
11 \r
12 if ( !class_exists( 'Translations' ) ):\r
13 class Translations {\r
14         var $entries = array();\r
15         var $headers = array();\r
16 \r
17         /**\r
18          * Add entry to the PO structure\r
19          *\r
20          * @param object &$entry\r
21          * @return bool true on success, false if the entry doesn't have a key\r
22          */\r
23         function add_entry($entry) {\r
24                 if (is_array($entry)) {\r
25                         $entry = new Translation_Entry($entry);\r
26                 }\r
27                 $key = $entry->key();\r
28                 if (false === $key) return false;\r
29                 $this->entries[$key] = &$entry;\r
30                 return true;\r
31         }\r
32 \r
33         function add_entry_or_merge($entry) {\r
34                 if (is_array($entry)) {\r
35                         $entry = new Translation_Entry($entry);\r
36                 }\r
37                 $key = $entry->key();\r
38                 if (false === $key) return false;\r
39                 if (isset($this->entries[$key]))\r
40                         $this->entries[$key]->merge_with($entry);\r
41                 else\r
42                         $this->entries[$key] = &$entry;\r
43                 return true;\r
44         }\r
45 \r
46         /**\r
47          * Sets $header PO header to $value\r
48          *\r
49          * If the header already exists, it will be overwritten\r
50          *\r
51          * TODO: this should be out of this class, it is gettext specific\r
52          *\r
53          * @param string $header header name, without trailing :\r
54          * @param string $value header value, without trailing \n\r
55          */\r
56         function set_header($header, $value) {\r
57                 $this->headers[$header] = $value;\r
58         }\r
59 \r
60         function set_headers($headers) {\r
61                 foreach($headers as $header => $value) {\r
62                         $this->set_header($header, $value);\r
63                 }\r
64         }\r
65 \r
66         function get_header($header) {\r
67                 return isset($this->headers[$header])? $this->headers[$header] : false;\r
68         }\r
69 \r
70         function translate_entry(&$entry) {\r
71                 $key = $entry->key();\r
72                 return isset($this->entries[$key])? $this->entries[$key] : false;\r
73         }\r
74 \r
75         function translate($singular, $context=null) {\r
76                 $entry = new Translation_Entry(array('singular' => $singular, 'context' => $context));\r
77                 $translated = $this->translate_entry($entry);\r
78                 return ($translated && !empty($translated->translations))? $translated->translations[0] : $singular;\r
79         }\r
80 \r
81         /**\r
82          * Given the number of items, returns the 0-based index of the plural form to use\r
83          *\r
84          * Here, in the base Translations class, the common logic for English is implemented:\r
85          *      0 if there is one element, 1 otherwise\r
86          *\r
87          * This function should be overrided by the sub-classes. For example MO/PO can derive the logic\r
88          * from their headers.\r
89          *\r
90          * @param integer $count number of items\r
91          */\r
92         function select_plural_form($count) {\r
93                 return 1 == $count? 0 : 1;\r
94         }\r
95 \r
96         function get_plural_forms_count() {\r
97                 return 2;\r
98         }\r
99 \r
100         function translate_plural($singular, $plural, $count, $context = null) {\r
101                 $entry = new Translation_Entry(array('singular' => $singular, 'plural' => $plural, 'context' => $context));\r
102                 $translated = $this->translate_entry($entry);\r
103                 $index = $this->select_plural_form($count);\r
104                 $total_plural_forms = $this->get_plural_forms_count();\r
105                 if ($translated && 0 <= $index && $index < $total_plural_forms &&\r
106                                 is_array($translated->translations) &&\r
107                                 isset($translated->translations[$index]))\r
108                         return $translated->translations[$index];\r
109                 else\r
110                         return 1 == $count? $singular : $plural;\r
111         }\r
112 \r
113         /**\r
114          * Merge $other in the current object.\r
115          *\r
116          * @param Object &$other Another Translation object, whose translations will be merged in this one\r
117          * @return void\r
118          **/\r
119         function merge_with(&$other) {\r
120                 foreach( $other->entries as $entry ) {\r
121                         $this->entries[$entry->key()] = $entry;\r
122                 }\r
123         }\r
124 \r
125         function merge_originals_with(&$other) {\r
126                 foreach( $other->entries as $entry ) {\r
127                         if ( !isset( $this->entries[$entry->key()] ) )\r
128                                 $this->entries[$entry->key()] = $entry;\r
129                         else\r
130                                 $this->entries[$entry->key()]->merge_with($entry);\r
131                 }\r
132         }\r
133 }\r
134 \r
135 class Gettext_Translations extends Translations {\r
136         /**\r
137          * The gettext implementation of select_plural_form.\r
138          *\r
139          * It lives in this class, because there are more than one descendand, which will use it and\r
140          * they can't share it effectively.\r
141          *\r
142          */\r
143         function gettext_select_plural_form($count) {\r
144                 if (!isset($this->_gettext_select_plural_form) || is_null($this->_gettext_select_plural_form)) {\r
145                         list( $nplurals, $expression ) = $this->nplurals_and_expression_from_header($this->get_header('Plural-Forms'));\r
146                         $this->_nplurals = $nplurals;\r
147                         $this->_gettext_select_plural_form = $this->make_plural_form_function($nplurals, $expression);\r
148                 }\r
149                 return call_user_func($this->_gettext_select_plural_form, $count);\r
150         }\r
151 \r
152         function nplurals_and_expression_from_header($header) {\r
153                 if (preg_match('/^\s*nplurals\s*=\s*(\d+)\s*;\s+plural\s*=\s*(.+)$/', $header, $matches)) {\r
154                         $nplurals = (int)$matches[1];\r
155                         $expression = trim($this->parenthesize_plural_exression($matches[2]));\r
156                         return array($nplurals, $expression);\r
157                 } else {\r
158                         return array(2, 'n != 1');\r
159                 }\r
160         }\r
161 \r
162         /**\r
163          * Makes a function, which will return the right translation index, according to the\r
164          * plural forms header\r
165          */\r
166         function make_plural_form_function($nplurals, $expression) {\r
167                 $expression = str_replace('n', '$n', $expression);\r
168                 $func_body = "\r
169                         \$index = (int)($expression);\r
170                         return (\$index < $nplurals)? \$index : $nplurals - 1;";\r
171                 return create_function('$n', $func_body);\r
172         }\r
173 \r
174         /**\r
175          * Adds parantheses to the inner parts of ternary operators in\r
176          * plural expressions, because PHP evaluates ternary oerators from left to right\r
177          *\r
178          * @param string $expression the expression without parentheses\r
179          * @return string the expression with parentheses added\r
180          */\r
181         function parenthesize_plural_exression($expression) {\r
182                 $expression .= ';';\r
183                 $res = '';\r
184                 $depth = 0;\r
185                 for ($i = 0; $i < strlen($expression); ++$i) {\r
186                         $char = $expression[$i];\r
187                         switch ($char) {\r
188                                 case '?':\r
189                                         $res .= ' ? (';\r
190                                         $depth++;\r
191                                         break;\r
192                                 case ':':\r
193                                         $res .= ') : (';\r
194                                         break;\r
195                                 case ';':\r
196                                         $res .= str_repeat(')', $depth) . ';';\r
197                                         $depth= 0;\r
198                                         break;\r
199                                 default:\r
200                                         $res .= $char;\r
201                         }\r
202                 }\r
203                 return rtrim($res, ';');\r
204         }\r
205 \r
206         function make_headers($translation) {\r
207                 $headers = array();\r
208                 // sometimes \ns are used instead of real new lines\r
209                 $translation = str_replace('\n', "\n", $translation);\r
210                 $lines = explode("\n", $translation);\r
211                 foreach($lines as $line) {\r
212                         $parts = explode(':', $line, 2);\r
213                         if (!isset($parts[1])) continue;\r
214                         $headers[trim($parts[0])] = trim($parts[1]);\r
215                 }\r
216                 return $headers;\r
217         }\r
218 \r
219         function set_header($header, $value) {\r
220                 parent::set_header($header, $value);\r
221                 if ('Plural-Forms' == $header) {\r
222                         list( $nplurals, $expression ) = $this->nplurals_and_expression_from_header($this->get_header('Plural-Forms'));\r
223                         $this->_nplurals = $nplurals;\r
224                         $this->_gettext_select_plural_form = $this->make_plural_form_function($nplurals, $expression);\r
225                 }\r
226         }\r
227 }\r
228 endif;\r
229 \r
230 if ( !class_exists( 'NOOP_Translations' ) ):\r
231 /**\r
232  * Provides the same interface as Translations, but doesn't do anything\r
233  */\r
234 class NOOP_Translations {\r
235         var $entries = array();\r
236         var $headers = array();\r
237 \r
238         function add_entry($entry) {\r
239                 return true;\r
240         }\r
241 \r
242         function set_header($header, $value) {\r
243         }\r
244 \r
245         function set_headers($headers) {\r
246         }\r
247 \r
248         function get_header($header) {\r
249                 return false;\r
250         }\r
251 \r
252         function translate_entry(&$entry) {\r
253                 return false;\r
254         }\r
255 \r
256         function translate($singular, $context=null) {\r
257                 return $singular;\r
258         }\r
259 \r
260         function select_plural_form($count) {\r
261                 return 1 == $count? 0 : 1;\r
262         }\r
263 \r
264         function get_plural_forms_count() {\r
265                 return 2;\r
266         }\r
267 \r
268         function translate_plural($singular, $plural, $count, $context = null) {\r
269                         return 1 == $count? $singular : $plural;\r
270         }\r
271 \r
272         function merge_with(&$other) {\r
273         }\r
274 }\r
275 endif;\r