Passed
Pull Request — master (#1)
by Guillaume
04:03
created

Localization   F

Complexity

Total Complexity 127

Size/Duplication

Total Lines 771
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 231
dl 0
loc 771
rs 2
c 0
b 0
f 0
wmc 127

How to fix   Complexity   

Complex Class

Complex classes like Localization often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Localization, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is part of the Carbon package.
5
 *
6
 * (c) Brian Nesbitt <[email protected]>
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\Traits;
12
13
use Carbon\CarbonInterface;
14
use Carbon\Exceptions\InvalidTypeException;
15
use Carbon\Exceptions\NotLocaleAwareException;
16
use Carbon\Language;
17
use Carbon\Translator;
18
use Closure;
19
use Symfony\Component\Translation\TranslatorBagInterface;
20
use Symfony\Component\Translation\TranslatorInterface;
21
use Symfony\Contracts\Translation\LocaleAwareInterface;
22
use Symfony\Contracts\Translation\TranslatorInterface as ContractsTranslatorInterface;
23
24
if (!interface_exists('Symfony\\Component\\Translation\\TranslatorInterface')) {
25
    class_alias(
26
        'Symfony\\Contracts\\Translation\\TranslatorInterface',
27
        'Symfony\\Component\\Translation\\TranslatorInterface'
28
    );
29
}
30
31
/**
32
 * Trait Localization.
33
 *
34
 * Embed default and locale translators and translation base methods.
35
 */
36
trait Localization
37
{
38
    /**
39
     * Default translator.
40
     *
41
     * @var \Symfony\Component\Translation\TranslatorInterface
42
     */
43
    protected static $translator;
44
45
    /**
46
     * Specific translator of the current instance.
47
     *
48
     * @var \Symfony\Component\Translation\TranslatorInterface
49
     */
50
    protected $localTranslator;
51
52
    /**
53
     * Options for diffForHumans().
54
     *
55
     * @var int
56
     */
57
    protected static $humanDiffOptions = CarbonInterface::NO_ZERO_DIFF;
58
59
    /**
60
     * @deprecated To avoid conflict between different third-party libraries, static setters should not be used.
61
     *             You should rather use the ->settings() method.
62
     * @see settings
63
     *
64
     * @param int $humanDiffOptions
65
     */
66
    public static function setHumanDiffOptions($humanDiffOptions)
67
    {
68
        static::$humanDiffOptions = $humanDiffOptions;
69
    }
70
71
    /**
72
     * @deprecated To avoid conflict between different third-party libraries, static setters should not be used.
73
     *             You should rather use the ->settings() method.
74
     * @see settings
75
     *
76
     * @param int $humanDiffOption
77
     */
78
    public static function enableHumanDiffOption($humanDiffOption)
79
    {
80
        static::$humanDiffOptions = static::getHumanDiffOptions() | $humanDiffOption;
81
    }
82
83
    /**
84
     * @deprecated To avoid conflict between different third-party libraries, static setters should not be used.
85
     *             You should rather use the ->settings() method.
86
     * @see settings
87
     *
88
     * @param int $humanDiffOption
89
     */
90
    public static function disableHumanDiffOption($humanDiffOption)
91
    {
92
        static::$humanDiffOptions = static::getHumanDiffOptions() & ~$humanDiffOption;
93
    }
94
95
    /**
96
     * Return default humanDiff() options (merged flags as integer).
97
     *
98
     * @return int
99
     */
100
    public static function getHumanDiffOptions()
101
    {
102
        return static::$humanDiffOptions;
103
    }
104
105
    /**
106
     * Get the default translator instance in use.
107
     *
108
     * @return \Symfony\Component\Translation\TranslatorInterface
109
     */
110
    public static function getTranslator()
111
    {
112
        return static::translator();
113
    }
114
115
    /**
116
     * Set the default translator instance to use.
117
     *
118
     * @param \Symfony\Component\Translation\TranslatorInterface $translator
119
     *
120
     * @return void
121
     */
122
    public static function setTranslator(TranslatorInterface $translator)
123
    {
124
        static::$translator = $translator;
125
    }
126
127
    /**
128
     * Return true if the current instance has its own translator.
129
     *
130
     * @return bool
131
     */
132
    public function hasLocalTranslator()
133
    {
134
        return isset($this->localTranslator);
135
    }
136
137
    /**
138
     * Get the translator of the current instance or the default if none set.
139
     *
140
     * @return \Symfony\Component\Translation\TranslatorInterface
141
     */
142
    public function getLocalTranslator()
143
    {
144
        return $this->localTranslator ?: static::translator();
145
    }
146
147
    /**
148
     * Set the translator for the current instance.
149
     *
150
     * @param \Symfony\Component\Translation\TranslatorInterface $translator
151
     *
152
     * @return $this
153
     */
154
    public function setLocalTranslator(TranslatorInterface $translator)
155
    {
156
        $this->localTranslator = $translator;
157
158
        return $this;
159
    }
160
161
    /**
162
     * Returns raw translation message for a given key.
163
     *
164
     * @param \Symfony\Component\Translation\TranslatorInterface $translator the translator to use
165
     * @param string                                             $key        key to find
166
     * @param string|null                                        $locale     current locale used if null
167
     * @param string|null                                        $default    default value if translation returns the key
168
     *
169
     * @return string
170
     */
171
    public static function getTranslationMessageWith($translator, string $key, string $locale = null, string $default = null)
172
    {
173
        if (!($translator instanceof TranslatorBagInterface && $translator instanceof TranslatorInterface)) {
174
            throw new InvalidTypeException(
175
                'Translator does not implement '.TranslatorInterface::class.' and '.TranslatorBagInterface::class.'. '.
176
                (is_object($translator) ? get_class($translator) : gettype($translator)).' has been given.'
177
            );
178
        }
179
180
        if (!$locale && $translator instanceof LocaleAwareInterface) {
181
            $locale = $translator->getLocale();
182
        }
183
184
        $result = $translator->getCatalogue($locale)->get($key);
185
186
        return $result === $key ? $default : $result;
187
    }
188
189
    /**
190
     * Returns raw translation message for a given key.
191
     *
192
     * @param string                                             $key        key to find
193
     * @param string|null                                        $locale     current locale used if null
194
     * @param string|null                                        $default    default value if translation returns the key
195
     * @param \Symfony\Component\Translation\TranslatorInterface $translator an optional translator to use
196
     *
197
     * @return string
198
     */
199
    public function getTranslationMessage(string $key, string $locale = null, string $default = null, $translator = null)
200
    {
201
        return static::getTranslationMessageWith($translator ?: $this->getLocalTranslator(), $key, $locale, $default);
202
    }
203
204
    /**
205
     * Translate using translation string or callback available.
206
     *
207
     * @param \Symfony\Component\Translation\TranslatorInterface $translator
208
     * @param string                                             $key
209
     * @param array                                              $parameters
210
     * @param null                                               $number
211
     *
212
     * @return string
213
     */
214
    public static function translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null): string
215
    {
216
        $message = static::getTranslationMessageWith($translator, $key, null, $key);
217
        if ($message instanceof Closure) {
218
            return (string) $message(...array_values($parameters));
219
        }
220
221
        if ($number !== null) {
222
            $parameters['%count%'] = $number;
223
        }
224
        if (isset($parameters['%count%'])) {
225
            $parameters[':count'] = $parameters['%count%'];
226
        }
227
228
        // @codeCoverageIgnoreStart
229
        $choice = $translator instanceof ContractsTranslatorInterface
230
            ? $translator->trans($key, $parameters)
231
            : $translator->transChoice($key, $number, $parameters);
232
        // @codeCoverageIgnoreEnd
233
234
        return (string) $choice;
235
    }
236
237
    /**
238
     * Translate using translation string or callback available.
239
     *
240
     * @param string                                             $key
241
     * @param array                                              $parameters
242
     * @param null                                               $number
243
     * @param \Symfony\Component\Translation\TranslatorInterface $translator
244
     *
245
     * @return string
246
     */
247
    public function translate(string $key, array $parameters = [], $number = null, TranslatorInterface $translator = null, bool $altNumbers = false): string
248
    {
249
        $translation = static::translateWith($translator ?: $this->getLocalTranslator(), $key, $parameters, $number);
250
251
        if ($number !== null && $altNumbers) {
252
            return str_replace($number, $this->translateNumber($number), $translation);
253
        }
254
255
        return $translation;
256
    }
257
258
    /**
259
     * Returns the alternative number for a given integer if available in the current locale.
260
     *
261
     * @param int $number
262
     *
263
     * @return string
264
     */
265
    public function translateNumber(int $number): string
266
    {
267
        $translateKey = "alt_numbers.$number";
268
        $symbol = $this->translate($translateKey);
269
270
        if ($symbol !== $translateKey) {
271
            return $symbol;
272
        }
273
274
        if ($number > 99 && $this->translate('alt_numbers.99') !== 'alt_numbers.99') {
275
            $start = '';
276
            foreach ([10000, 1000, 100] as $exp) {
277
                $key = "alt_numbers_pow.$exp";
278
                if ($number >= $exp && $number < $exp * 10 && ($pow = $this->translate($key)) !== $key) {
279
                    $unit = floor($number / $exp);
280
                    $number -= $unit * $exp;
281
                    $start .= ($unit > 1 ? $this->translate("alt_numbers.$unit") : '').$pow;
282
                }
283
            }
284
            $result = '';
285
            while ($number) {
286
                $chunk = $number % 100;
287
                $result = $this->translate("alt_numbers.$chunk").$result;
288
                $number = floor($number / 100);
289
            }
290
291
            return "$start$result";
292
        }
293
294
        if ($number > 9 && $this->translate('alt_numbers.9') !== 'alt_numbers.9') {
295
            $result = '';
296
            while ($number) {
297
                $chunk = $number % 10;
298
                $result = $this->translate("alt_numbers.$chunk").$result;
299
                $number = floor($number / 10);
300
            }
301
302
            return $result;
303
        }
304
305
        return "$number";
306
    }
307
308
    /**
309
     * Translate a time string from a locale to an other.
310
     *
311
     * @param string      $timeString date/time/duration string to translate (may also contain English)
312
     * @param string|null $from       input locale of the $timeString parameter (`Carbon::getLocale()` by default)
313
     * @param string|null $to         output locale of the result returned (`"en"` by default)
314
     * @param int         $mode       specify what to translate with options:
315
     *                                - CarbonInterface::TRANSLATE_ALL (default)
316
     *                                - CarbonInterface::TRANSLATE_MONTHS
317
     *                                - CarbonInterface::TRANSLATE_DAYS
318
     *                                - CarbonInterface::TRANSLATE_UNITS
319
     *                                - CarbonInterface::TRANSLATE_MERIDIEM
320
     *                                You can use pipe to group: CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS
321
     *
322
     * @return string
323
     */
324
    public static function translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL)
