1
<?php
2

3
/**
4
 * This file is part of the Carbon package.
5
 *
6
 * (c) Brian Nesbitt <brian@nesbot.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
namespace Carbon;
12

13
use Closure;
14
use ReflectionException;
15
use ReflectionFunction;
16
use Symfony\Component\Translation;
17

18
class Translator extends Translation\Translator
19
{
20
    /**
21
     * Translator singletons for each language.
22
     *
23
     * @var array
24
     */
25
    protected static $singletons = [];
26

27
    /**
28
     * List of custom localized messages.
29
     *
30
     * @var array
31
     */
32
    protected $messages = [];
33

34
    /**
35
     * List of custom directories that contain translation files.
36
     *
37
     * @var string[]
38
     */
39
    protected $directories = [];
40

41
    /**
42
     * Set to true while constructing.
43
     *
44
     * @var bool
45
     */
46
    protected $initializing = false;
47

48
    /**
49
     * List of locales aliases.
50
     *
51
     * @var string[]
52
     */
53
    protected $aliases = [
54
        'me' => 'sr_Latn_ME',
55
        'scr' => 'sh',
56
    ];
57

58
    /**
59
     * Return a singleton instance of Translator.
60
     *
61
     * @param string|null $locale optional initial locale ("en" - english by default)
62
     *
63
     * @return static
64
     */
65 1
    public static function get($locale = null)
66
    {
67 1
        $locale = $locale ?: 'en';
68

69 1
        if (!isset(static::$singletons[$locale])) {
70 1
            static::$singletons[$locale] = new static($locale ?: 'en');
71
        }
72

73 1
        return static::$singletons[$locale];
74
    }
75

76 1
    public function __construct($locale, Translation\Formatter\MessageFormatterInterface $formatter = null, $cacheDir = null, $debug = false)
77
    {
78 1
        $this->initializing = true;
79 1
        $this->directories = [__DIR__.'/Lang'];
80 1
        $this->addLoader('array', new Translation\Loader\ArrayLoader());
81 1
        parent::__construct($locale, $formatter, $cacheDir, $debug);
82 1
        $this->initializing = false;
83
    }
84

85
    /**
86
     * Returns the list of directories translation files are searched in.
87
     *
88
     * @return array
89
     */
90 1
    public function getDirectories(): array
91
    {
92 1
        return $this->directories;
93
    }
94

95
    /**
96
     * Set list of directories translation files are searched in.
97
     *
98
     * @param array $directories new directories list
99
     *
100
     * @return $this
101
     */
102 1
    public function setDirectories(array $directories)
103
    {
104 1
        $this->directories = $directories;
105

106 1
        return $this;
107
    }
108

109
    /**
110
     * Add a directory to the list translation files are searched in.
111
     *
112
     * @param string $directory new directory
113
     *
114
     * @return $this
115
     */
116 1
    public function addDirectory(string $directory)
117
    {
118 1
        $this->directories[] = $directory;
119

120 1
        return $this;
121
    }
122

123
    /**
124
     * Remove a directory from the list translation files are searched in.
125
     *
126
     * @param string $directory directory path
127
     *
128
     * @return $this
129
     */
130 1
    public function removeDirectory(string $directory)
131
    {
132 1
        $search = rtrim(strtr($directory, '\\', '/'), '/');
133

134
        return $this->setDirectories(array_filter($this->getDirectories(), function ($item) use ($search) {
135 1
            return rtrim(strtr($item, '\\', '/'), '/') !== $search;
136 1
        }));
137
    }
138

139
    /**
140
     * Returns the translation.
141
     *
142
     * @param string $id
143
     * @param array  $parameters
144
     * @param string $domain
145
     * @param string $locale
146
     *
147
     * @return string
148
     */
149 1
    public function trans($id, array $parameters = [], $domain = null, $locale = null)
150
    {
151 1
        if (null === $domain) {
152 1
            $domain = 'messages';
153
        }
154

155 1
        $format = $this->getCatalogue($locale)->get((string) $id, $domain);
156

157 1
        if ($format instanceof Closure) {
158
            // @codeCoverageIgnoreStart
159
            try {
160
                $count = (new ReflectionFunction($format))->getNumberOfRequiredParameters();
161
            } catch (ReflectionException $exception) {
162
                $count = 0;
163
            }
164
            // @codeCoverageIgnoreEnd
165

166 1
            return $format(
167 1
                ...array_values($parameters),
168 1
                ...array_fill(0, max(0, $count - \count($parameters)), null)
169
            );
170
        }
171

172 1
        return parent::trans($id, $parameters, $domain, $locale);
173
    }
174

175
    /**
176
     * Reset messages of a locale (all locale if no locale passed).
177
     * Remove custom messages and reload initial messages from matching
178
     * file in Lang directory.
179
     *
180
     * @param string|null $locale
181
     *
182
     * @return bool
183
     */
184 1
    public function resetMessages($locale = null)
185
    {
186 1
        if ($locale === null) {
187 1
            $this->messages = [];
188

189 1
            return true;
190
        }
191

192 1
        foreach ($this->getDirectories() as $directory) {
193 1
            $data = @include sprintf('%s/%s.php', rtrim($directory, '\\/'), $locale);
194

195 1
            if ($data !== false) {
196 1
                $this->messages[$locale] = $data;
197 1
                $this->addResource('array', $this->messages[$locale], $locale);
198

199 1
                return true;
200
            }
201
        }
202

203 1
        return false;
204
    }
205

206
    /**
207
     * Returns the list of files matching a given locale prefix (or all if empty).
208
     *
209
     * @param string $prefix prefix required to filter result
210
     *
211
     * @return array
212
     */
213 1
    public function getLocalesFiles($prefix = '')
