Completed
Push — master ( e15776...fcbc68 )
by
unknown
14:17
created

Locales::getLocaleDependencies()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 2
nop 1
dl 0
loc 14
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Core\Localization;
17
18
use TYPO3\CMS\Core\Log\LogManager;
19
use TYPO3\CMS\Core\SingletonInterface;
20
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
21
use TYPO3\CMS\Core\Utility\GeneralUtility;
22
23
/**
24
 * Locales. Used to define TYPO3- system languages
25
 * When adding new keys, remember to:
26
 * - Update 'setup' extension labels (sysext/setup/Resources/Private/Language/locallang.xlf)
27
 * That's it!
28
 */
29
class Locales implements SingletonInterface
30
{
31
    /**
32
     * Supported TYPO3 languages with locales
33
     *
34
     * @var array
35
     */
36
    protected $languages = [
37
        'default' => 'English',
38
        'af' => 'Afrikaans',
39
        'ar' => 'Arabic',
40
        'bs' => 'Bosnian',
41
        'bg' => 'Bulgarian',
42
        'ca' => 'Catalan',
43
        'ch' => 'Chinese (Simple)',
44
        'cs' => 'Czech',
45
        'da' => 'Danish',
46
        'de' => 'German',
47
        'el' => 'Greek',
48
        'eo' => 'Esperanto',
49
        'es' => 'Spanish',
50
        'et' => 'Estonian',
51
        'eu' => 'Basque',
52
        'fa' => 'Persian',
53
        'fi' => 'Finnish',
54
        'fo' => 'Faroese',
55
        'fr' => 'French',
56
        'fr_CA' => 'French (Canada)',
57
        'gl' => 'Galician',
58
        'he' => 'Hebrew',
59
        'hi' => 'Hindi',
60
        'hr' => 'Croatian',
61
        'hu' => 'Hungarian',
62
        'is' => 'Icelandic',
63
        'it' => 'Italian',
64
        'ja' => 'Japanese',
65
        'ka' => 'Georgian',
66
        'kl' => 'Greenlandic',
67
        'km' => 'Khmer',
68
        'ko' => 'Korean',
69
        'lt' => 'Lithuanian',
70
        'lv' => 'Latvian',
71
        'mi' => 'Maori',
72
        'mk' => 'Macedonian',
73
        'ms' => 'Malay',
74
        'nl' => 'Dutch',
75
        'no' => 'Norwegian',
76
        'pl' => 'Polish',
77
        'pt' => 'Portuguese',
78
        'pt_BR' => 'Brazilian Portuguese',
79
        'ro' => 'Romanian',
80
        'ru' => 'Russian',
81
        'rw' => 'Kinyarwanda',
82
        'sk' => 'Slovak',
83
        'sl' => 'Slovenian',
84
        'sq' => 'Albanian',
85
        'sr' => 'Serbian',
86
        'sv' => 'Swedish',
87
        'th' => 'Thai',
88
        'tr' => 'Turkish',
89
        'uk' => 'Ukrainian',
90
        'vi' => 'Vietnamese',
91
        'zh' => 'Chinese (Trad)'
92
    ];
93
94
    /**
95
     * Reversed mapping for backward compatibility codes
96
     *
97
     * @var array
98
     */
99
    protected $isoReverseMapping = [
100
        'bs' => 'ba', // Bosnian
101
        'cs' => 'cz', // Czech
102
        'da' => 'dk', // Danish
103
        'el' => 'gr', // Greek
104
        'fr_CA' => 'qc', // French (Canada)
105
        'gl' => 'ga', // Galician
106
        'ja' => 'jp', // Japanese
107
        'ka' => 'ge', // Georgian
108
        'kl' => 'gl', // Greenlandic
109
        'ko' => 'kr', // Korean
110
        'ms' => 'my', // Malay
111
        'pt_BR' => 'br', // Portuguese (Brazil)
112
        'sl' => 'si', // Slovenian
113
        'sv' => 'se', // Swedish
114
        'uk' => 'ua', // Ukrainian
115
        'vi' => 'vn', // Vietnamese
116
        'zh' => 'hk', // Chinese (China)
117
        'zh_CN' => 'ch', // Chinese (Simplified)
118
        'zh_HK' => 'hk', // Chinese (Simplified Hong Kong)
119
        'zh_Hans_CN' => 'ch' // Chinese (Simplified Han)
120
    ];
121
122
    /**
123
     * Dependencies for locales
124
     * This is a reverse mapping for the built-in languages within $this->languages that contain 5-letter codes.
125
     *
126
     * @var array
127
     */
128
    protected $localeDependencies = [
129
        'pt_BR' => ['pt'],
130
        'fr_CA' => ['fr']
131
    ];
132
133
    public function __construct()
134
    {
135
        // Allow user-defined locales
136
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SYS']['localization']['locales']['user'] ?? [] as $locale => $name) {
137
            if (!isset($this->languages[$locale])) {
138
                $this->languages[$locale] = $name;
139
            }
140
            // Initializes the locale dependencies with TYPO3 supported locales
141
            if (strlen($locale) === 5) {
142
                $this->localeDependencies[$locale] = [substr($locale, 0, 2)];
143
            }
144
        }
145
        // Merge user-provided locale dependencies
146
        if (is_array($GLOBALS['TYPO3_CONF_VARS']['SYS']['localization']['locales']['dependencies'] ?? null)) {
147
            $this->localeDependencies = array_replace_recursive(
148
                $this->localeDependencies,
149
                $GLOBALS['TYPO3_CONF_VARS']['SYS']['localization']['locales']['dependencies']
150
            );
151
        }
152
    }
153
154
    /**
155
     * Returns the locales.
156
     *
157
     * @return array
158
     */