325
    {
326
        // Fallback source and destination locales
327
        $from = $from ?: static::getLocale();
328
        $to = $to ?: 'en';
329
330
        if ($from === $to) {
331
            return $timeString;
332
        }
333
334
        // Standardize apostrophe
335
        $timeString = strtr($timeString, ['’' => "'"]);
336
337
        $fromTranslations = [];
338
        $toTranslations = [];
339
340
        foreach (['from', 'to'] as $key) {
341
            $language = $$key;
342
            $translator = Translator::get($language);
343
            $translations = $translator->getMessages();
344
345
            if (!isset($translations[$language])) {
346
                return $timeString;
347
            }
348
349
            $translationKey = $key.'Translations';
350
            $messages = $translations[$language];
351
            $months = $messages['months'] ?? [];
352
            $weekdays = $messages['weekdays'] ?? [];
353
            $meridiem = $messages['meridiem'] ?? ['AM', 'PM'];
354
355
            if ($key === 'from') {
356
                foreach (['months', 'weekdays'] as $variable) {
357
                    $list = $messages[$variable.'_standalone'] ?? null;
358
359
                    if ($list) {
360
                        foreach ($$variable as $index => &$name) {
361
                            $name .= '|'.$messages[$variable.'_standalone'][$index];
362
                        }
363
                    }
364
                }
365
            }
366
367
            $$translationKey = array_merge(
368
                $mode & CarbonInterface::TRANSLATE_MONTHS ? static::getTranslationArray($months, 12, $timeString) : [],
369
                $mode & CarbonInterface::TRANSLATE_MONTHS ? static::getTranslationArray($messages['months_short'] ?? [], 12, $timeString) : [],
370
                $mode & CarbonInterface::TRANSLATE_DAYS ? static::getTranslationArray($weekdays, 7, $timeString) : [],
371
                $mode & CarbonInterface::TRANSLATE_DAYS ? static::getTranslationArray($messages['weekdays_short'] ?? [], 7, $timeString) : [],
372
                $mode & CarbonInterface::TRANSLATE_DIFF ? static::translateWordsByKeys([
373
                    'diff_now',
374
                    'diff_today',
375
                    'diff_yesterday',
376
                    'diff_tomorrow',
377
                    'diff_before_yesterday',
378
                    'diff_after_tomorrow',
379
                ], $messages, $key) : [],
380
                $mode & CarbonInterface::TRANSLATE_UNITS ? static::translateWordsByKeys([
381
                    'year',
382
                    'month',
383
                    'week',
384
                    'day',
385
                    'hour',
386
                    'minute',
387
                    'second',
388
                ], $messages, $key) : [],
389
                $mode & CarbonInterface::TRANSLATE_MERIDIEM ? array_map(function ($hour) use ($meridiem) {
390
                    if (is_array($meridiem)) {
391
                        return $meridiem[$hour < 12 ? 0 : 1];
392
                    }
393
394
                    return $meridiem($hour, 0, false);
395
                }, range(0, 23)) : []
396
            );
397
        }
398
399
        return substr(preg_replace_callback('/(?<=[\d\s+.\/,_-])('.implode('|', $fromTranslations).')(?=[\d\s+.\/,_-])/i', function ($match) use ($fromTranslations, $toTranslations) {
400
            [$chunk] = $match;
401
402
            foreach ($fromTranslations as $index => $word) {
403
                if (preg_match("/^$word\$/i", $chunk)) {
404
                    return $toTranslations[$index] ?? '';
405
                }
406
            }
407
408
            return $chunk; // @codeCoverageIgnore
409
        }, " $timeString "), 1, -1);
410
    }