214
    {
215 1
        $files = [];
216

217 1
        foreach ($this->getDirectories() as $directory) {
218 1
            $directory = rtrim($directory, '\\/');
219

220 1
            foreach (glob("$directory/$prefix*.php") as $file) {
221 1
                $files[] = $file;
222
            }
223
        }
224

225 1
        return array_unique($files);
226
    }
227

228
    /**
229
     * Returns the list of internally available locales and already loaded custom locales.
230
     * (It will ignore custom translator dynamic loading.)
231
     *
232
     * @param string $prefix prefix required to filter result
233
     *
234
     * @return array
235
     */
236 1
    public function getAvailableLocales($prefix = '')
237
    {
238 1
        $locales = [];
239 1
        foreach ($this->getLocalesFiles($prefix) as $file) {
240 1
            $locales[] = substr($file, strrpos($file, '/') + 1, -4);
241
        }
242

243 1
        return array_unique(array_merge($locales, array_keys($this->messages)));
244
    }
245

246
    /**
247
     * Init messages language from matching file in Lang directory.
248
     *
249
     * @param string $locale
250
     *
251
     * @return bool
252
     */
253 1
    protected function loadMessagesFromFile($locale)
254
    {
255 1
        if (isset($this->messages[$locale])) {
256 1
            return true;
257
        }
258

259 1
        return $this->resetMessages($locale);
260
    }
261

262
    /**
263
     * Set messages of a locale and take file first if present.
264
     *
265
     * @param string $locale
266
     * @param array  $messages
267
     *
268
     * @return $this
269
     */
270 1
    public function setMessages($locale, $messages)
271
    {
272 1
        $this->loadMessagesFromFile($locale);
273 1
        $this->addResource('array', $messages, $locale);
274 1
        $this->messages[$locale] = array_merge(
275 1
            isset($this->messages[$locale]) ? $this->messages[$locale] : [],
276
            $messages
277
        );
278

279 1
        return $this;
280
    }
281

282
    /**
283
     * Set messages of the current locale and take file first if present.
284
     *
285
     * @param array $messages
286
     *
287
     * @return $this
288
     */
289 1
    public function setTranslations($messages)
290
    {
291 1
        return $this->setMessages($this->getLocale(), $messages);
292
    }
293

294
    /**
295
     * Get messages of a locale, if none given, return all the
296
     * languages.
297
     *
298
     * @param string|null $locale
299
     *
300
     * @return array
301
     */
302 1
    public function getMessages($locale = null)
303
    {
304 1
        return $locale === null ? $this->messages : $this->messages[$locale];
305
    }
306

307
    /**
308
     * Set the current translator locale and indicate if the source locale file exists
309
     *
310
     * @param string $locale locale ex. en
311
     *
312
     * @return bool
313
     */
314 1
    public function setLocale($locale)
315
    {
316
        $locale = preg_replace_callback('/[-_]([a-z]{2,})/', function ($matches) {
317
            // _2-letters or YUE is a region, _3+-letters is a variant
318 1
            $upper = strtoupper($matches[1]);
319

320 1
            if ($upper === 'YUE' || $upper === 'ISO' || \strlen($upper) < 3) {
321 1
                return "_$upper";
322
            }
323

324 1
            return '_'.ucfirst($matches[1]);
325 1
        }, strtolower($locale));
326

327 1
        $previousLocale = $this->getLocale();
328

329 1
        if ($previousLocale === $locale) {
330 1
            return true;
331
        }
332

333 1
        unset(static::$singletons[$previousLocale]);
334

335 1
        if ($locale === 'auto') {
336 1
            $completeLocale = setlocale(LC_TIME, '0');
337 1
            $locale = preg_replace('/^([^_.-]+).*$/', '$1', $completeLocale);
338 1
            $locales = $this->getAvailableLocales($locale);
339

340 1
            $completeLocaleChunks = preg_split('/[_.-]+/', $completeLocale);
341

342
            $getScore = function ($language) use ($completeLocaleChunks) {
343 1
                return static::compareChunkLists($completeLocaleChunks, preg_split('/[_.-]+/', $language));
344 1
            };
345

346
            usort($locales, function ($first, $second) use ($getScore) {
347 1
                return $getScore($second) <=> $getScore($first);
348 1
            });
349

350 1
            $locale = $locales[0];
351
        }
352

353 1
        if (isset($this->aliases[$locale])) {
354 1
            $locale = $this->aliases[$locale];
355
        }
356

357
        // If subtag (ex: en_CA) first load the macro (ex: en) to have a fallback
358 1
        if (strpos($locale, '_') !== false &&
359 1
            $this->loadMessagesFromFile($macroLocale = preg_replace('/^([^_]+).*$/', '$1', $locale))
360
        ) {
361 1
            parent::setLocale($macroLocale);
362
        }
363

364 1
        if ($this->loadMessagesFromFile($locale) || $this->initializing) {
365 1
            parent::setLocale($locale);
366

367 1
            return true;
368
        }
369

370 1
        return false;
371
    }
372

373
    /**
374
     * Show locale on var_dump().
375
     *
376
     * @return array
377
     */
378 1
    public function __debugInfo()
379
    {
380
        return [
381 1
            'locale' => $this->getLocale(),
382
        ];
383
    }
384

385 1
    private static function compareChunkLists($referenceChunks, $chunks)
386
    {
387 1
        $score = 0;
388

389 1
        foreach ($referenceChunks as $index => $chunk) {
390 1
            if (!isset($chunks[$index])) {
391 1
                $score++;
392

393 1
                continue;
394
            }
395

396 1
            if (strtolower($chunks[$index]) === strtolower($chunk)) {
397 1
                $score += 10;
398
            }
399
        }
400

401 1
        return $score;
402
    }
403
}

Read our documentation on viewing source code .

Loading