1 | <?php |
||||
2 | |||||
3 | namespace Elgg\I18n; |
||||
4 | |||||
5 | use Elgg\Config; |
||||
6 | |||||
7 | /** |
||||
8 | * WARNING: API IN FLUX. DO NOT USE DIRECTLY. |
||||
9 | * |
||||
10 | * @access private |
||||
11 | * |
||||
12 | * @since 1.10.0 |
||||
13 | */ |
||||
14 | class Translator { |
||||
15 | |||||
16 | /** |
||||
17 | * @var Config |
||||
18 | */ |
||||
19 | private $config; |
||||
20 | |||||
21 | /** |
||||
22 | * @var array |
||||
23 | */ |
||||
24 | private $translations = []; |
||||
25 | |||||
26 | /** |
||||
27 | * @var bool |
||||
28 | */ |
||||
29 | private $is_initialized = false; |
||||
30 | |||||
31 | /** |
||||
32 | * @var string |
||||
33 | */ |
||||
34 | private $defaultPath = null; |
||||
35 | |||||
36 | /** |
||||
37 | * @var string |
||||
38 | */ |
||||
39 | private $current_language = null; |
||||
40 | |||||
41 | /** |
||||
42 | * Paths to scan for autoloading languages. |
||||
43 | * |
||||
44 | * Languages are automatically loaded for the site or |
||||
45 | * user's default language. Plugins can extend or override strings. |
||||
46 | * language_paths is an array of paths to scan for PHP files matching |
||||
47 | * the default language. The order of paths is determined by the plugin load order, |
||||
48 | * with later entries overriding earlier. Language files within these paths are |
||||
49 | * named as the two-letter ISO 639-1 country codes for the language they represent. |
||||
50 | * |
||||
51 | * @link http://en.wikipedia.org/wiki/ISO_639-1 |
||||
52 | * |
||||
53 | * @var array (paths are keys) |
||||
54 | */ |
||||
55 | private $language_paths = []; |
||||
56 | |||||
57 | /** |
||||
58 | * @var bool |
||||
59 | */ |
||||
60 | private $was_reloaded = false; |
||||
61 | |||||
62 | /** |
||||
63 | * @var bool |
||||
64 | */ |
||||
65 | private $loaded_from_cache = false; |
||||
66 | |||||
67 | /** |
||||
68 | * Constructor |
||||
69 | * |
||||
70 | * @param Config $config Elgg config |
||||
71 | */ |
||||
72 | 4417 | public function __construct(Config $config) { |
|||
73 | 4417 | $this->config = $config; |
|||
74 | 4417 | $this->defaultPath = dirname(dirname(dirname(dirname(__DIR__)))) . "/languages/"; |
|||
75 | 4417 | } |
|||
76 | |||||
77 | /** |
||||
78 | * @return bool |
||||
79 | */ |
||||
80 | 19 | public function wasLoadedFromCache() { |
|||
81 | 19 | return $this->loaded_from_cache; |
|||
82 | } |
||||
83 | |||||
84 | /** |
||||
85 | * Get a map of all loaded translations |
||||
86 | * |
||||
87 | * @return array |
||||
88 | */ |
||||
89 | 978 | public function getLoadedTranslations() { |
|||
90 | 978 | return $this->translations; |
|||
91 | } |
||||
92 | |||||
93 | /** |
||||
94 | * Given a message key, returns an appropriately translated full-text string |
||||
95 | * |
||||
96 | * @param string $message_key The short message code |
||||
97 | * @param array $args An array of arguments to pass through vsprintf(). |
||||
98 | * @param string $language Optionally, the standard language code |
||||
99 | * (defaults to site/user default, then English) |
||||
100 | * |
||||
101 | * @return string Either the translated string, the English string, |
||||
102 | * or the original language string. |
||||
103 | */ |
||||
104 | 867 | public function translate($message_key, array $args = [], $language = "") { |
|||
105 | 867 | if (!is_string($message_key) || strlen($message_key) < 1) { |
|||
106 | _elgg_services()->logger->warn( |
||||
107 | '$message_key needs to be a string in ' . __METHOD__ . '(), ' . gettype($message_key) . ' provided' |
||||
108 | ); |
||||
109 | return ''; |
||||
110 | } |
||||
111 | |||||
112 | 867 | if (!$language) { |
|||
113 | // no language provided, get current language |
||||
114 | // based on detection, user setting or site |
||||
115 | 853 | $language = $this->getCurrentLanguage(); |
|||
116 | } |
||||
117 | |||||
118 | 867 | $this->ensureTranslationsLoaded($language); |
|||
119 | |||||
120 | // build language array for different trys |
||||
121 | // avoid dupes without overhead of array_unique |
||||
122 | 867 | $langs[$language] = true; |
|||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
Loading history...
|
|||||
123 | |||||
124 | // load site language |
||||
125 | 867 | $site_language = $this->config->language; |
|||
126 | 867 | if (!empty($site_language)) { |
|||
127 | 867 | $this->ensureTranslationsLoaded($site_language); |
|||
128 | |||||
129 | 867 | $langs[$site_language] = true; |
|||
130 | } |
||||
131 | |||||
132 | // ultimate language fallback |
||||
133 | 867 | $langs['en'] = true; |
|||
134 | |||||
135 | // try to translate |
||||
136 | 867 | $notice = ''; |
|||
137 | 867 | $string = $message_key; |
|||
138 | 867 | foreach (array_keys($langs) as $try_lang) { |
|||
139 | 867 | if (isset($this->translations[$try_lang][$message_key])) { |
|||
140 | 687 | $string = $this->translations[$try_lang][$message_key]; |
|||
141 | |||||
142 | // only pass through if we have arguments to allow backward compatibility |
||||
143 | // with manual sprintf() calls. |
||||
144 | 687 | if ($args) { |
|||
0 ignored issues
–
show
The expression
$args of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||||
145 | 59 | $string = vsprintf($string, $args); |
|||
146 | } |
||||
147 | |||||
148 | 687 | break; |
|||
149 | } else { |
||||
150 | 450 | $notice = sprintf( |
|||
151 | 450 | 'Missing %s translation for "%s" language key', |
|||
152 | 450 | ($try_lang === 'en') ? 'English' : $try_lang, |
|||
153 | 450 | $message_key |
|||
154 | ); |
||||
155 | } |
||||
156 | } |
||||
157 | |||||
158 | 867 | if ($notice) { |
|||
159 | 450 | _elgg_services()->logger->notice($notice); |
|||
160 | } |
||||
161 | |||||
162 | 867 | return $string; |
|||
163 | } |
||||
164 | |||||
165 | /** |
||||
166 | * Add a translation. |
||||
167 | * |
||||
168 | * Translations are arrays in the Zend Translation array format, eg: |
||||
169 | * |
||||
170 | * $english = array('message1' => 'message1', 'message2' => 'message2'); |
||||
171 | * $german = array('message1' => 'Nachricht1','message2' => 'Nachricht2'); |
||||
172 | * |
||||
173 | * @param string $country_code Standard country code (eg 'en', 'nl', 'es') |
||||
174 | * @param array $language_array Formatted array of strings |
||||
175 | * |
||||
176 | * @return bool Depending on success |
||||
177 | */ |
||||
178 | 4789 | public function addTranslation($country_code, $language_array) { |
|||
179 | 4789 | $country_code = strtolower($country_code); |
|||
180 | 4789 | $country_code = trim($country_code); |
|||
181 | |||||
182 | 4789 | if (!is_array($language_array) || $country_code === "") { |
|||
183 | return false; |
||||
184 | } |
||||
185 | |||||
186 | 4789 | if (count($language_array) > 0) { |
|||
187 | 4789 | if (!isset($this->translations[$country_code])) { |
|||
188 | 4418 | $this->translations[$country_code] = $language_array; |
|||
189 | } else { |
||||
190 | 1398 | $this->translations[$country_code] = $language_array + $this->translations[$country_code]; |
|||
191 | } |
||||
192 | } |
||||
193 | |||||
194 | 4789 | return true; |
|||
195 | } |
||||
196 | |||||
197 | /** |
||||
198 | * Get the current system/user language or "en". |
||||
199 | * |
||||
200 | * @return string The language code for the site/user or "en" if not set |
||||
201 | */ |
||||
202 | 5084 | public function getCurrentLanguage() { |
|||
203 | 5084 | if (!isset($this->current_language)) { |
|||
204 | 4459 | $this->current_language = $this->detectLanguage(); |
|||
1 ignored issue
–
show
It seems like
$this->detectLanguage() can also be of type false . However, the property $current_language is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
Loading history...
|
|||||
205 | } |
||||
206 | |||||
207 | 5084 | if (!$this->current_language) { |
|||
0 ignored issues
–
show
The expression
$this->current_language of type false|string is loosely compared to false ; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
Loading history...
|
|||||
208 | $this->current_language = 'en'; |
||||
209 | } |
||||
210 | |||||
211 | 5084 | return $this->current_language; |
|||
212 | } |
||||
213 | |||||
214 | /** |
||||
215 | * Sets current system language |
||||
216 | * |
||||
217 | * @param string $language Language code |
||||
218 | * |
||||
219 | * @return void |
||||
220 | */ |
||||
221 | 1398 | public function setCurrentLanguage($language = null) { |
|||
222 | 1398 | $this->current_language = $language; |
|||
223 | 1398 | } |
|||
224 | |||||
225 | /** |
||||
226 | * Detect the current system/user language or false. |
||||
227 | * |
||||
228 | * @return false|string The language code (eg "en") or false if not set |
||||
229 | */ |
||||
230 | 4459 | public function detectLanguage() { |
|||
231 | // detect from URL |
||||
232 | 4459 | $url_lang = _elgg_services()->input->get('hl'); |
|||
233 | 4459 | if (!empty($url_lang)) { |
|||
234 | 2 | return $url_lang; |
|||
235 | } |
||||
236 | |||||
237 | // check logged in user |
||||
238 | 4459 | $user = _elgg_services()->session->getLoggedInUser(); |
|||
239 | 4459 | if (!empty($user) && !empty($user->language)) { |
|||
240 | 1 | return $user->language; |
|||
241 | } |
||||
242 | |||||
243 | // get site setting |
||||
244 | 4459 | $site_language = $this->config->language; |
|||
245 | 4459 | if (!empty($site_language)) { |
|||
246 | 4459 | return $site_language; |
|||
247 | } |
||||
248 | |||||
249 | return false; |
||||
250 | } |
||||
251 | |||||
252 | /** |
||||
253 | * Load both core and plugin translations |
||||
254 | * |
||||
255 | * By default this loads only English and the language of the logged |
||||
256 | * in user. |
||||
257 | * |
||||
258 | * The optional $language argument can be used to load translations |
||||
259 | * on-demand in case we need to translate something to a language not |
||||
260 | * loaded by default for the current request. |
||||
261 | * |
||||
262 | * @param string $language Language code |
||||
263 | * |
||||
264 | * @return void |
||||
265 | * |
||||
266 | * @access private |
||||
267 | */ |
||||
268 | 4778 | public function loadTranslations($language = null) { |
|||
269 | 4778 | if (elgg_is_system_cache_enabled()) { |
|||
270 | 14 | $loaded = true; |
|||
271 | |||||
272 | 14 | if ($language) { |
|||
0 ignored issues
–
show
The expression
$language of type null|string is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
Loading history...
|
|||||
273 | 1 | $languages = [$language]; |
|||
274 | } else { |
||||
275 | 13 | $languages = array_unique(['en', $this->getCurrentLanguage()]); |
|||
276 | } |
||||
277 | |||||
278 | 14 | foreach ($languages as $language) { |
|||
279 | 14 | $data = elgg_load_system_cache("$language.lang"); |
|||
280 | 14 | if ($data) { |
|||
281 | 13 | $this->addTranslation($language, unserialize($data)); |
|||
282 | } else { |
||||
283 | 14 | $loaded = false; |
|||
284 | } |
||||
285 | } |
||||
286 | |||||
287 | 14 | if ($loaded) { |
|||
288 | 13 | $this->loaded_from_cache = true; |
|||
289 | 13 | $this->language_paths[$this->defaultPath] = true; |
|||
290 | 13 | $this->is_initialized = true; |
|||
291 | 13 | return; |
|||
292 | } |
||||
293 | } |
||||
294 | |||||
295 | // load core translations from languages directory |
||||
296 | 4766 | $this->registerTranslations($this->defaultPath, false, $language); |
|||
297 | |||||
298 | // Plugin translation have already been loaded for the default |
||||
299 | // languages by ElggApplication::bootCore(), so there's no need |
||||
300 | // to continue unless loading a specific language on-demand |
||||
301 | 4766 | if ($language) { |
|||
302 | 989 | $this->loadPluginTranslations($language); |
|||
303 | } |
||||
304 | 4766 | } |
|||
305 | |||||
306 | /** |
||||
307 | * Load plugin translations for a language |
||||
308 | * |
||||
309 | * This is needed only if the current request uses a language |
||||
310 | * that is neither English of the same as the language of the |
||||
311 | * logged in user. |
||||
312 | * |
||||
313 | * @param string $language Language code |
||||
314 | * @return void |
||||
315 | * @throws \PluginException |
||||
316 | */ |
||||
317 | 989 | private function loadPluginTranslations($language) { |
|||
318 | // Get active plugins |
||||
319 | 989 | $plugins = _elgg_services()->plugins->find('active'); |
|||
320 | |||||
321 | 989 | if (!$plugins) { |
|||
0 ignored issues
–
show
The expression
$plugins of type ElggPlugin[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||||
322 | // Active plugins were not found, so no need to register plugin translations |
||||
323 | 987 | return; |
|||
324 | } |
||||
325 | |||||
326 | 2 | foreach ($plugins as $plugin) { |
|||
327 | 2 | $languages_path = "{$plugin->getPath()}languages/"; |
|||
328 | |||||
329 | 2 | if (!is_dir($languages_path)) { |
|||
330 | // This plugin doesn't have anything to translate |
||||
331 | 2 | continue; |
|||
332 | } |
||||
333 | |||||
334 | 2 | $language_file = "{$languages_path}{$language}.php"; |
|||
335 | |||||
336 | 2 | if (!file_exists($language_file)) { |
|||
337 | // This plugin doesn't have translations for the requested language |
||||
338 | |||||
339 | $name = $plugin->getDisplayName(); |
||||
340 | _elgg_services()->logger->notice("Plugin $name is missing translations for $language language"); |
||||
341 | |||||
342 | continue; |
||||
343 | } |
||||
344 | |||||
345 | // Register translations from the plugin languages directory |
||||
346 | 2 | if (!$this->registerTranslations($languages_path, false, $language)) { |
|||
347 | throw new \PluginException(sprintf('Cannot register languages for plugin %s (guid: %s) at %s.', |
||||
348 | 2 | [$plugin->getID(), $plugin->guid, $languages_path])); |
|||
349 | } |
||||
350 | } |
||||
351 | 2 | } |
|||
352 | |||||
353 | /** |
||||
354 | * Registers translations in a directory assuming the standard plugin layout. |
||||
355 | * |
||||
356 | * @param string $path Without the trailing slash. |
||||
357 | * |
||||
358 | * @return bool Success |
||||
359 | */ |
||||
360 | 24 | public function registerPluginTranslations($path) { |
|||
361 | 24 | $languages_path = rtrim($path, "\\/") . "/languages"; |
|||
362 | |||||
363 | // don't need to have translations |
||||
364 | 24 | if (!is_dir($languages_path)) { |
|||
365 | 2 | return true; |
|||
366 | } |
||||
367 | |||||
368 | 24 | return $this->registerTranslations($languages_path); |
|||
369 | } |
||||
370 | |||||
371 | /** |
||||
372 | * When given a full path, finds translation files and loads them |
||||
373 | * |
||||
374 | * @param string $path Full path |
||||
375 | * @param bool $load_all If true all languages are loaded, if |
||||
376 | * false only the current language + en are loaded |
||||
377 | * @param string $language Language code |
||||
378 | * |
||||
379 | * @return bool success |
||||
380 | */ |
||||
381 | 4781 | public function registerTranslations($path, $load_all = false, $language = null) { |
|||
382 | 4781 | $path = \Elgg\Project\Paths::sanitize($path); |
|||
383 | |||||
384 | // Make a note of this path just in case we need to register this language later |
||||
385 | 4781 | $this->language_paths[$path] = true; |
|||
386 | 4781 | $this->is_initialized = true; |
|||
387 | |||||
388 | 4781 | _elgg_services()->logger->info("Translations loaded from: $path"); |
|||
389 | |||||
390 | 4781 | if ($language) { |
|||
0 ignored issues
–
show
The expression
$language of type null|string is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
Loading history...
|
|||||
391 | 989 | $load_language_files = ["$language.php"]; |
|||
392 | 989 | $load_all = false; |
|||
393 | } else { |
||||
394 | // Get the current language based on site defaults and user preference |
||||
395 | 4781 | $current_language = $this->getCurrentLanguage(); |
|||
396 | |||||
397 | $load_language_files = [ |
||||
398 | 4781 | 'en.php', |
|||
399 | 4781 | "$current_language.php" |
|||
400 | ]; |
||||
401 | |||||
402 | 4781 | $load_language_files = array_unique($load_language_files); |
|||
403 | } |
||||
404 | |||||
405 | 4781 | $handle = opendir($path); |
|||
406 | 4781 | if (!$handle) { |
|||
407 | _elgg_services()->logger->error("Could not open language path: $path"); |
||||
408 | return false; |
||||
409 | } |
||||
410 | |||||
411 | 4781 | $return = true; |
|||
412 | 4781 | while (false !== ($language_file = readdir($handle))) { |
|||
413 | // ignore bad files |
||||
414 | 4781 | if (substr($language_file, 0, 1) == '.' || substr($language_file, -4) !== '.php') { |
|||
415 | 4781 | continue; |
|||
416 | } |
||||
417 | |||||
418 | 4781 | if (in_array($language_file, $load_language_files) || $load_all) { |
|||
419 | 4781 | $result = (include $path . $language_file); |
|||
420 | 4781 | if ($result === false) { |
|||
421 | $return = false; |
||||
422 | continue; |
||||
423 | 4781 | } elseif (is_array($result)) { |
|||
424 | 4781 | $this->addTranslation(basename($language_file, '.php'), $result); |
|||
425 | } |
||||
426 | } |
||||
427 | } |
||||
428 | |||||
429 | 4781 | return $return; |
|||
430 | } |
||||
431 | |||||
432 | /** |
||||
433 | * Reload all translations from all registered paths. |
||||
434 | * |
||||
435 | * This is only called by functions which need to know all possible translations. |
||||
436 | * |
||||
437 | * @todo Better on demand loading based on language_paths array |
||||
438 | * |
||||
439 | * @return void |
||||
440 | */ |
||||
441 | 945 | public function reloadAllTranslations() { |
|||
442 | 945 | if ($this->was_reloaded) { |
|||
443 | 455 | return; |
|||
444 | } |
||||
445 | |||||
446 | 945 | if ($this->loaded_from_cache) { |
|||
447 | $cache = elgg_get_system_cache(); |
||||
448 | $cache_dir = $cache->getVariable("cache_path"); |
||||
449 | $filenames = elgg_get_file_list($cache_dir, [], [], [".lang"]); |
||||
450 | foreach ($filenames as $filename) { |
||||
451 | // Look for files matching for example 'en.lang', 'cmn.lang' or 'pt_br.lang'. |
||||
452 | // Note that this regex is just for the system cache. The original language |
||||
453 | // files are allowed to have uppercase letters (e.g. pt_BR.php). |
||||
454 | if (preg_match('/(([a-z]{2,3})(_[a-z]{2})?)\.lang$/', $filename, $matches)) { |
||||
455 | $language = $matches[1]; |
||||
456 | $data = elgg_load_system_cache("$language.lang"); |
||||
457 | if ($data) { |
||||
458 | $this->addTranslation($language, unserialize($data)); |
||||
459 | } |
||||
460 | } |
||||
461 | } |
||||
462 | } else { |
||||
463 | 945 | foreach (array_keys($this->language_paths) as $path) { |
|||
464 | 945 | $this->registerTranslations($path, true); |
|||
465 | } |
||||
466 | } |
||||
467 | |||||
468 | 945 | _elgg_services()->hooks->getEvents()->triggerAfter('reload', 'translations'); |
|||
469 | |||||
470 | 945 | $this->was_reloaded = true; |
|||
471 | 945 | } |
|||
472 | |||||
473 | /** |
||||
474 | * Return an array of installed translations as an associative |
||||
475 | * array "two letter code" => "native language name". |
||||
476 | * |
||||
477 | * @return array |
||||
478 | */ |
||||
479 | 487 | public function getInstalledTranslations() { |
|||
480 | // Ensure that all possible translations are loaded |
||||
481 | 487 | $this->reloadAllTranslations(); |
|||
482 | |||||
483 | 487 | $installed = []; |
|||
484 | |||||
485 | 487 | $admin_logged_in = _elgg_services()->session->isAdminLoggedIn(); |
|||
486 | |||||
487 | 487 | foreach ($this->translations as $k => $v) { |
|||
488 | 487 | if ($this->languageKeyExists($k, $k)) { |
|||
489 | 487 | $lang = $this->translate($k, [], $k); |
|||
490 | } else { |
||||
491 | 476 | $lang = $this->translate($k); |
|||
492 | } |
||||
493 | |||||
494 | 487 | $installed[$k] = $lang; |
|||
495 | |||||
496 | 487 | if (!$admin_logged_in || ($k === 'en')) { |
|||
497 | 487 | continue; |
|||
498 | } |
||||
499 | |||||
500 | $completeness = $this->getLanguageCompleteness($k); |
||||
501 | if ($completeness < 100) { |
||||
502 | $installed[$k] .= " (" . $completeness . "% " . $this->translate('complete') . ")"; |
||||
503 | } |
||||
504 | } |
||||
505 | |||||
506 | 487 | return $installed; |
|||
507 | } |
||||
508 | |||||
509 | /** |
||||
510 | * Return the level of completeness for a given language code (compared to english) |
||||
511 | * |
||||
512 | * @param string $language Language |
||||
513 | * |
||||
514 | * @return float |
||||
515 | */ |
||||
516 | 487 | public function getLanguageCompleteness($language) { |
|||
517 | |||||
518 | 487 | if ($language == 'en') { |
|||
519 | 32 | return (float) 100; |
|||
520 | } |
||||
521 | |||||
522 | // Ensure that all possible translations are loaded |
||||
523 | 455 | $this->reloadAllTranslations(); |
|||
524 | |||||
525 | 455 | $language = sanitise_string($language); |
|||
0 ignored issues
–
show
The function
sanitise_string() has been deprecated: Use query parameters where possible
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.
Loading history...
|
|||||
526 | |||||
527 | 455 | $en = count($this->translations['en']); |
|||
528 | |||||
529 | 455 | $missing = $this->getMissingLanguageKeys($language); |
|||
530 | 455 | if ($missing) { |
|||
531 | 455 | $missing = count($missing); |
|||
532 | } else { |
||||
533 | $missing = 0; |
||||
534 | } |
||||
535 | |||||
536 | 455 | $lang = $en - $missing; |
|||
537 | |||||
538 | 455 | return round(($lang / $en) * 100, 2); |
|||
539 | } |
||||
540 | |||||
541 | /** |
||||
542 | * Return the translation keys missing from a given language, |
||||
543 | * or those that are identical to the english version. |
||||
544 | * |
||||
545 | * @param string $language The language |
||||
546 | * |
||||
547 | * @return mixed |
||||
548 | */ |
||||
549 | 455 | public function getMissingLanguageKeys($language) { |
|||
550 | |||||
551 | |||||
552 | // Ensure that all possible translations are loaded |
||||
553 | 455 | $this->reloadAllTranslations(); |
|||
554 | |||||
555 | 455 | $missing = []; |
|||
556 | |||||
557 | 455 | foreach ($this->translations['en'] as $k => $v) { |
|||
558 | 455 | if ((!isset($this->translations[$language][$k])) |
|||
559 | 455 | || ($this->translations[$language][$k] == $this->translations['en'][$k])) { |
|||
560 | 455 | $missing[] = $k; |
|||
561 | } |
||||
562 | } |
||||
563 | |||||
564 | 455 | if (count($missing)) { |
|||
565 | 455 | return $missing; |
|||
566 | } |
||||
567 | |||||
568 | return false; |
||||
569 | } |
||||
570 | |||||
571 | /** |
||||
572 | * Check if a given language key exists |
||||
573 | * |
||||
574 | * @param string $key The translation key |
||||
575 | * @param string $language The specific language to check |
||||
576 | * |
||||
577 | * @return bool |
||||
578 | * @since 1.11 |
||||
579 | */ |
||||
580 | 538 | function languageKeyExists($key, $language = 'en') { |
|||
581 | 538 | if (empty($key)) { |
|||
582 | return false; |
||||
583 | } |
||||
584 | |||||
585 | 538 | $this->ensureTranslationsLoaded($language); |
|||
586 | |||||
587 | 538 | if (!array_key_exists($language, $this->translations)) { |
|||
588 | 6 | return false; |
|||
589 | } |
||||
590 | |||||
591 | 532 | return array_key_exists($key, $this->translations[$language]); |
|||
592 | } |
||||
593 | |||||
594 | /** |
||||
595 | * Make sure translations are loaded |
||||
596 | * |
||||
597 | * @param string $language Language |
||||
598 | * @return void |
||||
599 | */ |
||||
600 | 870 | private function ensureTranslationsLoaded($language) { |
|||
601 | 870 | if (!$this->is_initialized) { |
|||
602 | // this means we probably had an exception before translations were initialized |
||||
603 | 11 | $this->registerTranslations($this->defaultPath); |
|||
604 | } |
||||
605 | |||||
606 | 870 | if (!isset($this->translations[$language])) { |
|||
607 | // The language being requested is not the same as the language of the |
||||
608 | // logged in user, so we will have to load it separately. (Most likely |
||||
609 | // we're sending a notification and the recipient is using a different |
||||
610 | // language than the logged in user.) |
||||
611 | 8 | $this->loadTranslations($language); |
|||
612 | } |
||||
613 | 870 | } |
|||
614 | |||||
615 | /** |
||||
616 | * Returns an array of language codes. |
||||
617 | * |
||||
618 | * @return array |
||||
619 | */ |
||||
620 | public static function getAllLanguageCodes() { |
||||
621 | return [ |
||||
622 | "aa", // "Afar" |
||||
623 | "ab", // "Abkhazian" |
||||
624 | "af", // "Afrikaans" |
||||
625 | "am", // "Amharic" |
||||
626 | "ar", // "Arabic" |
||||
627 | "as", // "Assamese" |
||||
628 | "ay", // "Aymara" |
||||
629 | "az", // "Azerbaijani" |
||||
630 | "ba", // "Bashkir" |
||||
631 | "be", // "Byelorussian" |
||||
632 | "bg", // "Bulgarian" |
||||
633 | "bh", // "Bihari" |
||||
634 | "bi", // "Bislama" |
||||
635 | "bn", // "Bengali; Bangla" |
||||
636 | "bo", // "Tibetan" |
||||
637 | "br", // "Breton" |
||||
638 | "ca", // "Catalan" |
||||
639 | "cmn", // "Mandarin Chinese" // ISO 639-3 |
||||
640 | "co", // "Corsican" |
||||
641 | "cs", // "Czech" |
||||
642 | "cy", // "Welsh" |
||||
643 | "da", // "Danish" |
||||
644 | "de", // "German" |
||||
645 | "dz", // "Bhutani" |
||||
646 | "el", // "Greek" |
||||
647 | "en", // "English" |
||||
648 | "eo", // "Esperanto" |
||||
649 | "es", // "Spanish" |
||||
650 | "et", // "Estonian" |
||||
651 | "eu", // "Basque" |
||||
652 | "eu_es", // "Basque (Spain)" |
||||
653 | "fa", // "Persian" |
||||
654 | "fi", // "Finnish" |
||||
655 | "fj", // "Fiji" |
||||
656 | "fo", // "Faeroese" |
||||
657 | "fr", // "French" |
||||
658 | "fy", // "Frisian" |
||||
659 | "ga", // "Irish" |
||||
660 | "gd", // "Scots / Gaelic" |
||||
661 | "gl", // "Galician" |
||||
662 | "gn", // "Guarani" |
||||
663 | "gu", // "Gujarati" |
||||
664 | "he", // "Hebrew" |
||||
665 | "ha", // "Hausa" |
||||
666 | "hi", // "Hindi" |
||||
667 | "hr", // "Croatian" |
||||
668 | "hu", // "Hungarian" |
||||
669 | "hy", // "Armenian" |
||||
670 | "ia", // "Interlingua" |
||||
671 | "id", // "Indonesian" |
||||
672 | "ie", // "Interlingue" |
||||
673 | "ik", // "Inupiak" |
||||
674 | "is", // "Icelandic" |
||||
675 | "it", // "Italian" |
||||
676 | "iu", // "Inuktitut" |
||||
677 | "iw", // "Hebrew (obsolete)" |
||||
678 | "ja", // "Japanese" |
||||
679 | "ji", // "Yiddish (obsolete)" |
||||
680 | "jw", // "Javanese" |
||||
681 | "ka", // "Georgian" |
||||
682 | "kk", // "Kazakh" |
||||
683 | "kl", // "Greenlandic" |
||||
684 | "km", // "Cambodian" |
||||
685 | "kn", // "Kannada" |
||||
686 | "ko", // "Korean" |
||||
687 | "ks", // "Kashmiri" |
||||
688 | "ku", // "Kurdish" |
||||
689 | "ky", // "Kirghiz" |
||||
690 | "la", // "Latin" |
||||
691 | "ln", // "Lingala" |
||||
692 | "lo", // "Laothian" |
||||
693 | "lt", // "Lithuanian" |
||||
694 | "lv", // "Latvian/Lettish" |
||||
695 | "mg", // "Malagasy" |
||||
696 | "mi", // "Maori" |
||||
697 | "mk", // "Macedonian" |
||||
698 | "ml", // "Malayalam" |
||||
699 | "mn", // "Mongolian" |
||||
700 | "mo", // "Moldavian" |
||||
701 | "mr", // "Marathi" |
||||
702 | "ms", // "Malay" |
||||
703 | "mt", // "Maltese" |
||||
704 | "my", // "Burmese" |
||||
705 | "na", // "Nauru" |
||||
706 | "ne", // "Nepali" |
||||
707 | "nl", // "Dutch" |
||||
708 | "no", // "Norwegian" |
||||
709 | "oc", // "Occitan" |
||||
710 | "om", // "(Afan) Oromo" |
||||
711 | "or", // "Oriya" |
||||
712 | "pa", // "Punjabi" |
||||
713 | "pl", // "Polish" |
||||
714 | "ps", // "Pashto / Pushto" |
||||
715 | "pt", // "Portuguese" |
||||
716 | "pt_br", // "Portuguese (Brazil)" |
||||
717 | "qu", // "Quechua" |
||||
718 | "rm", // "Rhaeto-Romance" |
||||
719 | "rn", // "Kirundi" |
||||
720 | "ro", // "Romanian" |
||||
721 | "ro_ro", // "Romanian (Romania)" |
||||
722 | "ru", // "Russian" |
||||
723 | "rw", // "Kinyarwanda" |
||||
724 | "sa", // "Sanskrit" |
||||
725 | "sd", // "Sindhi" |
||||
726 | "sg", // "Sangro" |
||||
727 | "sh", // "Serbo-Croatian" |
||||
728 | "si", // "Singhalese" |
||||
729 | "sk", // "Slovak" |
||||
730 | "sl", // "Slovenian" |
||||
731 | "sm", // "Samoan" |
||||
732 | "sn", // "Shona" |
||||
733 | "so", // "Somali" |
||||
734 | "sq", // "Albanian" |
||||
735 | "sr", // "Serbian" |
||||
736 | "sr_latin", // "Serbian (Latin)" |
||||
737 | "ss", // "Siswati" |
||||
738 | "st", // "Sesotho" |
||||
739 | "su", // "Sundanese" |
||||
740 | "sv", // "Swedish" |
||||
741 | "sw", // "Swahili" |
||||
742 | "ta", // "Tamil" |
||||
743 | "te", // "Tegulu" |
||||
744 | "tg", // "Tajik" |
||||
745 | "th", // "Thai" |
||||
746 | "ti", // "Tigrinya" |
||||
747 | "tk", // "Turkmen" |
||||
748 | "tl", // "Tagalog" |
||||
749 | "tn", // "Setswana" |
||||
750 | "to", // "Tonga" |
||||
751 | "tr", // "Turkish" |
||||
752 | "ts", // "Tsonga" |
||||
753 | "tt", // "Tatar" |
||||
754 | "tw", // "Twi" |
||||
755 | "ug", // "Uigur" |
||||
756 | "uk", // "Ukrainian" |
||||
757 | "ur", // "Urdu" |
||||
758 | "uz", // "Uzbek" |
||||
759 | "vi", // "Vietnamese" |
||||
760 | "vo", // "Volapuk" |
||||
761 | "wo", // "Wolof" |
||||
762 | "xh", // "Xhosa" |
||||
763 | "yi", // "Yiddish" |
||||
764 | "yo", // "Yoruba" |
||||
765 | "za", // "Zuang" |
||||
766 | "zh", // "Chinese" |
||||
767 | "zh_hans", // "Chinese Simplified" |
||||
768 | "zu", // "Zulu" |
||||
769 | ]; |
||||
770 | } |
||||
771 | |||||
772 | /** |
||||
773 | * Normalize a language code (e.g. from Transifex) |
||||
774 | * |
||||
775 | * @param string $code Language code |
||||
776 | * |
||||
777 | * @return string |
||||
778 | */ |
||||
779 | public static function normalizeLanguageCode($code) { |
||||
780 | $code = strtolower($code); |
||||
781 | $code = preg_replace('~[^a-z0-9]~', '_', $code); |
||||
782 | return $code; |
||||
783 | } |
||||
784 | } |
||||
785 |