159
    public function getLocales()
160
    {
161
        return array_keys($this->languages);
162
    }
163
164
    /**
165
     * Returns the supported languages indexed by their corresponding locale.
166
     *
167
     * @return array
168
     */
169
    public function getLanguages()
170
    {
171
        return $this->languages;
172
    }
173
174
    /**
175
     * Returns the mapping between TYPO3 (old) language codes and ISO codes.
176
     *
177
     * @return array
178
     */
179
    public function getIsoMapping()
180
    {
181
        return array_flip($this->isoReverseMapping);
182
    }
183
184
    /**
185
     * Returns the dependencies of a given locale, if any.
186
     *
187
     * @param string $locale
188
     * @return array
189
     */
190
    public function getLocaleDependencies($locale)
191
    {
192
        $dependencies = [];
193
        if (isset($this->localeDependencies[$locale])) {
194
            $dependencies = $this->localeDependencies[$locale];
195
            // Search for dependencies recursively
196
            $localeDependencies = $dependencies;
197
            foreach ($localeDependencies as $dependency) {
198
                if (isset($this->localeDependencies[$dependency])) {
199
                    $dependencies = array_merge($dependencies, $this->getLocaleDependencies($dependency));
200
                }
201
            }
202
        }
203
        return $dependencies;
204
    }
205
206
    /**
207
     * Converts the language codes that we get from the client (usually HTTP_ACCEPT_LANGUAGE)
208
     * into a TYPO3-readable language code
209
     *
210
     * @param string $languageCodesList List of language codes. something like 'de,en-us;q=0.9,de-de;q=0.7,es-cl;q=0.6,en;q=0.4,es;q=0.3,zh;q=0.1'
211
     * @return string A preferred language that TYPO3 supports, or "default" if none found
212
     */
213
    public function getPreferredClientLanguage($languageCodesList)
214
    {
215
        $allLanguageCodesFromLocales = ['en' => 'default'];
216
        foreach ($this->isoReverseMapping as $isoLang => $typo3Lang) {
217
            $isoLang = str_replace('_', '-', $isoLang);
218
            $allLanguageCodesFromLocales[$isoLang] = $typo3Lang;
219
        }
220
        foreach ($this->getLocales() as $locale) {
221
            $locale = str_replace('_', '-', $locale);
222
            $allLanguageCodesFromLocales[$locale] = $locale;
223
        }
224
        $selectedLanguage = 'default';
225
        $preferredLanguages = GeneralUtility::trimExplode(',', $languageCodesList);
226
        // Order the preferred languages after they key
227
        $sortedPreferredLanguages = [];
228
        foreach ($preferredLanguages as $preferredLanguage) {
229
            $quality = 1.0;
230
            if (strpos($preferredLanguage, ';q=') !== false) {
231
                [$preferredLanguage, $quality] = explode(';q=', $preferredLanguage);
232
            }
233
            $sortedPreferredLanguages[$preferredLanguage] = $quality;
234
        }
235
        // Loop through the languages, with the highest priority first
236
        arsort($sortedPreferredLanguages, SORT_NUMERIC);
237
        foreach ($sortedPreferredLanguages as $preferredLanguage => $quality) {
238
            if (isset($allLanguageCodesFromLocales[$preferredLanguage])) {
239
                $selectedLanguage = $allLanguageCodesFromLocales[$preferredLanguage];
240
                break;
241
            }
242
            // Strip the country code from the end
243
            [$preferredLanguage, ] = explode('-', $preferredLanguage);
244
            if (isset($allLanguageCodesFromLocales[$preferredLanguage])) {
245
                $selectedLanguage = $allLanguageCodesFromLocales[$preferredLanguage];
246
                break;
247
            }
248
        }
249
        if (!$selectedLanguage || $selectedLanguage === 'en') {
250
            $selectedLanguage = 'default';
251
        }
252
        return $selectedLanguage;
253
    }
254
255
    /**
256
     * Setting locale based on a SiteLanguage's defined locale.
257
     * Used for frontend rendering, previously set within TSFE->settingLocale
258
     *
259
     * @param SiteLanguage $siteLanguage
260
     * @return bool whether the locale was found on the system (and could be set properly) or not
261
     */
262
    public static function setSystemLocaleFromSiteLanguage(SiteLanguage $siteLanguage): bool
263
    {
264
        $locale = $siteLanguage->getLocale();
265
        // No locale was given, so return false;
266
        if (!$locale) {
267
            return false;
268
        }
269
        $availableLocales = GeneralUtility::trimExplode(',', $locale, true);
270
        // If LC_NUMERIC is set e.g. to 'de_DE' PHP parses float values locale-aware resulting in strings with comma
271
        // as decimal point which causes problems with value conversions - so we set all locale types except LC_NUMERIC
272
        // @see https://bugs.php.net/bug.php?id=53711
273
        $locale = setlocale(LC_COLLATE, ...$availableLocales);
274
        if ($locale) {
275
            // As str_* methods are locale aware and turkish has no upper case I
276
            // Class autoloading and other checks depending on case changing break with turkish locale LC_CTYPE
277
            // @see http://bugs.php.net/bug.php?id=35050
278
            if (strpos($locale, 'tr') !== 0) {
279
                setlocale(LC_CTYPE, ...$availableLocales);
280
            }
281
            setlocale(LC_MONETARY, ...$availableLocales);
282
            setlocale(LC_TIME, ...$availableLocales);
283
        } else {
284
            GeneralUtility::makeInstance(LogManager::class)
285
                ->getLogger(__CLASS__)
286
                ->error('Locale "' . htmlspecialchars($siteLanguage->getLocale()) . '" not found.');
287
            return false;
288
        }
289
        return true;
290
    }
291
}
292