411
412
    /**
413
     * Translate a time string from the current locale (`$date->locale()`) to an other.
414
     *
415
     * @param string      $timeString time string to translate
416
     * @param string|null $to         output locale of the result returned ("en" by default)
417
     *
418
     * @return string
419
     */
420
    public function translateTimeStringTo($timeString, $to = null)
421
    {
422
        return static::translateTimeString($timeString, $this->getTranslatorLocale(), $to);
423
    }
424
425
    /**
426
     * Get/set the locale for the current instance.
427
     *
428
     * @param string|null $locale
429
     * @param string      ...$fallbackLocales
430
     *
431
     * @return $this|string
432
     */
433
    public function locale(string $locale = null, ...$fallbackLocales)
434
    {
435
        if ($locale === null) {
436
            return $this->getTranslatorLocale();
437
        }
438
439
        if (!$this->localTranslator || $this->getTranslatorLocale($this->localTranslator) !== $locale) {
440
            $translator = Translator::get($locale);
441
442
            if (!empty($fallbackLocales)) {
443
                $translator->setFallbackLocales($fallbackLocales);
444
445
                foreach ($fallbackLocales as $fallbackLocale) {
446
                    $messages = Translator::get($fallbackLocale)->getMessages();
447
448
                    if (isset($messages[$fallbackLocale])) {
449
                        $translator->setMessages($fallbackLocale, $messages[$fallbackLocale]);
450
                    }
451
                }
452
            }
453
454
            $this->setLocalTranslator($translator);
455
        }
456
457
        return $this;
458
    }
