Translated using Weblate (Portuguese)
[phpmyadmin.git] / src / LanguageManager.php
blobb76d449971decfc2f09f8ab994d9b332ffb58e03
1 <?php
3 declare(strict_types=1);
5 namespace PhpMyAdmin;
7 use PhpMyAdmin\Exceptions\UnsupportedLanguageCode;
9 use function __;
10 use function closedir;
11 use function count;
12 use function explode;
13 use function file_exists;
14 use function is_dir;
15 use function opendir;
16 use function preg_grep;
17 use function readdir;
18 use function strtolower;
19 use function uasort;
20 use function ucfirst;
22 /**
23 * Language selection manager
25 class LanguageManager
27 /**
28 * Definition data for languages
30 * Each member contains:
31 * - Language code
32 * - English language name
33 * - Native language name
34 * - Match regular expression
35 * - MySQL locale
37 * @var array<string, string[]>
38 * @psalm-var array<string, array{non-empty-string, non-empty-string, string, non-empty-string, string}>
40 private static array $languageData = [
41 'af' => [
42 'af',
43 'Afrikaans',
44 '',
45 'af|afrikaans',
46 '',
48 'am' => [
49 'am',
50 'Amharic',
51 'አማርኛ',
52 'am|amharic',
53 '',
55 'ar' => [
56 'ar',
57 'Arabic',
58 '&#1575;&#1604;&#1593;&#1585;&#1576;&#1610;&#1577;',
59 'ar(?![-_]ly)([-_][[:alpha:]]{2,3})?|arabic',
60 'ar_AE',
62 'ar_ly' => [
63 'ar_LY',
64 'Arabic (Libya)',
65 'ليبي',
66 'ar[_-]ly|arabic (libya)|libian arabic',
67 'ar_LY',
69 'az' => [
70 'az',
71 'Azerbaijani',
72 'Az&#601;rbaycanca',
73 'az|azerbaijani',
74 '',
76 'bn' => [
77 'bn',
78 'Bangla',
79 'বাংলা',
80 'bn|bangla',
81 '',
83 'be' => [
84 'be',
85 'Belarusian',
86 '&#1041;&#1077;&#1083;&#1072;&#1088;&#1091;&#1089;&#1082;&#1072;&#1103;',
87 'be|belarusian',
88 'be_BY',
90 'be@latin' => [
91 'be@latin',
92 'Belarusian (latin)',
93 'Bie&#0322;aruskaja',
94 'be[-_]lat|be@latin|belarusian latin',
95 '',
97 'ber' => [
98 'ber',
99 'Berber',
100 'Tamaziɣt',
101 'ber|berber',
104 'bg' => [
105 'bg',
106 'Bulgarian',
107 '&#1041;&#1098;&#1083;&#1075;&#1072;&#1088;&#1089;&#1082;&#1080;',
108 'bg|bulgarian',
109 'bg_BG',
111 'bs' => [
112 'bs',
113 'Bosnian',
114 'Bosanski',
115 'bs|bosnian',
118 'br' => [
119 'br',
120 'Breton',
121 'Brezhoneg',
122 'br|breton',
125 'brx' => [
126 'brx',
127 'Bodo',
128 'बड़ो',
129 'brx|bodo',
132 'ca' => [
133 'ca',
134 'Catalan',
135 'Catal&agrave;',
136 'ca|catalan',
137 'ca_ES',
139 'ckb' => [
140 'ckb',
141 'Sorani',
142 'سۆرانی',
143 'ckb|sorani',
146 'cs' => [
147 'cs',
148 'Czech',
149 'Čeština',
150 'cs|czech',
151 'cs_CZ',
153 'cy' => [
154 'cy',
155 'Welsh',
156 'Cymraeg',
157 'cy|welsh',
160 'da' => [
161 'da',
162 'Danish',
163 'Dansk',
164 'da|danish',
165 'da_DK',
167 'de' => [
168 'de',
169 'German',
170 'Deutsch',
171 'de|german',
172 'de_DE',
174 'el' => [
175 'el',
176 'Greek',
177 '&Epsilon;&lambda;&lambda;&eta;&nu;&iota;&kappa;&#940;',
178 'el|greek',
181 'en' => [
182 'en',
183 'English',
185 'en(?![-_]gb)([-_][[:alpha:]]{2,3})?|english',
186 'en_US',
188 'en_gb' => [
189 'en_GB',
190 'English (United Kingdom)',
192 'en[_-]gb|english (United Kingdom)',
193 'en_GB',
195 'enm' => [
196 'enm',
197 'English (Middle)',
199 'enm|english (middle)',
202 'eo' => [
203 'eo',
204 'Esperanto',
205 'Esperanto',
206 'eo|esperanto',
209 'es' => [
210 'es',
211 'Spanish',
212 'Espa&ntilde;ol',
213 'es|spanish',
214 'es_ES',
216 'et' => [
217 'et',
218 'Estonian',
219 'Eesti',
220 'et|estonian',
221 'et_EE',
223 'eu' => [
224 'eu',
225 'Basque',
226 'Euskara',
227 'eu|basque',
228 'eu_ES',
230 'fa' => [
231 'fa',
232 'Persian',
233 '&#1601;&#1575;&#1585;&#1587;&#1740;',
234 'fa|persian',
237 'fi' => [
238 'fi',
239 'Finnish',
240 'Suomi',
241 'fi|finnish',
242 'fi_FI',
244 'fil' => [
245 'fil',
246 'Filipino',
247 'Pilipino',
248 'fil|filipino',
251 'fr' => [
252 'fr',
253 'French',
254 'Fran&ccedil;ais',
255 'fr|french',
256 'fr_FR',
258 'fy' => [
259 'fy',
260 'Frisian',
261 'Frysk',
262 'fy|frisian',
265 'gl' => [
266 'gl',
267 'Galician',
268 'Galego',
269 'gl|galician',
270 'gl_ES',
272 'gu' => [
273 'gu',
274 'Gujarati',
275 'ગુજરાતી',
276 'gu|gujarati',
277 'gu_IN',
279 'he' => [
280 'he',
281 'Hebrew',
282 '&#1506;&#1489;&#1512;&#1497;&#1514;',
283 'he|hebrew',
284 'he_IL',
286 'hi' => [
287 'hi',
288 'Hindi',
289 '&#2361;&#2367;&#2344;&#2381;&#2342;&#2368;',
290 'hi|hindi',
291 'hi_IN',
293 'hr' => [
294 'hr',
295 'Croatian',
296 'Hrvatski',
297 'hr|croatian',
298 'hr_HR',
300 'hu' => [
301 'hu',
302 'Hungarian',
303 'Magyar',
304 'hu|hungarian',
305 'hu_HU',
307 'hy' => [
308 'hy',
309 'Armenian',
310 'Հայերէն',
311 'hy|armenian',
314 'ia' => [
315 'ia',
316 'Interlingua',
318 'ia|interlingua',
321 'id' => [
322 'id',
323 'Indonesian',
324 'Bahasa Indonesia',
325 'id|indonesian',
326 'id_ID',
328 'ig' => [
329 'ig',
330 'Igbo',
331 'Asụsụ Igbo',
332 'ig|igbo',
335 'it' => [
336 'it',
337 'Italian',
338 'Italiano',
339 'it|italian',
340 'it_IT',
342 'ja' => [
343 'ja',
344 'Japanese',
345 '&#26085;&#26412;&#35486;',
346 'ja|japanese',
347 'ja_JP',
349 'ko' => [
350 'ko',
351 'Korean',
352 '&#54620;&#44397;&#50612;',
353 'ko|korean',
354 'ko_KR',
356 'ka' => [
357 'ka',
358 'Georgian',
359 '&#4325;&#4304;&#4320;&#4311;&#4323;&#4314;&#4312;',
360 'ka|georgian',
363 'kab' => [
364 'kab',
365 'Kabylian',
366 'Taqbaylit',
367 'kab|kabylian',
370 'kk' => [
371 'kk',
372 'Kazakh',
373 'Қазақ',
374 'kk|kazakh',
377 'km' => [
378 'km',
379 'Khmer',
380 'ខ្មែរ',
381 'km|khmer',
384 'kn' => [
385 'kn',
386 'Kannada',
387 'ಕನ್ನಡ',
388 'kn|kannada',
391 'ksh' => [
392 'ksh',
393 'Colognian',
394 'Kölsch',
395 'ksh|colognian',
398 'ku' => [
399 'ku',
400 'Kurdish',
401 'کوردی',
402 'ku|kurdish',
405 'ky' => [
406 'ky',
407 'Kyrgyz',
408 'Кыргызча',
409 'ky|kyrgyz',
412 'li' => [
413 'li',
414 'Limburgish',
415 'Lèmbörgs',
416 'li|limburgish',
419 'lt' => [
420 'lt',
421 'Lithuanian',
422 'Lietuvi&#371;',
423 'lt|lithuanian',
424 'lt_LT',
426 'lv' => [
427 'lv',
428 'Latvian',
429 'Latvie&scaron;u',
430 'lv|latvian',
431 'lv_LV',
433 'mk' => [
434 'mk',
435 'Macedonian',
436 'Macedonian',
437 'mk|macedonian',
438 'mk_MK',
440 'ml' => [
441 'ml',
442 'Malayalam',
443 'Malayalam',
444 'ml|malayalam',
447 'mn' => [
448 'mn',
449 'Mongolian',
450 '&#1052;&#1086;&#1085;&#1075;&#1086;&#1083;',
451 'mn|mongolian',
452 'mn_MN',
454 'ms' => [
455 'ms',
456 'Malay',
457 'Bahasa Melayu',
458 'ms|malay',
459 'ms_MY',
461 'my' => [
462 'my',
463 'Burmese',
464 'မြန်မာ',
465 'my|burmese',
468 'ne' => [
469 'ne',
470 'Nepali',
471 'नेपाली',
472 'ne|nepali',
475 'nb' => [
476 'nb',
477 'Norwegian',
478 'Norsk',
479 'nb|norwegian',
480 'nb_NO',
482 'nn' => [
483 'nn',
484 'Norwegian Nynorsk',
485 'Nynorsk',
486 'nn|nynorsk',
487 'nn_NO',
489 'nl' => [
490 'nl',
491 'Dutch',
492 'Nederlands',
493 'nl|dutch',
494 'nl_NL',
496 'pa' => [
497 'pa',
498 'Punjabi',
499 'ਪੰਜਾਬੀ',
500 'pa|punjabi',
503 'pl' => [
504 'pl',
505 'Polish',
506 'Polski',
507 'pl|polish',
508 'pl_PL',
510 'pt' => [
511 'pt',
512 'Portuguese',
513 'Portugu&ecirc;s',
514 'pt(?![-_]br)([-_][[:alpha:]]{2,3})?|portuguese',
515 'pt_PT',
517 'pt_br' => [
518 'pt_BR',
519 'Portuguese (Brazil)',
520 'Portugu&ecirc;s (Brasil)',
521 'pt[-_]br|portuguese (brazil)',
522 'pt_BR',
524 'rcf' => [
525 'rcf',
526 'R&eacute;union Creole',
527 'Kr&eacute;ol',
528 'rcf|creole (reunion)',
531 'ro' => [
532 'ro',
533 'Romanian',
534 'Rom&acirc;n&#259;',
535 'ro|romanian',
536 'ro_RO',
538 'ru' => [
539 'ru',
540 'Russian',
541 '&#1056;&#1091;&#1089;&#1089;&#1082;&#1080;&#1081;',
542 'ru|russian',
543 'ru_RU',
545 'si' => [
546 'si',
547 'Sinhala',
548 '&#3523;&#3538;&#3458;&#3524;&#3517;',
549 'si|sinhala',
552 'sk' => [
553 'sk',
554 'Slovak',
555 'Sloven&#269;ina',
556 'sk|slovak',
557 'sk_SK',
559 'sl' => [
560 'sl',
561 'Slovenian',
562 'Sloven&scaron;&#269;ina',
563 'sl|slovenian',
564 'sl_SI',
566 'sq' => [
567 'sq',
568 'Albanian',
569 'Shqip',
570 'sq|albanian',
571 'sq_AL',
573 'sr@latin' => [
574 'sr@latin',
575 'Serbian (latin)',
576 'Srpski',
577 'sr[-_]lat|sr@latin|serbian latin',
578 'sr_YU',
580 'sr' => [
581 'sr',
582 'Serbian',
583 '&#1057;&#1088;&#1087;&#1089;&#1082;&#1080;',
584 'sr|serbian',
585 'sr_YU',
587 'sv' => [
588 'sv',
589 'Swedish',
590 'Svenska',
591 'sv|swedish',
592 'sv_SE',
594 'ta' => [
595 'ta',
596 'Tamil',
597 'தமிழ்',
598 'ta|tamil',
599 'ta_IN',
601 'te' => [
602 'te',
603 'Telugu',
604 'తెలుగు',
605 'te|telugu',
606 'te_IN',
608 'th' => [
609 'th',
610 'Thai',
611 '&#3616;&#3634;&#3625;&#3634;&#3652;&#3607;&#3618;',
612 'th|thai',
613 'th_TH',
615 'tk' => [
616 'tk',
617 'Turkmen',
618 'Türkmençe',
619 'tk|turkmen',
622 'tr' => [
623 'tr',
624 'Turkish',
625 'T&uuml;rk&ccedil;e',
626 'tr|turkish',
627 'tr_TR',
629 'tt' => [
630 'tt',
631 'Tatarish',
632 'Tatar&ccedil;a',
633 'tt|tatarish',
636 'tzm' => [
637 'tzm',
638 'Central Atlas Tamazight',
639 'Tamaziɣt',
640 'tzm|central atlas tamazight',
643 'ug' => [
644 'ug',
645 'Uyghur',
646 'ئۇيغۇرچە',
647 'ug|uyghur',
650 'uk' => [
651 'uk',
652 'Ukrainian',
653 '&#1059;&#1082;&#1088;&#1072;&#1111;&#1085;&#1089;&#1100;&#1082;&#1072;',
654 'uk|ukrainian',
655 'uk_UA',
657 'ur' => [
658 'ur',
659 'Urdu',
660 'اُردوُ',
661 'ur|urdu',
662 'ur_PK',
664 'uz@latin' => [
665 'uz@latin',
666 'Uzbek (latin)',
667 'O&lsquo;zbekcha',
668 'uz[-_]lat|uz@latin|uzbek-latin',
671 'uz' => [
672 'uz',
673 'Uzbek (cyrillic)',
674 '&#1038;&#1079;&#1073;&#1077;&#1082;&#1095;&#1072;',
675 'uz[-_]cyr|uz@cyrillic|uzbek-cyrillic',
678 'vi' => [
679 'vi',
680 'Vietnamese',
681 'Tiếng Việt',
682 'vi|vietnamese',
683 'vi_VN',
685 'vls' => [
686 'vls',
687 'Flemish',
688 'West-Vlams',
689 'vls|flemish',
692 'zh_tw' => [
693 'zh_TW',
694 'Chinese traditional',
695 '&#20013;&#25991;',
696 'zh[-_](tw|hk)|chinese traditional',
697 'zh_TW',
699 // only TW and HK use traditional Chinese while others (CN, SG, MY)
700 // use simplified Chinese
701 'zh_cn' => [
702 'zh_CN',
703 'Chinese simplified',
704 '&#20013;&#25991;',
705 'zh(?![-_](tw|hk))([-_][[:alpha:]]{2,3})?|chinese simplified',
706 'zh_CN',
710 /** @var string[] */
711 private array $availableLocales = [];
713 /** @var array<string, Language> */
714 private array $availableLanguages = [];
716 private bool $langFailedConfig = false;
718 private bool $langFailedCookie = false;
720 private bool $langFailedRequest = false;
722 private static LanguageManager|null $instance = null;
724 /** @psalm-var 'ltr'|'rtl' */
725 public static string $textDir = 'ltr';
726 private readonly Config $config;
728 public function __construct()
730 $this->config = Config::getInstance();
734 * Returns LanguageManager singleton
736 public static function getInstance(): LanguageManager
738 if (self::$instance === null) {
739 self::$instance = new LanguageManager();
742 return self::$instance;
746 * Returns list of available locales
748 * @return string[]
750 public function listLocaleDir(): array
752 $result = ['en'];
754 /* Check for existing directory */
755 if (! is_dir(LOCALE_PATH)) {
756 return $result;
759 /* Open the directory */
760 $handle = @opendir(LOCALE_PATH);
761 /* This can happen if the kit is English-only */
762 if ($handle === false) {
763 return $result;
766 /* Process all files */
767 /** @infection-ignore-all */
768 while (($file = readdir($handle)) !== false) {
769 $path = LOCALE_PATH
770 . '/' . $file
771 . '/LC_MESSAGES/phpmyadmin.mo';
772 if ($file === '.' || $file === '..' || ! @file_exists($path)) {
773 continue;
776 $result[] = $file;
779 /* Close the handle */
780 closedir($handle);
782 return $result;
786 * Returns (cached) list of all available locales
788 * @return string[]
790 public function availableLocales(): array
792 if ($this->availableLocales === []) {
793 if (empty($this->config->get('FilterLanguages'))) {
794 $this->availableLocales = $this->listLocaleDir();
795 } else {
796 $this->availableLocales = preg_grep(
797 '@' . $this->config->get('FilterLanguages') . '@',
798 $this->listLocaleDir(),
803 return $this->availableLocales;
807 * Checks whether there are some languages available
809 public function hasChoice(): bool
811 return count($this->availableLanguages()) > 1;
815 * Returns (cached) list of all available languages
817 * @return array<string, Language>
819 public function availableLanguages(): array
821 if ($this->availableLanguages !== []) {
822 return $this->availableLanguages;
825 foreach ($this->availableLocales() as $lang) {
826 $lang = strtolower($lang);
827 $data = self::$languageData[$lang] ?? [$lang, ucfirst($lang), ucfirst($lang), $lang, ''];
828 $this->availableLanguages[$lang] = new Language(...$data);
831 return $this->availableLanguages;
835 * Returns (cached) list of all available languages sorted
836 * by name
838 * @return Language[] array of Language objects
840 public function sortedLanguages(): array
842 $this->availableLanguages();
843 uasort($this->availableLanguages, static fn (Language $a, Language $b): int => $a->cmp($b));
845 return $this->availableLanguages;
849 * Return Language object for given code
851 * @param string $code Language code
853 * @return Language|false Language object or false on failure
855 public function getLanguage(string $code): Language|false
857 $code = strtolower($code);
858 $langs = $this->availableLanguages();
860 return $langs[$code] ?? false;
864 * Return currently active Language object
866 * @return Language Language object
868 public function getCurrentLanguage(): Language
870 return $this->availableLanguages[strtolower($GLOBALS['lang'])];
874 * Activates language based on configuration, user preferences or
875 * browser
877 public function selectLanguage(): Language
879 // check forced language
880 if (! empty($this->config->get('Lang'))) {
881 $lang = $this->getLanguage($this->config->get('Lang'));
882 if ($lang !== false) {
883 return $lang;
886 $this->langFailedConfig = true;
889 // Don't use REQUEST in following code as it might be confused by cookies
890 // with same name. Check user requested language (POST)
891 if (! empty($_POST['lang'])) {
892 $lang = $this->getLanguage($_POST['lang']);
893 if ($lang !== false) {
894 return $lang;
897 $this->langFailedRequest = true;
900 // check user requested language (GET)
901 if (! empty($_GET['lang'])) {
902 $lang = $this->getLanguage($_GET['lang']);
903 if ($lang !== false) {
904 return $lang;
907 $this->langFailedRequest = true;
910 // check previous set language
911 if (! empty($this->config->getCookie('pma_lang'))) {
912 $lang = $this->getLanguage($this->config->getCookie('pma_lang'));
913 if ($lang !== false) {
914 return $lang;
917 $this->langFailedCookie = true;
920 $langs = $this->availableLanguages();
922 // try to find out user's language by checking its HTTP_ACCEPT_LANGUAGE variable;
923 $acceptedLanguages = Core::getEnv('HTTP_ACCEPT_LANGUAGE');
924 if ($acceptedLanguages !== '') {
925 foreach (explode(',', $acceptedLanguages) as $header) {
926 foreach ($langs as $language) {
927 if ($language->matchesAcceptLanguage($header)) {
928 return $language;
934 // try to find out user's language by checking its HTTP_USER_AGENT variable
935 $userAgent = Core::getEnv('HTTP_USER_AGENT');
936 if ($userAgent !== '') {
937 foreach ($langs as $language) {
938 if ($language->matchesUserAgent($userAgent)) {
939 return $language;
944 // Fallback to English
945 return $langs[$this->config->get('DefaultLang')] ?? $langs['en'];
949 * Displays warnings about invalid languages. This needs to be postponed
950 * to show messages at time when language is initialized.
952 public function showWarnings(): void
954 // now, that we have loaded the language strings we can send the errors
955 if (! $this->langFailedConfig && ! $this->langFailedCookie && ! $this->langFailedRequest) {
956 return;
959 throw new UnsupportedLanguageCode(__('Ignoring unsupported language code.'));