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