Completed
Push — master ( ad0a10...39b605 )
by Maurício
16s queued 14s
created

Loader::getTranslator()   A

Complexity

Conditions 6
Paths 14

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 18
c 2
b 0
f 0
dl 0
loc 34
ccs 12
cts 12
cp 1
rs 9.0444
cc 6
nc 14
nop 1
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
    Copyright (c) 2005 Steven Armstrong <sa at c-area dot ch>
7
    Copyright (c) 2009 Danilo Segan <[email protected]>
8
    Copyright (c) 2016 Michal Čihař <[email protected]>
9
10
    This file is part of MoTranslator.
11
12
    This program is free software; you can redistribute it and/or modify
13
    it under the terms of the GNU General Public License as published by
14
    the Free Software Foundation; either version 2 of the License, or
15
    (at your option) any later version.
16
17
    This program is distributed in the hope that it will be useful,
18
    but WITHOUT ANY WARRANTY; without even the implied warranty of
19
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
    GNU General Public License for more details.
21
22
    You should have received a copy of the GNU General Public License along
23
    with this program; if not, write to the Free Software Foundation, Inc.,
24
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25
*/
26
27
namespace PhpMyAdmin\MoTranslator;
28
29
use PhpMyAdmin\MoTranslator\Cache\CacheFactoryInterface;
30
use PhpMyAdmin\MoTranslator\Cache\InMemoryCache;
31
32
use function file_exists;
33
use function getenv;
34
use function in_array;
35
use function preg_match;
36
use function sprintf;
37
38
class Loader
39
{
40
    /**
41
     * Loader instance.
42
     *
43
     * @static
44
     */
45
    private static Loader|null $instance = null;
46
47
    /**
48
     * Factory to return a factory responsible for returning a `CacheInterface`
49
     *
50
     * @static
51
     */
52
    private static CacheFactoryInterface|null $cacheFactory = null;
53
54
    /**
55
     * Default gettext domain to use.
56
     */
57
    private string $defaultDomain = '';
58
59
    /**
60
     * Configured locale.
61
     */
62
    private string $locale = '';
63
64
    /**
65
     * Loaded domains.
66
     *
67
     * @var array<string,array<string,Translator>>
68
     */
69
    private array $domains = [];
70
71
    /**
72
     * Bound paths for domains.
73
     *
74
     * @var array<string,string>
75
     */
76
    private array $paths = ['' => './'];
77
78
    /**
79
     * Returns the singleton Loader object.
80
     *
81
     * @return Loader object
82
     */
83
    public static function getInstance(): Loader
84
    {
85
        if (self::$instance === null) {
86
            self::$instance = new self();
87
        }
88
89
        return self::$instance;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::instance could return the type null which is incompatible with the type-hinted return PhpMyAdmin\MoTranslator\Loader. Consider adding an additional type-check to rule them out.
Loading history...
90 84
    }
91
92 84
    /**
93 14
     * Loads global localization functions.
94
     */
95
    public static function loadFunctions(): void
96 84
    {
97
        require_once __DIR__ . '/functions.php';
98
    }
99
100
    /**
101
     * Figure out all possible locale names and start with the most
102 28
     * specific ones.  I.e. for sr_CS.UTF-8@latin, look through all of
103
     * sr_CS.UTF-8@latin, sr_CS@latin, sr@latin, sr_CS.UTF-8, sr_CS, sr.
104 28
     *
105 4
     * @param string $locale Locale code
106
     *
107
     * @return string[] list of locales to try for any POSIX-style locale specification
108
     */
109
    public static function listLocales(string $locale): array
110
    {
111
        $localeNames = [];
112
113
        if ($locale) {
114
            if (
115
                preg_match(
116 294
                    '/^(?P<lang>[a-z]{2,3})' // language code
117
                    . '(?:_(?P<country>[A-Z]{2}))?' // country code
118 294
                    . '(?:\\.(?P<charset>[-A-Za-z0-9_]+))?' // charset
119
                    . '(?:@(?P<modifier>[-A-Za-z0-9_]+))?$/', // @ modifier
120 294
                    $locale,
121
                    $matches,
122 280
                )
123
            ) {
124
                $lang = $matches['lang'] ?? null;
125
                $country = $matches['country'] ?? null;
126 40
                $charset = $matches['charset'] ?? null;
127 40
                $modifier = $matches['modifier'] ?? null;
128 40
129
                if ($modifier) {
130
                    if ($country) {
131 266
                        if ($charset) {
132 266
                            $localeNames[] = sprintf('%s_%s.%s@%s', $lang, $country, $charset, $modifier);
133 266
                        }
134 266
135
                        $localeNames[] = sprintf('%s_%s@%s', $lang, $country, $modifier);
136 266
                    } elseif ($charset) {
137 70
                        $localeNames[] = sprintf('%s.%s@%s', $lang, $charset, $modifier);
138 28
                    }
139 28
140 4
                    $localeNames[] = sprintf('%s@%s', $lang, $modifier);
141 28
                }
142
143
                if ($country) {
144
                    if ($charset) {
145 28
                        $localeNames[] = sprintf('%s_%s.%s', $lang, $country, $charset);
146 4
                    }
147 28
148
                    $localeNames[] = sprintf('%s_%s', $lang, $country);
149 42
                } elseif ($charset) {
150 14
                    $localeNames[] = sprintf('%s.%s', $lang, $charset);
151 2
                }
152 14
153
                if ($lang !== null) {
154
                    $localeNames[] = $lang;
155
                }
156 70
            }
157 10
158 70
            // If the locale name doesn't match POSIX style, just include it as-is.
159
            if (! in_array($locale, $localeNames)) {
160
                $localeNames[] = $locale;
161
            }
162 266
        }
163 140
164 42
        return $localeNames;
165 6
    }
166 42
167
    /**
168
     * Sets factory responsible for composing a `CacheInterface`
169
     */
170 140
    public static function setCacheFactory(CacheFactoryInterface|null $cacheFactory): void
171 20
    {
172 140
        self::$cacheFactory = $cacheFactory;
173
    }
174 140
175 28
    /**
176 4
     * Returns Translator object for domain or for default domain.
177 28
     *
178
     * @param string $domain Translation domain
179
     */
180
    public function getTranslator(string $domain = ''): Translator
181 266
    {
182
        if ($domain === '') {
183
            $domain = $this->defaultDomain;
184
        }
185 280
186 14
        $this->domains[$this->locale] ??= [];
187
188
        if (! isset($this->domains[$this->locale][$domain])) {
189
            $base = $this->paths[$domain] ?? './';
190 294
191
            $localeNames = self::listLocales($this->locale);
192
193
            $filename = '';
194
            foreach ($localeNames as $locale) {
195
                $filename = $base . '/' . $locale . '/LC_MESSAGES/' . $domain . '.mo';
196 14
                if (file_exists($filename)) {
197
                    break;
198 14
                }
199 2
            }
200
201
            // We don't care about invalid path, we will get fallback
202
            // translator here
203
            $moParser = new MoParser($filename);
204
            if (self::$cacheFactory instanceof CacheFactoryInterface) {
205
                $cache = self::$cacheFactory->getInstance($moParser, $this->locale, $domain);
206 154
            } else {
207
                $cache = new InMemoryCache($moParser);
208 154
            }
209 84
210
            $this->domains[$this->locale][$domain] = new Translator($cache);
211
        }
212 154
213 126
        return $this->domains[$this->locale][$domain];
214
    }
215
216 154
    /**
217 140
     * Sets the path for a domain.
218 112
     *
219
     * @param string $domain Domain name
220 28
     * @param string $path   Path where to find locales
221
     */
222
    public function bindtextdomain(string $domain, string $path): void
223 140
    {
224
        $this->paths[$domain] = $path;
225 140
    }
226 140
227 140
    /**
228 140
     * Sets the default domain.
229 98
     *
230
     * @param string $domain Domain name
231
     */
232
    public function textdomain(string $domain): void
233
    {
234
        $this->defaultDomain = $domain;
235 140
    }
236 140
237 14
    /**
238
     * Sets a requested locale.
239 126
     *
240
     * @param string $locale Locale name
241
     *
242 140
     * @return string Set or current locale
243
     */
244
    public function setlocale(string $locale): string
245 154
    {
246
        if (! empty($locale)) {
247
            $this->locale = $locale;
248
        }
249
250
        return $this->locale;
251
    }
252
253
    /**
254 154
     * Detects currently configured locale.
255
     *
256 154
     * It checks:
257 22
     *
258
     * - global lang variable
259
     * - environment for LC_ALL, LC_MESSAGES and LANG
260
     *
261
     * @return string with locale name
262
     */
263
    public function detectlocale(): string
264 140
    {
265
        if (isset($GLOBALS['lang'])) {
266 140
            return $GLOBALS['lang'];
267 20
        }
268
269
        $locale = getenv('LC_ALL');
270
        if ($locale !== false) {
271
            return $locale;
272
        }
273
274
        $locale = getenv('LC_MESSAGES');
275
        if ($locale !== false) {
276 154
            return $locale;
277
        }
278 154
279 154
        $locale = getenv('LANG');
280
        if ($locale !== false) {
281
            return $locale;
282 154
        }
283
284
        return 'en';
285
    }
286
}
287