459
460
    /**
461
     * Get the current translator locale.
462
     *
463
     * @return string
464
     */
465
    public static function getLocale()
466
    {
467
        return static::getLocaleAwareTranslator()->getLocale();
468
    }
469
470
    /**
471
     * Set the current translator locale and indicate if the source locale file exists.
472
     * Pass 'auto' as locale to use closest language from the current LC_TIME locale.
473
     *
474
     * @param string $locale locale ex. en
475
     *
476
     * @return bool
477
     */
478
    public static function setLocale($locale)
479
    {
480
        return static::getLocaleAwareTranslator()->setLocale($locale) !== false;
481
    }
482
483
    /**
484
     * Set the fallback locale.
485
     *
486
     * @see https://symfony.com/doc/current/components/translation.html#fallback-locales
487
     *
488
     * @param string $locale
489
     */
490
    public static function setFallbackLocale($locale)
491
    {
492
        $translator = static::getTranslator();
493
494
        if (method_exists($translator, 'setFallbackLocales')) {
495
            $translator->setFallbackLocales([$locale]);
496
497
            if ($translator instanceof Translator) {
498
                $preferredLocale = $translator->getLocale();
499
                $translator->setMessages($preferredLocale, array_replace_recursive(
500
                    $translator->getMessages()[$locale] ?? [],
501
                    Translator::get($locale)->getMessages()[$locale] ?? [],
502
                    $translator->getMessages($preferredLocale)
503
                ));
504
            }
505
        }
506
    }
