These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * PrivateBin |
||
4 | * |
||
5 | * a zero-knowledge paste bin |
||
6 | * |
||
7 | * @link https://github.com/PrivateBin/PrivateBin |
||
8 | * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net) |
||
9 | * @license https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License |
||
10 | * @version 1.1.1 |
||
11 | */ |
||
12 | |||
13 | namespace PrivateBin; |
||
14 | |||
15 | /** |
||
16 | * I18n |
||
17 | * |
||
18 | * provides internationalization tools like translation, browser language detection, etc. |
||
19 | */ |
||
20 | class I18n |
||
21 | { |
||
22 | /** |
||
23 | * language |
||
24 | * |
||
25 | * @access protected |
||
26 | * @static |
||
27 | * @var string |
||
28 | */ |
||
29 | protected static $_language = 'en'; |
||
30 | |||
31 | /** |
||
32 | * language fallback |
||
33 | * |
||
34 | * @access protected |
||
35 | * @static |
||
36 | * @var string |
||
37 | */ |
||
38 | protected static $_languageFallback = 'en'; |
||
39 | |||
40 | /** |
||
41 | * language labels |
||
42 | * |
||
43 | * @access protected |
||
44 | * @static |
||
45 | * @var array |
||
46 | */ |
||
47 | protected static $_languageLabels = array(); |
||
48 | |||
49 | /** |
||
50 | * available languages |
||
51 | * |
||
52 | * @access protected |
||
53 | * @static |
||
54 | * @var array |
||
55 | */ |
||
56 | protected static $_availableLanguages = array(); |
||
57 | |||
58 | /** |
||
59 | * path to language files |
||
60 | * |
||
61 | * @access protected |
||
62 | * @static |
||
63 | * @var string |
||
64 | */ |
||
65 | protected static $_path = ''; |
||
66 | |||
67 | /** |
||
68 | * translation cache |
||
69 | * |
||
70 | * @access protected |
||
71 | * @static |
||
72 | * @var array |
||
73 | */ |
||
74 | protected static $_translations = array(); |
||
75 | |||
76 | /** |
||
77 | * translate a string, alias for translate() |
||
78 | * |
||
79 | * @access public |
||
80 | * @static |
||
81 | * @param string $messageId |
||
82 | * @param mixed $args one or multiple parameters injected into placeholders |
||
83 | * @return string |
||
84 | */ |
||
85 | 83 | public static function _($messageId) |
|
86 | { |
||
87 | 83 | return forward_static_call_array('self::translate', func_get_args()); |
|
88 | } |
||
89 | |||
90 | /** |
||
91 | * translate a string |
||
92 | * |
||
93 | * @access public |
||
94 | * @static |
||
95 | * @param string $messageId |
||
96 | * @param mixed $args one or multiple parameters injected into placeholders |
||
97 | * @return string |
||
98 | */ |
||
99 | 83 | public static function translate($messageId) |
|
100 | { |
||
101 | 83 | if (empty($messageId)) { |
|
102 | 36 | return $messageId; |
|
103 | } |
||
104 | 83 | if (count(self::$_translations) === 0) { |
|
105 | 65 | self::loadTranslations(); |
|
106 | } |
||
107 | 83 | $messages = $messageId; |
|
108 | 83 | if (is_array($messageId)) { |
|
109 | 37 | $messageId = count($messageId) > 1 ? $messageId[1] : $messageId[0]; |
|
110 | } |
||
111 | 83 | if (!array_key_exists($messageId, self::$_translations)) { |
|
112 | 65 | self::$_translations[$messageId] = $messages; |
|
113 | } |
||
114 | 83 | $args = func_get_args(); |
|
115 | 83 | if (is_array(self::$_translations[$messageId])) { |
|
116 | 46 | $number = (int) $args[1]; |
|
117 | 46 | $key = self::_getPluralForm($number); |
|
118 | 46 | $max = count(self::$_translations[$messageId]) - 1; |
|
119 | 46 | if ($key > $max) { |
|
120 | $key = $max; |
||
121 | } |
||
122 | |||
123 | 46 | $args[0] = self::$_translations[$messageId][$key]; |
|
124 | 46 | $args[1] = $number; |
|
125 | } else { |
||
126 | 82 | $args[0] = self::$_translations[$messageId]; |
|
127 | } |
||
128 | 83 | return call_user_func_array('sprintf', $args); |
|
129 | } |
||
130 | |||
131 | /** |
||
132 | * loads translations |
||
133 | * |
||
134 | * From: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447 |
||
135 | * |
||
136 | * @access public |
||
137 | * @static |
||
138 | */ |
||
139 | 75 | public static function loadTranslations() |
|
140 | { |
||
141 | 75 | $availableLanguages = self::getAvailableLanguages(); |
|
142 | |||
143 | // check if the lang cookie was set and that language exists |
||
144 | if ( |
||
145 | 75 | array_key_exists('lang', $_COOKIE) && |
|
146 | 75 | ($key = array_search($_COOKIE['lang'], $availableLanguages)) !== false |
|
147 | ) { |
||
148 | 7 | $match = $availableLanguages[$key]; |
|
149 | } |
||
150 | // find a translation file matching the browsers language preferences |
||
151 | else { |
||
152 | 68 | $match = self::_getMatchingLanguage( |
|
153 | 68 | self::getBrowserLanguages(), $availableLanguages |
|
154 | ); |
||
155 | } |
||
156 | |||
157 | // load translations |
||
158 | 75 | self::$_language = $match; |
|
159 | 75 | self::$_translations = ($match == 'en') ? array() : json_decode( |
|
160 | 16 | file_get_contents(self::_getPath($match . '.json')), |
|
161 | 75 | true |
|
162 | ); |
||
163 | 75 | } |
|
164 | |||
165 | /** |
||
166 | * get list of available translations based on files found |
||
167 | * |
||
168 | * @access public |
||
169 | * @static |
||
170 | * @return array |
||
171 | */ |
||
172 | 111 | public static function getAvailableLanguages() |
|
173 | { |
||
174 | 111 | if (count(self::$_availableLanguages) == 0) { |
|
175 | 97 | $i18n = dir(self::_getPath()); |
|
176 | 97 | while (false !== ($file = $i18n->read())) { |
|
177 | 97 | if (preg_match('/^([a-z]{2}).json$/', $file, $match) === 1) { |
|
178 | 97 | self::$_availableLanguages[] = $match[1]; |
|
179 | } |
||
180 | } |
||
181 | 97 | self::$_availableLanguages[] = 'en'; |
|
182 | } |
||
183 | 111 | return self::$_availableLanguages; |
|
184 | } |
||
185 | |||
186 | /** |
||
187 | * detect the clients supported languages and return them ordered by preference |
||
188 | * |
||
189 | * From: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447 |
||
190 | * |
||
191 | * @access public |
||
192 | * @static |
||
193 | * @return array |
||
194 | */ |
||
195 | 68 | public static function getBrowserLanguages() |
|
196 | { |
||
197 | 68 | $languages = array(); |
|
198 | 68 | if (array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER)) { |
|
199 | 11 | $languageRanges = explode(',', trim($_SERVER['HTTP_ACCEPT_LANGUAGE'])); |
|
200 | 11 | View Code Duplication | foreach ($languageRanges as $languageRange) { |
0 ignored issues
–
show
|
|||
201 | 11 | if (preg_match( |
|
202 | 11 | '/(\*|[a-zA-Z0-9]{1,8}(?:-[a-zA-Z0-9]{1,8})*)(?:\s*;\s*q\s*=\s*(0(?:\.\d{0,3})|1(?:\.0{0,3})))?/', |
|
203 | 11 | trim($languageRange), $match |
|
204 | )) { |
||
205 | 11 | if (!isset($match[2])) { |
|
206 | 5 | $match[2] = '1.0'; |
|
207 | } else { |
||
208 | 8 | $match[2] = (string) floatval($match[2]); |
|
209 | } |
||
210 | 11 | if (!isset($languages[$match[2]])) { |
|
211 | 11 | $languages[$match[2]] = array(); |
|
212 | } |
||
213 | 11 | $languages[$match[2]][] = strtolower($match[1]); |
|
214 | } |
||
215 | } |
||
216 | 11 | krsort($languages); |
|
217 | } |
||
218 | 68 | return $languages; |
|
219 | } |
||
220 | |||
221 | /** |
||
222 | * get currently loaded language |
||
223 | * |
||
224 | * @access public |
||
225 | * @static |
||
226 | * @return string |
||
227 | */ |
||
228 | 2 | public static function getLanguage() |
|
229 | { |
||
230 | 2 | return self::$_language; |
|
231 | } |
||
232 | |||
233 | /** |
||
234 | * get list of language labels |
||
235 | * |
||
236 | * Only for given language codes, otherwise all labels. |
||
237 | * |
||
238 | * @access public |
||
239 | * @static |
||
240 | * @param array $languages |
||
241 | * @return array |
||
242 | */ |
||
243 | 38 | public static function getLanguageLabels($languages = array()) |
|
244 | { |
||
245 | 38 | $file = self::_getPath('languages.json'); |
|
246 | 38 | if (count(self::$_languageLabels) == 0 && is_readable($file)) { |
|
247 | 37 | self::$_languageLabels = json_decode(file_get_contents($file), true); |
|
248 | } |
||
249 | 38 | if (count($languages) == 0) { |
|
250 | return self::$_languageLabels; |
||
251 | } |
||
252 | 38 | return array_intersect_key(self::$_languageLabels, array_flip($languages)); |
|
253 | } |
||
254 | |||
255 | /** |
||
256 | * set the default language |
||
257 | * |
||
258 | * @access public |
||
259 | * @static |
||
260 | * @param string $lang |
||
261 | */ |
||
262 | 96 | public static function setLanguageFallback($lang) |
|
263 | { |
||
264 | 96 | if (in_array($lang, self::getAvailableLanguages())) { |
|
265 | 2 | self::$_languageFallback = $lang; |
|
266 | } |
||
267 | 96 | } |
|
268 | |||
269 | /** |
||
270 | * get language file path |
||
271 | * |
||
272 | * @access protected |
||
273 | * @static |
||
274 | * @param string $file |
||
275 | * @return string |
||
276 | */ |
||
277 | 109 | protected static function _getPath($file = '') |
|
278 | { |
||
279 | 109 | if (strlen(self::$_path) == 0) { |
|
280 | 97 | self::$_path = PUBLIC_PATH . DIRECTORY_SEPARATOR . 'i18n'; |
|
281 | } |
||
282 | 109 | return self::$_path . (strlen($file) ? DIRECTORY_SEPARATOR . $file : ''); |
|
283 | } |
||
284 | |||
285 | /** |
||
286 | * determines the plural form to use based on current language and given number |
||
287 | * |
||
288 | * From: http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html |
||
289 | * |
||
290 | * @access protected |
||
291 | * @static |
||
292 | * @param int $n |
||
293 | * @return int |
||
294 | */ |
||
295 | 46 | protected static function _getPluralForm($n) |
|
296 | { |
||
297 | 46 | switch (self::$_language) { |
|
298 | 46 | case 'fr': |
|
299 | 43 | case 'oc': |
|
300 | 42 | case 'zh': |
|
301 | 5 | return $n > 1 ? 1 : 0; |
|
302 | 41 | case 'pl': |
|
303 | 1 | return $n == 1 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); |
|
304 | 40 | case 'ru': |
|
305 | 1 | return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); |
|
306 | 39 | case 'sl': |
|
307 | 1 | return $n % 100 == 1 ? 1 : ($n % 100 == 2 ? 2 : ($n % 100 == 3 || $n % 100 == 4 ? 3 : 0)); |
|
308 | // de, en, es, it, no, pt |
||
309 | default: |
||
310 | 38 | return $n != 1 ? 1 : 0; |
|
311 | } |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * compares two language preference arrays and returns the preferred match |
||
316 | * |
||
317 | * From: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447 |
||
318 | * |
||
319 | * @access protected |
||
320 | * @static |
||
321 | * @param array $acceptedLanguages |
||
322 | * @param array $availableLanguages |
||
323 | * @return string |
||
324 | */ |
||
325 | 68 | protected static function _getMatchingLanguage($acceptedLanguages, $availableLanguages) |
|
326 | { |
||
327 | 68 | $matches = array(); |
|
328 | 68 | $any = false; |
|
329 | 68 | foreach ($acceptedLanguages as $acceptedQuality => $acceptedValues) { |
|
330 | 11 | $acceptedQuality = floatval($acceptedQuality); |
|
331 | 11 | if ($acceptedQuality === 0.0) { |
|
332 | continue; |
||
333 | } |
||
334 | 11 | foreach ($availableLanguages as $availableValue) { |
|
335 | 11 | $availableQuality = 1.0; |
|
336 | 11 | foreach ($acceptedValues as $acceptedValue) { |
|
337 | 11 | if ($acceptedValue === '*') { |
|
338 | 1 | $any = true; |
|
339 | } |
||
340 | 11 | $matchingGrade = self::_matchLanguage($acceptedValue, $availableValue); |
|
341 | 11 | if ($matchingGrade > 0) { |
|
342 | 8 | $q = (string) ($acceptedQuality * $availableQuality * $matchingGrade); |
|
343 | 8 | if (!isset($matches[$q])) { |
|
344 | 8 | $matches[$q] = array(); |
|
345 | } |
||
346 | 8 | if (!in_array($availableValue, $matches[$q])) { |
|
347 | 11 | $matches[$q][] = $availableValue; |
|
348 | } |
||
349 | } |
||
350 | } |
||
351 | } |
||
352 | } |
||
353 | 68 | if (count($matches) === 0 && $any) { |
|
354 | 1 | if (count($availableLanguages) > 0) { |
|
355 | 1 | $matches['1.0'] = $availableLanguages; |
|
356 | } |
||
357 | } |
||
358 | 68 | if (count($matches) === 0) { |
|
359 | 59 | return self::$_languageFallback; |
|
360 | } |
||
361 | 9 | krsort($matches); |
|
362 | 9 | $topmatches = current($matches); |
|
363 | 9 | return current($topmatches); |
|
364 | } |
||
365 | |||
366 | /** |
||
367 | * compare two language IDs and return the degree they match |
||
368 | * |
||
369 | * From: https://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447 |
||
370 | * |
||
371 | * @access protected |
||
372 | * @static |
||
373 | * @param string $a |
||
374 | * @param string $b |
||
375 | * @return float |
||
376 | */ |
||
377 | 11 | protected static function _matchLanguage($a, $b) |
|
378 | { |
||
379 | 11 | $a = explode('-', $a); |
|
380 | 11 | $b = explode('-', $b); |
|
381 | 11 | for ($i = 0, $n = min(count($a), count($b)); $i < $n; ++$i) { |
|
382 | 11 | if ($a[$i] !== $b[$i]) { |
|
383 | 11 | break; |
|
384 | } |
||
385 | } |
||
386 | 11 | return $i === 0 ? 0 : (float) $i / count($a); |
|
387 | } |
||
388 | } |
||
389 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.