Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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