LocaleConfigurator::setLanguage()   A
last analyzed

Complexity

Conditions 5
Paths 8

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5.0073

Importance

Changes 0
Metric Value
cc 5
eloc 14
nc 8
nop 2
dl 0
loc 28
ccs 14
cts 15
cp 0.9333
crap 5.0073
rs 9.4888
c 0
b 0
f 0
1
<?php
2
3
namespace App\Infrastructure\Locale;
4
5
use App\Infrastructure\Settings\Settings;
6
7
final class LocaleConfigurator
8
{
9
    private array $localeSettings;
10
11 203
    public function __construct(Settings $settings)
12
    {
13 203
        $this->localeSettings = $settings->get('locale');
14
    }
15
16
    /**
17
     * Sets the locale and language settings for the application.
18
     *
19
     * @param string|false|null $locale The locale or language code (e.g. 'en_US' or 'en').
20
     * If null or false, the default locale from the settings is used.
21
     * @param string $domain the text domain (default 'messages') for gettext translations
22
     *
23
     * @throws \UnexpectedValueException if the locale is not 'en_US' and no translation file exists for the locale
24
     *
25
     * @return false|string the new locale string, or false on failure
26
     */
27 203
    public function setLanguage(string|false|null $locale, string $domain = 'messages'): bool|string
28
    {
29 203
        $codeset = 'UTF-8';
30 203
        $directory = $this->localeSettings['translations_path'];
31
        // If locale has hyphen instead of underscore, replace it
32 203
        $locale = $locale && str_contains($locale, '-') ? str_replace('-', '_', $locale) : $locale;
33
        // Get an available locale. Either input locale, the locale for another region or default
34 203
        $locale = $this->getAvailableLocale($locale);
35
36
        // Get locale with hyphen as an alternative if server doesn't have the one with underscore (windows)
37 203
        $localeWithHyphen = str_replace('_', '-', $locale);
38
39
        // Set locale information
40 203
        $setLocaleResult = setlocale(LC_ALL, $locale, $localeWithHyphen);
41
        // Check for existing mo file (optional)
42 203
        $file = sprintf('%s/%s/LC_MESSAGES/%s_%s.mo', $directory, $locale, $domain, $locale);
43 203
        if ($locale !== 'en_US' && !file_exists($file)) {
44
            throw new \UnexpectedValueException(sprintf('File not found: %s', $file));
45
        }
46
        // Generate new text domain
47 203
        $textDomain = sprintf('%s_%s', $domain, $locale);
48
        // Set base directory for all locales
49 203
        bindtextdomain($textDomain, $directory);
50
        // Set domain codeset
51 203
        bind_textdomain_codeset($textDomain, $codeset);
52 203
        textdomain($textDomain);
53
54 203
        return $setLocaleResult;
55
    }
56
57
    /**
58
     * Returns the current language code of the set locale with an
59
     * added slash "/" at the end of the string if not empty.
60
     *
61
     * When using this function, a subdirectory for the language has to exist in templates.
62
     *
63
     * @return string language code or empty string if default or language code not found
64
     */
65 6
    public function getLanguageCodeForPath(): string
66
    {
67
        // Get the key of the current locale which has to be an available locale
68 6
        $currentLocale = setlocale(LC_ALL, 0);
69 6
        $langCode = $this->getLanguageCodeFromLocale($currentLocale);
70
71
        // If language code is 'en' return an empty string as the default email templates are in english and not in a
72
        // subdirectory.
73
        // If language code is not empty, add a slash to complete the path it will be inserted into
74 6
        return $langCode === 'en' || $langCode === '' ? '' : $langCode . '/';
75
    }
76
77
    /**
78
     * Returns the locale if available, if not searches for the same
79
     * language with a different region and if not found,
80
     * returns the default locale.
81
     *
82
     * @param false|string|null $locale
83
     *
84
     * @return string
85
     */
86 203
    private function getAvailableLocale(false|string|null $locale): string
87
    {
88 203
        $availableLocales = $this->localeSettings['available'];
89
90
        // If locale is in available locales, return it
91 203
        if ($locale && in_array($locale, $availableLocales, true)) {
92 152
            return $locale;
93
        }
94
95
        // If locale was not found in the available locales, check if the language from another country is available
96 55
        $localesMappedByLanguage = [];
97 55
        foreach ($availableLocales as $availableLocale) {
98 55
            $languageCode = $this->getLanguageCodeFromLocale($availableLocale);
99
            // If the language code is already in the result array, skip it (the first locale of the
100
            // language should be default)
101 55
            if ($languageCode && !array_key_exists($languageCode, $localesMappedByLanguage)) {
102 55
                $localesMappedByLanguage[$languageCode] = $availableLocale;
103
            }
104
        }
105
        // Get the language code from the "target" locale
106 55
        $localeLanguageCode = $this->getLanguageCodeFromLocale($locale);
107
108
        // Take the locale from the same language if available or the default one
109 55
        return $localesMappedByLanguage[$localeLanguageCode] ?? $this->localeSettings['default'] ?? 'en_US';
110
    }
111
112
    /**
113
     * Get the language code part of a locale.
114
     *
115
     * @param string|false|null $locale e.g. 'en_US'
116
     *
117
     * @return string|null e.g. 'en'
118
     */
119 58
    private function getLanguageCodeFromLocale(string|false|null $locale): ?string
120
    {
121
        // If locale has hyphen instead of underscore, replace it
122 58
        if ($locale && str_contains($locale, '-')) {
123
            $locale = str_replace('-', '_', $locale);
124
        }
125
126
        // The language code is the first part of the locale string
127 58
        return $locale ? explode('_', $locale)[0] : null;
128
    }
129
}
130