3 declare(strict_types
=1);
7 use PhpMyAdmin\Exceptions\UnsupportedLanguageCode
;
10 use function closedir
;
13 use function file_exists
;
16 use function preg_grep
;
18 use function strtolower
;
23 * Language selection manager
28 * Definition data for languages
30 * Each member contains:
32 * - English language name
33 * - Native language name
34 * - Match regular expression
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 = [
58 'العربية',
59 'ar(?![-_]ly)([-_][[:alpha:]]{2,3})?|arabic',
66 'ar[_-]ly|arabic (libya)|libian arabic',
86 'Беларуская',
94 'be[-_]lat|be@latin|belarusian latin',
107 'Български',
177 'Ελληνικά',
185 'en(?![-_]gb)([-_][[:alpha:]]{2,3})?|english',
190 'English (United Kingdom)',
192 'en[_-]gb|english (United Kingdom)',
199 'enm|english (middle)',
233 'فارسی',
282 'עברית',
289 'हिन्दी',
345 '日本語',
352 '한국어',
359 'ქართული',
450 'Монгол',
514 'pt(?![-_]br)([-_][[:alpha:]]{2,3})?|portuguese',
519 'Portuguese (Brazil)',
520 'Português (Brasil)',
521 'pt[-_]br|portuguese (brazil)',
526 'Réunion Creole',
528 'rcf|creole (reunion)',
541 'Русский',
548 'සිංහල',
562 'Slovenščina',
577 'sr[-_]lat|sr@latin|serbian latin',
583 'Српски',
611 'ภาษาไทย',
625 'Türkçe',
638 'Central Atlas Tamazight',
640 'tzm|central atlas tamazight',
653 'Українська',
668 'uz[-_]lat|uz@latin|uzbek-latin',
674 'Ўзбекча',
675 'uz[-_]cyr|uz@cyrillic|uzbek-cyrillic',
694 'Chinese traditional',
696 'zh[-_](tw|hk)|chinese traditional',
699 // only TW and HK use traditional Chinese while others (CN, SG, MY)
700 // use simplified Chinese
703 'Chinese simplified',
705 'zh(?![-_](tw|hk))([-_][[:alpha:]]{2,3})?|chinese simplified',
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
750 public function listLocaleDir(): array
754 /* Check for existing directory */
755 if (! is_dir(LOCALE_PATH
)) {
759 /* Open the directory */
760 $handle = @opendir
(LOCALE_PATH
);
761 /* This can happen if the kit is English-only */
762 if ($handle === false) {
766 /* Process all files */
767 /** @infection-ignore-all */
768 while (($file = readdir($handle)) !== false) {
771 . '/LC_MESSAGES/phpmyadmin.mo';
772 if ($file === '.' ||
$file === '..' ||
! @file_exists
($path)) {
779 /* Close the handle */
786 * Returns (cached) list of all available locales
790 public function availableLocales(): array
792 if ($this->availableLocales
=== []) {
793 if (empty($this->config
->get('FilterLanguages'))) {
794 $this->availableLocales
= $this->listLocaleDir();
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
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
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) {
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) {
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) {
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) {
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)) {
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)) {
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
) {
959 throw new UnsupportedLanguageCode(__('Ignoring unsupported language code.'));