Elgg  Version 3.0
Translator.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\I18n;
4 
5 use Elgg\Config;
6 use Elgg\Includer;
7 
15 class Translator {
16 
20  private $config;
21 
25  private $localeService;
26 
30  private $translations = [];
31 
35  private $defaultPath = null;
36 
40  private $current_language = null;
41 
56  private $language_paths = [];
57 
61  private $was_reloaded = false;
62 
69  public function __construct(Config $config, LocaleService $localService) {
70  $this->config = $config;
71  $this->localeService = $localService;
72 
73  $this->defaultPath = dirname(dirname(dirname(dirname(__DIR__)))) . "/languages/";
74 
75  $this->registerLanguagePath($this->defaultPath);
76  }
77 
83  public function getLoadedTranslations() {
84  return $this->translations;
85  }
86 
98  public function translate($message_key, array $args = [], $language = "") {
99  if (!is_string($message_key) || strlen($message_key) < 1) {
100  _elgg_services()->logger->warning(
101  '$message_key needs to be a string in ' . __METHOD__ . '(), ' . gettype($message_key) . ' provided'
102  );
103  return '';
104  }
105 
106  if (!$language) {
107  // no language provided, get current language
108  // based on detection, user setting or site
109  $language = $this->getCurrentLanguage();
110  }
111 
112  $this->ensureTranslationsLoaded($language);
113 
114  // build language array for different trys
115  // avoid dupes without overhead of array_unique
116  $langs = [
117  $language => true,
118  ];
119 
120  // load site language
121  $site_language = $this->config->language;
122  if (!empty($site_language)) {
123  $this->ensureTranslationsLoaded($site_language);
124 
125  $langs[$site_language] = true;
126  }
127 
128  // ultimate language fallback
129  $langs['en'] = true;
130 
131  // try to translate
132  $logger = _elgg_services()->logger;
133  $string = $message_key;
134  foreach (array_keys($langs) as $try_lang) {
135  if (isset($this->translations[$try_lang][$message_key])) {
136  $string = $this->translations[$try_lang][$message_key];
137 
138  // only pass through if we have arguments to allow backward compatibility
139  // with manual sprintf() calls.
140  if (!empty($args)) {
141  $string = vsprintf($string, $args);
142  }
143 
144  break;
145  } else {
146  $message = sprintf(
147  'Missing %s translation for "%s" language key',
148  ($try_lang === 'en') ? 'English' : $try_lang,
149  $message_key
150  );
151 
152  if ($try_lang === 'en') {
153  $logger->notice($message);
154  } else {
155  $logger->info($message);
156  }
157  }
158  }
159 
160  return $string;
161  }
162 
177  public function addTranslation($country_code, $language_array, $ensure_translations_loaded = true) {
178  $country_code = strtolower($country_code);
179  $country_code = trim($country_code);
180 
181  if (!is_array($language_array) || empty($language_array) || $country_code === "") {
182  return false;
183  }
184 
185  if (!isset($this->translations[$country_code])) {
186  $this->translations[$country_code] = [];
187 
188  if ($ensure_translations_loaded) {
189  // make sure all existing paths are included first before adding language arrays
190  $this->loadTranslations($country_code);
191  }
192  }
193 
194  // Note that we are using union operator instead of array_merge() due to performance implications
195  $this->translations[$country_code] = $language_array + $this->translations[$country_code];
196 
197  return true;
198  }
199 
205  public function getCurrentLanguage() {
206  if (!isset($this->current_language)) {
207  $this->current_language = $this->detectLanguage();
208  }
209 
210  if (!$this->current_language) {
211  $this->current_language = 'en';
212  }
213 
214  return $this->current_language;
215  }
216 
224  public function setCurrentLanguage($language = null) {
225  $this->current_language = $language;
226  }
227 
235  public function detectLanguage() {
236  // detect from URL
237  $url_lang = _elgg_services()->request->getParam('hl');
238  if (!empty($url_lang)) {
239  return $url_lang;
240  }
241 
242  // check logged in user
243  $user = _elgg_services()->session->getLoggedInUser();
244  if (!empty($user) && !empty($user->language)) {
245  return $user->language;
246  }
247 
248  // get site setting
249  $site_language = $this->config->language;
250  if (!empty($site_language)) {
251  return $site_language;
252  }
253 
254  return false;
255  }
256 
266  public function bootTranslations() {
267  $languages = array_unique(['en', $this->getCurrentLanguage()]);
268 
269  foreach ($languages as $language) {
270  $this->loadTranslations($language);
271  }
272  }
273 
287  public function loadTranslations($language) {
288  if (!is_string($language)) {
289  return;
290  }
291 
292  $data = elgg_load_system_cache("{$language}.lang");
293  if (is_array($data)) {
294  $this->addTranslation($language, $data, false);
295  return;
296  }
297 
298  foreach ($this->getLanguagePaths() as $path) {
299  $this->registerTranslations($path, false, $language);
300  }
301 
302  $translations = elgg_extract($language, $this->translations, []);
303  elgg_save_system_cache("{$language}.lang", $translations);
304  }
305 
318  public function registerTranslations($path, $load_all = false, $language = null) {
320 
321  // don't need to register translations as the folder is missing
322  if (!is_dir($path)) {
323  _elgg_services()->logger->info("No translations could be loaded from: $path");
324  return true;
325  }
326 
327  // Make a note of this path just in case we need to register this language later
328  $this->registerLanguagePath($path);
329 
330  _elgg_services()->logger->info("Translations loaded from: $path");
331 
332  if ($language) {
333  $load_language_files = ["$language.php"];
334  $load_all = false;
335  } else {
336  // Get the current language based on site defaults and user preference
337  $current_language = $this->getCurrentLanguage();
338 
339  $load_language_files = [
340  'en.php',
341  "$current_language.php"
342  ];
343 
344  $load_language_files = array_unique($load_language_files);
345  }
346 
347  $return = true;
348  if ($handle = opendir($path)) {
349  while (false !== ($language_file = readdir($handle))) {
350  // ignore bad files
351  if (substr($language_file, 0, 1) == '.' || substr($language_file, -4) !== '.php') {
352  continue;
353  }
354 
355  if (in_array($language_file, $load_language_files) || $load_all) {
356  $return = $return && $this->includeLanguageFile($path . $language_file);
357  }
358  }
359  closedir($handle);
360  } else {
361  _elgg_services()->logger->error("Could not open language path: $path");
362  $return = false;
363  }
364 
365  return $return;
366  }
367 
376  protected function includeLanguageFile($path) {
378 
379  if (is_array($result)) {
380  $this->addTranslation(basename($path, '.php'), $result);
381  return true;
382  }
383 
384  return false;
385  }
386 
398  public function reloadAllTranslations() {
399  if ($this->was_reloaded) {
400  return;
401  }
402 
403  $languages = $this->getAvailableLanguages();
404 
405  foreach ($languages as $language) {
406  $this->ensureTranslationsLoaded($language);
407  }
408 
409  _elgg_services()->events->triggerAfter('reload', 'translations');
410 
411  $this->was_reloaded = true;
412  }
413 
422  public function getInstalledTranslations($calculate_completeness = false) {
423  if ($calculate_completeness) {
424  // Ensure that all possible translations are loaded
425  $this->reloadAllTranslations();
426  }
427 
428  $result = [];
429 
430  $languages = $this->getAvailableLanguages();
431  foreach ($languages as $language) {
432  if ($this->languageKeyExists($language, $language)) {
433  $value = $this->translate($language, [], $language);
434  } else {
435  $value = $this->translate($language);
436  }
437 
438  if (($language !== 'en') && $calculate_completeness) {
439  $completeness = $this->getLanguageCompleteness($language);
440  $value .= " (" . $completeness . "% " . $this->translate('complete') . ")";
441  }
442 
444  }
445 
446  natcasesort($result);
447 
448  return $result;
449  }
450 
459 
460  if ($language == 'en') {
461  return (float) 100;
462  }
463 
464  // Ensure that all possible translations are loaded
465  $this->reloadAllTranslations();
466 
467  $en = count($this->translations['en']);
468 
469  $missing = $this->getMissingLanguageKeys($language);
470  if ($missing) {
471  $missing = count($missing);
472  } else {
473  $missing = 0;
474  }
475 
476  $lang = $en - $missing;
477 
478  return round(($lang / $en) * 100, 2);
479  }
480 
492 
493  // Ensure that all possible translations are loaded
494  $this->reloadAllTranslations();
495 
496  $missing = [];
497 
498  foreach ($this->translations['en'] as $k => $v) {
499  if ((!isset($this->translations[$language][$k]))
500  || ($this->translations[$language][$k] == $this->translations['en'][$k])) {
501  $missing[] = $k;
502  }
503  }
504 
505  if (count($missing)) {
506  return $missing;
507  }
508 
509  return false;
510  }
511 
521  public function languageKeyExists($key, $language = 'en') {
522  if (empty($key)) {
523  return false;
524  }
525 
526  $this->ensureTranslationsLoaded($language);
527 
528  if (!array_key_exists($language, $this->translations)) {
529  return false;
530  }
531 
532  return array_key_exists($key, $this->translations[$language]);
533  }
534 
541  public function getAvailableLanguages() {
542  $languages = [];
543 
544  $allowed_languages = $this->localeService->getLanguageCodes();
545 
546  foreach ($this->getLanguagePaths() as $path) {
547  try {
548  $iterator = new \DirectoryIterator($path);
549  } catch (\Exception $e) {
550  continue;
551  }
552 
553  foreach ($iterator as $file) {
554  if ($file->isDir()) {
555  continue;
556  }
557 
558  if ($file->getExtension() !== 'php') {
559  continue;
560  }
561 
562  $language = $file->getBasename('.php');
563  if (empty($language) || !in_array($language, $allowed_languages)) {
564  continue;
565  }
566 
567  $languages[$language] = true;
568  }
569  }
570 
571  $languages = array_keys($languages);
572 
573  return _elgg_services()->hooks->trigger('languages', 'translations', [], $languages);
574  }
575 
585  public function registerLanguagePath($path) {
586  $this->language_paths[$path] = true;
587  }
588 
594  protected function getLanguagePaths() {
595  return array_keys($this->language_paths);
596  }
597 
604  private function ensureTranslationsLoaded($language) {
605  if (isset($this->translations[$language])) {
606  return;
607  }
608 
609  // The language being requested is not the same as the language of the
610  // logged in user, so we will have to load it separately. (Most likely
611  // we're sending a notification and the recipient is using a different
612  // language than the logged in user.)
613  $this->loadTranslations($language);
614  }
615 
622  public static function getAllLanguageCodes() {
623  elgg_deprecated_notice(__METHOD__ . ' has been deprecated use elgg()->locale->getLanguageCodes()', '3.0');
624  return elgg()->locale->getLanguageCodes();
625  }
626 
636  public static function normalizeLanguageCode($code) {
637  $code = strtolower($code);
638  $code = preg_replace('~[^a-z0-9]~', '_', $code);
639  return $code;
640  }
641 }
__construct(Config $config, LocaleService $localService)
Constructor.
Definition: Translator.php:69
static includeFile($file)
Include a file with as little context as possible.
Definition: Includer.php:18
registerLanguagePath($path)
Registers a path for potential translation files.
Definition: Translator.php:585
getInstalledTranslations($calculate_completeness=false)
Return an array of installed translations as an associative array "two letter code" => "native langua...
Definition: Translator.php:422
if(!array_key_exists($filename, $text_files)) $file
setCurrentLanguage($language=null)
Sets current system language.
Definition: Translator.php:224
$lang
Definition: html.php:13
registerTranslations($path, $load_all=false, $language=null)
When given a full path, finds translation files and loads them.
Definition: Translator.php:318
static getAllLanguageCodes()
Returns an array of language codes.
Definition: Translator.php:622
$path
Definition: details.php:89
translate($message_key, array $args=[], $language="")
Given a message key, returns an appropriately translated full-text string.
Definition: Translator.php:98
if(elgg_trigger_plugin_hook('usersettings:save', 'user', $hooks_params, true)) foreach($request->validation() ->all() as $item) $data
Definition: save.php:57
addTranslation($country_code, $language_array, $ensure_translations_loaded=true)
Add a translation.
Definition: Translator.php:177
loadTranslations($language)
Load both core and plugin translations.
Definition: Translator.php:287
getAvailableLanguages()
Returns an array of all available language keys.
Definition: Translator.php:541
$args
Some servers don&#39;t allow PHP to check the rewrite, so try via AJAX.
$code
Provides locale related features.
getMissingLanguageKeys($language)
Return the translation keys missing from a given language, or those that are identical to the english...
Definition: Translator.php:491
includeLanguageFile($path)
Load cached or include a language file by its path.
Definition: Translator.php:376
detectLanguage()
Detect the current system/user language or false.
Definition: Translator.php:235
reloadAllTranslations()
Reload all translations from all registered paths.
Definition: Translator.php:398
$language
Definition: useradd.php:17
getLanguagePaths()
Returns a unique array with locations of translation files.
Definition: Translator.php:594
getLoadedTranslations()
Get a map of all loaded translations.
Definition: Translator.php:83
$user
Definition: ban.php:7
bootTranslations()
Ensures all needed translations are loaded.
Definition: Translator.php:266
elgg_deprecated_notice($msg, $dep_version, $backtrace_level=1)
Log a notice about deprecated use of a function, view, etc.
Definition: elgglib.php:841
getCurrentLanguage()
Get the current system/user language or "en".
Definition: Translator.php:205
getLanguageCompleteness($language)
Return the level of completeness for a given language code (compared to english)
Definition: Translator.php:458
elgg_extract($key, $array, $default=null, $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:1131
if($container instanceof ElggGroup &&$container->guid!=elgg_get_page_owner_guid()) $key
Definition: summary.php:55
$site_language
elgg_load_system_cache($type)
Retrieve the contents of a system cache.
Definition: cache.php:47
$value
Definition: debugging.php:7
static normalizeLanguageCode($code)
Normalize a language code (e.g.
Definition: Translator.php:636
elgg_save_system_cache($type, $data)
Saves a system cache.
Definition: cache.php:36
languageKeyExists($key, $language= 'en')
Check if a given language key exists.
Definition: Translator.php:521
static sanitize($path, $append_slash=true)
Sanitise file paths ensuring that they begin and end with slashes etc.
Definition: Paths.php:76
_elgg_services()
Get the global service provider.
Definition: elgglib.php:1292
var elgg
Definition: elgglib.js:4