507
508
    /**
509
     * Get the fallback locale.
510
     *
511
     * @see https://symfony.com/doc/current/components/translation.html#fallback-locales
512
     *
513
     * @return string|null
514
     */
515
    public static function getFallbackLocale()
516
    {
517
        $translator = static::getTranslator();
518
519
        if (method_exists($translator, 'getFallbackLocales')) {
520
            return $translator->getFallbackLocales()[0] ?? null;
521
        }
522
523
        return null;
524
    }
525
526
    /**
527
     * Set the current locale to the given, execute the passed function, reset the locale to previous one,
528
     * then return the result of the closure (or null if the closure was void).
529
     *
530
     * @param string   $locale locale ex. en
531
     * @param callable $func
532
     *
533
     * @return mixed
534
     */
535
    public static function executeWithLocale($locale, $func)
536
    {
537
        $currentLocale = static::getLocale();
538
        $result = call_user_func($func, static::setLocale($locale) ? static::getLocale() : false, static::translator());
539
        static::setLocale($currentLocale);
540
541
        return $result;
542
    }
543
544
    /**
545
     * Returns true if the given locale is internally supported and has short-units support.
546
     * Support is considered enabled if either year, day or hour has a short variant translated.
547
     *
548
     * @param string $locale locale ex. en
549
     *
550
     * @return bool
551
     */
552
    public static function localeHasShortUnits($locale)
553
    {
554
        return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) {
555
            return $newLocale &&
556
                (
557
                    ($y = static::translateWith($translator, 'y')) !== 'y' &&
558
                    $y !== static::translateWith($translator, 'year')
559
                ) || (
560
                    ($y = static::translateWith($translator, 'd')) !== 'd' &&
561
                    $y !== static::translateWith($translator, 'day')
562
                ) || (
563
                    ($y = static::translateWith($translator, 'h')) !== 'h' &&
564
                    $y !== static::translateWith($translator, 'hour')
565
                );
566
        });
