ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
Language.php
Go to the documentation of this file.
1<?php
2namespace Gettext\Languages;
3
4use Exception;
5
10{
15 public $id;
20 public $name;
30 public $script;
35 public $territory;
50 public $formula;
56 private function __construct($info)
57 {
58 $this->id = $info['id'];
59 $this->name = $info['name'];
60 $this->supersededBy = isset($info['supersededBy']) ? $info['supersededBy'] : null;
61 $this->script = isset($info['script']) ? $info['script'] : null;
62 $this->territory = isset($info['territory']) ? $info['territory'] : null;
63 $this->baseLanguage = isset($info['baseLanguage']) ? $info['baseLanguage'] : null;
64 // Let's build the category list
65 $this->categories = array();
66 foreach ($info['categories'] as $cldrCategoryId => $cldrFormulaAndExamples) {
67 $category = new Category($cldrCategoryId, $cldrFormulaAndExamples);
68 foreach ($this->categories as $c) {
69 if ($category->id === $c->id) {
70 throw new Exception("The category '{$category->id}' is specified more than once");
71 }
72 }
73 $this->categories[] = $category;
74 }
75 if (empty($this->categories)) {
76 throw new Exception("The language '{$info['id']}' does not have any plural category");
77 }
78 // Let's sort the categories from 'zero' to 'other'
79 usort($this->categories, function (Category $category1, Category $category2) {
80 return array_search($category1->id, CldrData::$categories) - array_search($category2->id, CldrData::$categories);
81 });
82 // The 'other' category should always be there
83 if ($this->categories[count($this->categories) - 1]->id !== CldrData::OTHER_CATEGORY) {
84 throw new Exception("The language '{$info['id']}' does not have the '".CldrData::OTHER_CATEGORY."' plural category");
85 }
89 $this->formula = $this->buildFormula();
90 }
96 public static function getAll()
97 {
98 $result = array();
99 foreach (array_keys(CldrData::getLanguageNames()) as $cldrLanguageId) {
100 $result[] = new Language(CldrData::getLanguageInfo($cldrLanguageId));
101 }
102
103 return $result;
104 }
110 public static function getById($id)
111 {
112 $result = null;
114 if (isset($info)) {
115 $result = new Language($info);
116 }
117
118 return $result;
119 }
120
126 private function checkAlwaysTrueCategories()
127 {
128 $alwaysTrueCategory = null;
129 foreach ($this->categories as $category) {
130 if ($category->formula === true) {
131 if (!isset($category->examples)) {
132 throw new Exception("The category '{$category->id}' should always occur, but it does not have examples (so for CLDR it will never occur for integers!)");
133 }
134 $alwaysTrueCategory = $category;
135 break;
136 }
137 }
138 if (isset($alwaysTrueCategory)) {
139 foreach ($this->categories as $category) {
140 if (($category !== $alwaysTrueCategory) && isset($category->examples)) {
141 throw new Exception("The category '{$category->id}' should never occur, but it has some examples (so for CLDR it will occur!)");
142 }
143 }
144 $alwaysTrueCategory->id = CldrData::OTHER_CATEGORY;
145 $alwaysTrueCategory->formula = null;
146 $this->categories = array($alwaysTrueCategory);
147 }
148 }
154 private function checkAlwaysFalseCategories()
155 {
156 $filtered = array();
157 foreach ($this->categories as $category) {
158 if ($category->formula === false) {
159 if (isset($category->examples)) {
160 throw new Exception("The category '{$category->id}' should never occur, but it has examples (so for CLDR it may occur!)");
161 }
162 } else {
163 $filtered[] = $category;
164 }
165 }
166 $this->categories = $filtered;
167 }
175 {
176 $allCategoriesIds = array();
177 $goodCategories = array();
178 $badCategories = array();
179 $badCategoriesIds = array();
180 foreach ($this->categories as $category) {
181 $allCategoriesIds[] = $category->id;
182 if (isset($category->examples)) {
183 $goodCategories[] = $category;
184 } else {
185 $badCategories[] = $category;
186 $badCategoriesIds[] = $category->id;
187 }
188 }
189 if (empty($badCategories)) {
190 return;
191 }
192 $removeCategoriesWithoutExamples = false;
193 switch (implode(',', $badCategoriesIds).'@'.implode(',', $allCategoriesIds)) {
195 switch ($this->buildFormula()) {
196 case '(n % 10 == 1 && n % 100 != 11) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : ((n % 10 == 0 || n % 10 >= 5 && n % 10 <= 9 || n % 100 >= 11 && n % 100 <= 14) ? 2 : 3))':
197 // Numbers ending with 0 => case 2 ('many')
198 // Numbers ending with 1 but not with 11 => case 0 ('one')
199 // Numbers ending with 11 => case 2 ('many')
200 // Numbers ending with 2 but not with 12 => case 1 ('few')
201 // Numbers ending with 12 => case 2 ('many')
202 // Numbers ending with 3 but not with 13 => case 1 ('few')
203 // Numbers ending with 13 => case 2 ('many')
204 // Numbers ending with 4 but not with 14 => case 1 ('few')
205 // Numbers ending with 14 => case 2 ('many')
206 // Numbers ending with 5 => case 2 ('many')
207 // Numbers ending with 6 => case 2 ('many')
208 // Numbers ending with 7 => case 2 ('many')
209 // Numbers ending with 8 => case 2 ('many')
210 // Numbers ending with 9 => case 2 ('many')
211 // => the 'other' case never occurs: use 'other' for 'many'
212 $removeCategoriesWithoutExamples = true;
213 break;
214 case '(n == 1) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : ((n != 1 && (n % 10 == 0 || n % 10 == 1) || n % 10 >= 5 && n % 10 <= 9 || n % 100 >= 12 && n % 100 <= 14) ? 2 : 3))':
215 // Numbers ending with 0 => case 2 ('many')
216 // Numbers ending with 1 but not number 1 => case 2 ('many')
217 // Number 1 => case 0 ('one')
218 // Numbers ending with 2 but not with 12 => case 1 ('few')
219 // Numbers ending with 12 => case 2 ('many')
220 // Numbers ending with 3 but not with 13 => case 1 ('few')
221 // Numbers ending with 13 => case 2 ('many')
222 // Numbers ending with 4 but not with 14 => case 1 ('few')
223 // Numbers ending with 14 => case 2 ('many')
224 // Numbers ending with 5 => case 2 ('many')
225 // Numbers ending with 6 => case 2 ('many')
226 // Numbers ending with 7 => case 2 ('many')
227 // Numbers ending with 8 => case 2 ('many')
228 // Numbers ending with 9 => case 2 ('many')
229 // => the 'other' case never occurs: use 'other' for 'many'
230 $removeCategoriesWithoutExamples = true;
231 break;
232 }
233 }
234 if (!$removeCategoriesWithoutExamples) {
235 throw new Exception("Unhandled case of plural categories without examples '".implode(', ', $badCategoriesIds)."' out of '".implode(', ', $allCategoriesIds)."'");
236 }
237 if ($badCategories[count($badCategories) - 1]->id === CldrData::OTHER_CATEGORY) {
238 // We're removing the 'other' cagory: let's change the last good category to 'other'
239 $lastGood = $goodCategories[count($goodCategories) - 1];
240 $lastGood->id = CldrData::OTHER_CATEGORY;
241 $lastGood->formula = null;
242 }
243 $this->categories = $goodCategories;
244 }
249 private function buildFormula()
250 {
251 $numCategories = count($this->categories);
252 switch ($numCategories) {
253 case 1:
254 // Just one category
255 return '0';
256 case 2:
257 return self::reduceFormula(self::reverseFormula($this->categories[0]->formula));
258 default:
259 $formula = strval($numCategories - 1);
260 for ($i = $numCategories - 2; $i >= 0; $i--) {
261 $f = self::reduceFormula($this->categories[$i]->formula);
262 if (!preg_match('/^\‍([^()]+\‍)$/', $f)) {
263 $f = "($f)";
264 }
265 $formula = "$f ? $i : $formula";
266 if ($i > 0) {
267 $formula = "($formula)";
268 }
269 }
270
271 return $formula;
272 }
273 }
280 private static function reverseFormula($formula)
281 {
282 if (preg_match('/^n( % \d+)? == \d+(\.\.\d+|,\d+)*?$/', $formula)) {
283 return str_replace(' == ', ' != ', $formula);
284 }
285 if (preg_match('/^n( % \d+)? != \d+(\.\.\d+|,\d+)*?$/', $formula)) {
286 return str_replace(' != ', ' == ', $formula);
287 }
288 if (preg_match('/^\‍(?n == \d+ \|\| n == \d+\‍)?$/', $formula)) {
289 return trim(str_replace(array(' == ', ' || '), array(' != ', ' && '), $formula), '()');
290 }
291 $m = null;
292 if (preg_match('/^(n(?: % \d+)?) == (\d+) && (n(?: % \d+)?) != (\d+)$/', $formula, $m)) {
293 return "{$m[1]} != {$m[2]} || {$m[3]} == {$m[4]}";
294 }
295 switch ($formula) {
296 case '(n == 1 || n == 2 || n == 3) || n % 10 != 4 && n % 10 != 6 && n % 10 != 9':
297 return 'n != 1 && n != 2 && n != 3 && (n % 10 == 4 || n % 10 == 6 || n % 10 == 9)';
298 case '(n == 0 || n == 1) || n >= 11 && n <= 99':
299 return 'n >= 2 && (n < 11 || n > 99)';
300 }
301 throw new Exception("Unable to reverse the formula '$formula'");
302 }
308 private static function reduceFormula($formula)
309 {
310 $map = array(
311 'n != 0 && n != 1' => 'n > 1' ,
312 '(n == 0 || n == 1) && n != 0' => 'n == 1',
313 );
314
315 return isset($map[$formula]) ? $map[$formula] : $formula;
316 }
322 private static function asciifier(&$value)
323 {
324 if (is_string($value) && ($value !== '')) {
325 // Avoid converting from 'Ÿ' to '"Y', let's prefer 'Y'
326 $transliterated = strtr($value, array(
327 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A',
328 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E',
329 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I',
330 'Ñ' => 'N',
331 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O',
332 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U',
333 'Ÿ' => 'Y', 'Ý' => 'Y',
334 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a',
335 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e',
336 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i',
337 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o',
338 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u',
339 'ý' => 'y', 'ÿ' => 'y',
340 ));
341 $transliterated = @iconv('UTF-8', 'US-ASCII//IGNORE//TRANSLIT', $transliterated);
342 if (($transliterated === false) || ($transliterated === '')) {
343 throw new Exception("Unable to transliterate '$value'");
344 }
345 $value = $transliterated;
346 }
347 }
352 public function getUSAsciiClone()
353 {
354 $clone = clone $this;
355 self::asciifier($clone->name);
356 self::asciifier($clone->formula);
357 $clone->categories = array();
358 foreach ($this->categories as $category) {
359 $categoryClone = clone $category;
360 self::asciifier($categoryClone->examples);
361 $clone->categories[] = $categoryClone;
362 }
363
364 return $clone;
365 }
366}
$result
An exception for terminatinating execution or to throw for unit testing.
A helper class that handles a single category rules (eg 'zero', 'one', ...) and its formula and examp...
Definition: Category.php:10
static getLanguageNames()
Returns a dictionary containing the language names.
Definition: CldrData.php:140
static getLanguageInfo($id)
Retrieve the name of a language, as well as if a language code is deprecated in favor of another lang...
Definition: CldrData.php:199
Main class to convert the plural rules of a language from CLDR to gettext.
Definition: Language.php:10
__construct($info)
Initialize the instance and parse the language code.
Definition: Language.php:56
checkAllCategoriesWithExamples()
Let's look for categories that don't have examples.
Definition: Language.php:174
static getAll()
Return a list of all languages available.
Definition: Language.php:96
static reverseFormula($formula)
Reverse a formula.
Definition: Language.php:280
static reduceFormula($formula)
Reduce some excessively complex formulas.
Definition: Language.php:308
getUSAsciiClone()
Returns a clone of this instance with all the strings to US-ASCII.
Definition: Language.php:352
buildFormula()
Build the formula starting from the currently defined categories.
Definition: Language.php:249
static getById($id)
Return a Language instance given the language id.
Definition: Language.php:110
checkAlwaysTrueCategories()
Let's look for categories that will always occur.
Definition: Language.php:126
static asciifier(&$value)
Take one variable and, if it's a string, we transliterate it to US-ASCII.
Definition: Language.php:322
checkAlwaysFalseCategories()
Let's look for categories that will never occur.
Definition: Language.php:154
$i
Definition: disco.tpl.php:19
$info
Definition: index.php:5