* @psalm-var array */ private static $languageData = [ 'af' => [ 'af', 'Afrikaans', '', 'af|afrikaans', '', ], 'am' => [ 'am', 'Amharic', 'አማርኛ', 'am|amharic', '', ], 'ar' => [ 'ar', 'Arabic', 'العربية', 'ar|arabic', 'ar_AE', ], 'ar_ly' => [ 'ar_LY', 'Arabic (Libya)', 'ليبي', 'ar[_-]ly|arabic (libya)|libian arabic', 'ar_LY', ], 'az' => [ 'az', 'Azerbaijani', 'Azərbaycanca', 'az|azerbaijani', '', ], 'bn' => [ 'bn', 'Bangla', 'বাংলা', 'bn|bangla', '', ], 'be' => [ 'be', 'Belarusian', 'Беларуская', 'be|belarusian', 'be_BY', ], 'be@latin' => [ 'be@latin', 'Belarusian (latin)', 'Biełaruskaja', 'be[-_]lat|be@latin|belarusian latin', '', ], 'ber' => [ 'ber', 'Berber', 'Tamaziɣt', 'ber|berber', '', ], 'bg' => [ 'bg', 'Bulgarian', 'Български', 'bg|bulgarian', 'bg_BG', ], 'bs' => [ 'bs', 'Bosnian', 'Bosanski', 'bs|bosnian', '', ], 'br' => [ 'br', 'Breton', 'Brezhoneg', 'br|breton', '', ], 'brx' => [ 'brx', 'Bodo', 'बड़ो', 'brx|bodo', '', ], 'ca' => [ 'ca', 'Catalan', 'Català', 'ca|catalan', 'ca_ES', ], 'ckb' => [ 'ckb', 'Sorani', 'سۆرانی', 'ckb|sorani', '', ], 'cs' => [ 'cs', 'Czech', 'Čeština', 'cs|czech', 'cs_CZ', ], 'cy' => [ 'cy', 'Welsh', 'Cymraeg', 'cy|welsh', '', ], 'da' => [ 'da', 'Danish', 'Dansk', 'da|danish', 'da_DK', ], 'de' => [ 'de', 'German', 'Deutsch', 'de|german', 'de_DE', ], 'el' => [ 'el', 'Greek', 'Ελληνικά', 'el|greek', '', ], 'en' => [ 'en', 'English', '', 'en|english', 'en_US', ], 'en_gb' => [ 'en_GB', 'English (United Kingdom)', '', 'en[_-]gb|english (United Kingdom)', 'en_GB', ], 'enm' => [ 'enm', 'English (Middle)', '', 'enm|english (middle)', '', ], 'eo' => [ 'eo', 'Esperanto', 'Esperanto', 'eo|esperanto', '', ], 'es' => [ 'es', 'Spanish', 'Español', 'es|spanish', 'es_ES', ], 'et' => [ 'et', 'Estonian', 'Eesti', 'et|estonian', 'et_EE', ], 'eu' => [ 'eu', 'Basque', 'Euskara', 'eu|basque', 'eu_ES', ], 'fa' => [ 'fa', 'Persian', 'فارسی', 'fa|persian', '', ], 'fi' => [ 'fi', 'Finnish', 'Suomi', 'fi|finnish', 'fi_FI', ], 'fil' => [ 'fil', 'Filipino', 'Pilipino', 'fil|filipino', '', ], 'fr' => [ 'fr', 'French', 'Français', 'fr|french', 'fr_FR', ], 'fy' => [ 'fy', 'Frisian', 'Frysk', 'fy|frisian', '', ], 'gl' => [ 'gl', 'Galician', 'Galego', 'gl|galician', 'gl_ES', ], 'gu' => [ 'gu', 'Gujarati', 'ગુજરાતી', 'gu|gujarati', 'gu_IN', ], 'he' => [ 'he', 'Hebrew', 'עברית', 'he|hebrew', 'he_IL', ], 'hi' => [ 'hi', 'Hindi', 'हिन्दी', 'hi|hindi', 'hi_IN', ], 'hr' => [ 'hr', 'Croatian', 'Hrvatski', 'hr|croatian', 'hr_HR', ], 'hu' => [ 'hu', 'Hungarian', 'Magyar', 'hu|hungarian', 'hu_HU', ], 'hy' => [ 'hy', 'Armenian', 'Հայերէն', 'hy|armenian', '', ], 'ia' => [ 'ia', 'Interlingua', '', 'ia|interlingua', '', ], 'id' => [ 'id', 'Indonesian', 'Bahasa Indonesia', 'id|indonesian', 'id_ID', ], 'ig' => [ 'ig', 'Igbo', 'Asụsụ Igbo', 'ig|igbo', '', ], 'it' => [ 'it', 'Italian', 'Italiano', 'it|italian', 'it_IT', ], 'ja' => [ 'ja', 'Japanese', '日本語', 'ja|japanese', 'ja_JP', ], 'ko' => [ 'ko', 'Korean', '한국어', 'ko|korean', 'ko_KR', ], 'ka' => [ 'ka', 'Georgian', 'ქართული', 'ka|georgian', '', ], 'kab' => [ 'kab', 'Kabylian', 'Taqbaylit', 'kab|kabylian', '', ], 'kk' => [ 'kk', 'Kazakh', 'Қазақ', 'kk|kazakh', '', ], 'km' => [ 'km', 'Khmer', 'ខ្មែរ', 'km|khmer', '', ], 'kn' => [ 'kn', 'Kannada', 'ಕನ್ನಡ', 'kn|kannada', '', ], 'ksh' => [ 'ksh', 'Colognian', 'Kölsch', 'ksh|colognian', '', ], 'ku' => [ 'ku', 'Kurdish', 'کوردی', 'ku|kurdish', '', ], 'ky' => [ 'ky', 'Kyrgyz', 'Кыргызча', 'ky|kyrgyz', '', ], 'li' => [ 'li', 'Limburgish', 'Lèmbörgs', 'li|limburgish', '', ], 'lt' => [ 'lt', 'Lithuanian', 'Lietuvių', 'lt|lithuanian', 'lt_LT', ], 'lv' => [ 'lv', 'Latvian', 'Latviešu', 'lv|latvian', 'lv_LV', ], 'mk' => [ 'mk', 'Macedonian', 'Macedonian', 'mk|macedonian', 'mk_MK', ], 'ml' => [ 'ml', 'Malayalam', 'Malayalam', 'ml|malayalam', '', ], 'mn' => [ 'mn', 'Mongolian', 'Монгол', 'mn|mongolian', 'mn_MN', ], 'ms' => [ 'ms', 'Malay', 'Bahasa Melayu', 'ms|malay', 'ms_MY', ], 'my' => [ 'my', 'Burmese', 'မြန်မာ', 'my|burmese', '', ], 'ne' => [ 'ne', 'Nepali', 'नेपाली', 'ne|nepali', '', ], 'nb' => [ 'nb', 'Norwegian', 'Norsk', 'nb|norwegian', 'nb_NO', ], 'nn' => [ 'nn', 'Norwegian Nynorsk', 'Nynorsk', 'nn|nynorsk', 'nn_NO', ], 'nl' => [ 'nl', 'Dutch', 'Nederlands', 'nl|dutch', 'nl_NL', ], 'pa' => [ 'pa', 'Punjabi', 'ਪੰਜਾਬੀ', 'pa|punjabi', '', ], 'pl' => [ 'pl', 'Polish', 'Polski', 'pl|polish', 'pl_PL', ], 'pt' => [ 'pt', 'Portuguese', 'Português', 'pt|portuguese', 'pt_PT', ], 'pt_br' => [ 'pt_BR', 'Portuguese (Brazil)', 'Português (Brasil)', 'pt[-_]br|portuguese (brazil)', 'pt_BR', ], 'rcf' => [ 'rcf', 'Réunion Creole', 'Kréol', 'rcf|creole (reunion)', '', ], 'ro' => [ 'ro', 'Romanian', 'Română', 'ro|romanian', 'ro_RO', ], 'ru' => [ 'ru', 'Russian', 'Русский', 'ru|russian', 'ru_RU', ], 'si' => [ 'si', 'Sinhala', 'සිංහල', 'si|sinhala', '', ], 'sk' => [ 'sk', 'Slovak', 'Slovenčina', 'sk|slovak', 'sk_SK', ], 'sl' => [ 'sl', 'Slovenian', 'Slovenščina', 'sl|slovenian', 'sl_SI', ], 'sq' => [ 'sq', 'Albanian', 'Shqip', 'sq|albanian', 'sq_AL', ], 'sr@latin' => [ 'sr@latin', 'Serbian (latin)', 'Srpski', 'sr[-_]lat|sr@latin|serbian latin', 'sr_YU', ], 'sr' => [ 'sr', 'Serbian', 'Српски', 'sr|serbian', 'sr_YU', ], 'sv' => [ 'sv', 'Swedish', 'Svenska', 'sv|swedish', 'sv_SE', ], 'ta' => [ 'ta', 'Tamil', 'தமிழ்', 'ta|tamil', 'ta_IN', ], 'te' => [ 'te', 'Telugu', 'తెలుగు', 'te|telugu', 'te_IN', ], 'th' => [ 'th', 'Thai', 'ภาษาไทย', 'th|thai', 'th_TH', ], 'tk' => [ 'tk', 'Turkmen', 'Türkmençe', 'tk|turkmen', '', ], 'tr' => [ 'tr', 'Turkish', 'Türkçe', 'tr|turkish', 'tr_TR', ], 'tt' => [ 'tt', 'Tatarish', 'Tatarça', 'tt|tatarish', '', ], 'tzm' => [ 'tzm', 'Central Atlas Tamazight', 'Tamaziɣt', 'tzm|central atlas tamazight', '', ], 'ug' => [ 'ug', 'Uyghur', 'ئۇيغۇرچە', 'ug|uyghur', '', ], 'uk' => [ 'uk', 'Ukrainian', 'Українська', 'uk|ukrainian', 'uk_UA', ], 'ur' => [ 'ur', 'Urdu', 'اُردوُ', 'ur|urdu', 'ur_PK', ], 'uz@latin' => [ 'uz@latin', 'Uzbek (latin)', 'O‘zbekcha', 'uz[-_]lat|uz@latin|uzbek-latin', '', ], 'uz' => [ 'uz', 'Uzbek (cyrillic)', 'Ўзбекча', 'uz[-_]cyr|uz@cyrillic|uzbek-cyrillic', '', ], 'vi' => [ 'vi', 'Vietnamese', 'Tiếng Việt', 'vi|vietnamese', 'vi_VN', ], 'vls' => [ 'vls', 'Flemish', 'West-Vlams', 'vls|flemish', '', ], 'zh_tw' => [ 'zh_TW', 'Chinese traditional', '中文', 'zh[-_](tw|hk)|chinese traditional', 'zh_TW', ], // only TW and HK use traditional Chinese while others (CN, SG, MY) // use simplified Chinese 'zh_cn' => [ 'zh_CN', 'Chinese simplified', '中文', 'zh(?![-_](tw|hk))([-_][[:alpha:]]{2,3})?|chinese simplified', 'zh_CN', ], ]; /** @var array */ private $availableLocales; /** @var array */ private $availableLanguages = []; /** @var bool */ private $langFailedConfig = false; /** @var bool */ private $langFailedCookie = false; /** @var bool */ private $langFailedRequest = false; /** @var LanguageManager */ private static $instance; /** * Returns LanguageManager singleton * * @return LanguageManager */ public static function getInstance() { if (self::$instance === null) { self::$instance = new LanguageManager(); } return self::$instance; } /** * Returns list of available locales * * @return array */ public function listLocaleDir() { $result = ['en']; /* Check for existing directory */ if (! is_dir(LOCALE_PATH)) { return $result; } /* Open the directory */ $handle = @opendir(LOCALE_PATH); /* This can happen if the kit is English-only */ if ($handle === false) { return $result; } /* Process all files */ while (($file = readdir($handle)) !== false) { $path = LOCALE_PATH . '/' . $file . '/LC_MESSAGES/phpmyadmin.mo'; if ($file === '.' || $file === '..' || ! @file_exists($path)) { continue; } $result[] = $file; } /* Close the handle */ closedir($handle); return $result; } /** * Returns (cached) list of all available locales * * @return array of strings */ public function availableLocales() { if (! $this->availableLocales) { if (! isset($GLOBALS['config']) || empty($GLOBALS['config']->get('FilterLanguages'))) { $this->availableLocales = $this->listLocaleDir(); } else { $this->availableLocales = preg_grep( '@' . $GLOBALS['config']->get('FilterLanguages') . '@', $this->listLocaleDir() ); } } return $this->availableLocales; } /** * Checks whether there are some languages available */ public function hasChoice(): bool { return count($this->availableLanguages()) > 1; } /** * Returns (cached) list of all available languages * * @return Language[] array of Language objects */ public function availableLanguages() { if (! $this->availableLanguages) { $this->availableLanguages = []; foreach ($this->availableLocales() as $lang) { $lang = strtolower($lang); if (isset(static::$languageData[$lang])) { $data = static::$languageData[$lang]; $this->availableLanguages[$lang] = new Language($data[0], $data[1], $data[2], $data[3], $data[4]); } else { $this->availableLanguages[$lang] = new Language( $lang, ucfirst($lang), ucfirst($lang), $lang, '' ); } } } return $this->availableLanguages; } /** * Returns (cached) list of all available languages sorted * by name * * @return Language[] array of Language objects */ public function sortedLanguages() { $this->availableLanguages(); uasort($this->availableLanguages, static function (Language $a, Language $b) { return $a->cmp($b); }); return $this->availableLanguages; } /** * Return Language object for given code * * @param string $code Language code * * @return Language|false Language object or false on failure */ public function getLanguage($code) { $code = strtolower($code); $langs = $this->availableLanguages(); if (isset($langs[$code])) { return $langs[$code]; } return false; } /** * Return currently active Language object * * @return Language Language object */ public function getCurrentLanguage() { return $this->availableLanguages[strtolower($GLOBALS['lang'])]; } /** * Activates language based on configuration, user preferences or * browser * * @return Language */ public function selectLanguage() { // check forced language if (! empty($GLOBALS['config']->get('Lang'))) { $lang = $this->getLanguage($GLOBALS['config']->get('Lang')); if ($lang !== false) { return $lang; } $this->langFailedConfig = true; } // Don't use REQUEST in following code as it might be confused by cookies // with same name. Check user requested language (POST) if (! empty($_POST['lang'])) { $lang = $this->getLanguage($_POST['lang']); if ($lang !== false) { return $lang; } $this->langFailedRequest = true; } // check user requested language (GET) if (! empty($_GET['lang'])) { $lang = $this->getLanguage($_GET['lang']); if ($lang !== false) { return $lang; } $this->langFailedRequest = true; } // check previous set language if (! empty($GLOBALS['config']->getCookie('pma_lang'))) { $lang = $this->getLanguage($GLOBALS['config']->getCookie('pma_lang')); if ($lang !== false) { return $lang; } $this->langFailedCookie = true; } $langs = $this->availableLanguages(); // try to find out user's language by checking its HTTP_ACCEPT_LANGUAGE variable; $accepted_languages = Core::getenv('HTTP_ACCEPT_LANGUAGE'); if ($accepted_languages) { foreach (explode(',', $accepted_languages) as $header) { foreach ($langs as $language) { if ($language->matchesAcceptLanguage($header)) { return $language; } } } } // try to find out user's language by checking its HTTP_USER_AGENT variable $user_agent = Core::getenv('HTTP_USER_AGENT'); if (! empty($user_agent)) { foreach ($langs as $language) { if ($language->matchesUserAgent($user_agent)) { return $language; } } } // Didn't catch any valid lang : we use the default settings if (isset($langs[$GLOBALS['config']->get('DefaultLang')])) { return $langs[$GLOBALS['config']->get('DefaultLang')]; } // Fallback to English return $langs['en']; } /** * Displays warnings about invalid languages. This needs to be postponed * to show messages at time when language is initialized. */ public function showWarnings(): void { // now, that we have loaded the language strings we can send the errors if (! $this->langFailedConfig && ! $this->langFailedCookie && ! $this->langFailedRequest) { return; } trigger_error( __('Ignoring unsupported language code.'), E_USER_ERROR ); } }