567
    }
568
569
    /**
570
     * Returns true if the given locale is internally supported and has diff syntax support (ago, from now, before, after).
571
     * Support is considered enabled if the 4 sentences are translated in the given locale.
572
     *
573
     * @param string $locale locale ex. en
574
     *
575
     * @return bool
576
     */
577
    public static function localeHasDiffSyntax($locale)
578
    {
579
        return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) {
580
            if (!$newLocale) {
581
                return false;
582
            }
583
584
            foreach (['ago', 'from_now', 'before', 'after'] as $key) {
585
                if ($translator instanceof TranslatorBagInterface && $translator->getCatalogue($newLocale)->get($key) instanceof Closure) {
586
                    continue;
587
                }
588
589
                if ($translator->trans($key) === $key) {
590
                    return false;
591
                }
592
            }
593
594
            return true;
595
        });
596
    }
597
598
    /**
599
     * Returns true if the given locale is internally supported and has words for 1-day diff (just now, yesterday, tomorrow).
600
     * Support is considered enabled if the 3 words are translated in the given locale.
601
     *
602
     * @param string $locale locale ex. en
603
     *
604
     * @return bool
605
     */
606
    public static function localeHasDiffOneDayWords($locale)
607
    {
608
        return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) {
609
            return $newLocale &&
610
                $translator->trans('diff_now') !== 'diff_now' &&
611
                $translator->trans('diff_yesterday') !== 'diff_yesterday' &&
612
                $translator->trans('diff_tomorrow') !== 'diff_tomorrow';
613
        });
614
    }
615
616
    /**
617
     * Returns true if the given locale is internally supported and has words for 2-days diff (before yesterday, after tomorrow).
618
     * Support is considered enabled if the 2 words are translated in the given locale.
619
     *
620
     * @param string $locale locale ex. en
621
     *
622
     * @return bool
623
     */
624
    public static function localeHasDiffTwoDayWords($locale)
625
    {
626
        return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) {
627
            return $newLocale &&
628
                $translator->trans('diff_before_yesterday') !== 'diff_before_yesterday' &&
629
                $translator->trans('diff_after_tomorrow') !== 'diff_after_tomorrow';
630
        });
631
    }
632
633
    /**
634
     * Returns true if the given locale is internally supported and has period syntax support (X times, every X, from X, to X).
635
     * Support is considered enabled if the 4 sentences are translated in the given locale.
636
     *
637
     * @param string $locale locale ex. en
638
     *
639
     * @return bool
640
     */
641
    public static function localeHasPeriodSyntax($locale)
642
    {
643
        return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) {
644
            return $newLocale &&
645
                $translator->trans('period_recurrences') !== 'period_recurrences' &&
646
                $translator->trans('period_interval') !== 'period_interval' &&
647
                $translator->trans('period_start_date') !== 'period_start_date' &&
648
                $translator->trans('period_end_date') !== 'period_end_date';
649
        });
650
    }
651
652
    /**
653
     * Returns the list of internally available locales and already loaded custom locales.
654
     * (It will ignore custom translator dynamic loading.)
655
     *
656
     * @return array
657
     */
658
    public static function getAvailableLocales()
659
    {
660
        $translator = static::getLocaleAwareTranslator();
661
662
        return $translator instanceof Translator
663
            ? $translator->getAvailableLocales()
664
            : [$translator->getLocale()];
665
    }
666
667
    /**
668
     * Returns list of Language object for each available locale. This object allow you to get the ISO name, native
669
     * name, region and variant of the locale.
670
     *
671
     * @return Language[]
672
     */
673
    public static function getAvailableLocalesInfo()
674
    {
675
        $languages = [];
676
        foreach (static::getAvailableLocales() as $id) {
677
            $languages[$id] = new Language($id);
678
        }
679
680
        return $languages;
681
    }
682
683
    /**
684
     * Initialize the default translator instance if necessary.
685
     *
686
     * @return \Symfony\Component\Translation\TranslatorInterface
687
     */
688
    protected static function translator()
689
    {
690
        if (static::$translator === null) {
691
            static::$translator = Translator::get();
692
        }
693
694
        return static::$translator;
695
    }
696
697
    /**
698
     * Get the locale of a given translator.
699
     *
700
     * If null or omitted, current local translator is used.
701
     * If no local translator is in use, current global translator is used.
702
     *
703
     * @param null $translator
704
     *
705
     * @return string|null
706
     */
707
    protected function getTranslatorLocale($translator = null): ?string
708
    {
709
        if (func_num_args() === 0) {
710
            $translator = $this->getLocalTranslator();
711
        }
712
713
        $translator = static::getLocaleAwareTranslator($translator);
714
715
        return $translator ? $translator->getLocale() : null;
716
    }
717
718
    /**
719
     * Throw an error if passed object is not LocaleAwareInterface.
720
     *
721
     * @param LocaleAwareInterface|null $translator
722
     *
723
     * @return LocaleAwareInterface|null
724
     */
725
    protected static function getLocaleAwareTranslator($translator = null)
726
    {
727
        if (func_num_args() === 0) {
728
            $translator = static::translator();
729
        }
730
731
        if ($translator && !($translator instanceof LocaleAwareInterface || method_exists($translator, 'getLocale'))) {
732
            throw new NotLocaleAwareException($translator);
733
        }
734
735
        return $translator;
736
    }
737
738
    /**
739
     * Return the word cleaned from its translation codes.
740
     *
741
     * @param string $word
742
     *
743
     * @return string
744
     */
745
    private static function cleanWordFromTranslationString($word)
746
    {
747
        $word = str_replace([':count', '%count', ':time'], '', $word);
748
        $word = strtr($word, ['’' => "'"]);
749
        $word = preg_replace('/({\d+(,(\d+|Inf))?}|[\[\]]\d+(,(\d+|Inf))?[\[\]])/', '', $word);
750
751
        return trim($word);
752
    }
753
754
    /**
755
     * Translate a list of words.
756
     *
757
     * @param string[] $keys     keys to translate.
758
     * @param string[] $messages messages bag handling translations.
759
     * @param string   $key      'to' (to get the translation) or 'from' (to get the detection RegExp pattern).
760
     *
761
     * @return string[]
762
     */
763
    private static function translateWordsByKeys($keys, $messages, $key): array
764
    {
765
        return array_map(function ($wordKey) use ($messages, $key) {
766
            $message = $key === 'from' && isset($messages[$wordKey.'_regexp'])
767
                ? $messages[$wordKey.'_regexp']
768
                : ($messages[$wordKey] ?? null);
769
770
            if (!$message) {
771
                return '>>DO NOT REPLACE<<';
772
            }
773
774
            $parts = explode('|', $message);
775
776
            return $key === 'to'
777
                ? static::cleanWordFromTranslationString(end($parts))
778
                : '(?:'.implode('|', array_map([static::class, 'cleanWordFromTranslationString'], $parts)).')';
779
        }, $keys);
780
    }
781
782
    /**
783
     * Get an array of translations based on the current date.
784
     *
785
     * @param callable $translation
786
     * @param int      $length
787
     * @param string   $timeString
788
     *
789
     * @return string[]
790
     */
791
    private static function getTranslationArray($translation, $length, $timeString): array
792
    {
793
        $filler = '>>DO NOT REPLACE<<';
794
795
        if (is_array($translation)) {
796
            return array_pad($translation, $length, $filler);
797
        }
798
799
        $list = [];
800
        $date = static::now();
801
802
        for ($i = 0; $i < $length; $i++) {
803
            $list[] = $translation($date, $timeString, $i) ?? $filler;
804
        }
805
806
        return $list;
807
    }
808
}
809