Boundaries.php
4 years ago
Cast.php
4 years ago
Comparison.php
2 years ago
Converter.php
2 years ago
Creator.php
2 years ago
Date.php
9 months ago
DeprecatedProperties.php
4 years ago
Difference.php
2 years ago
IntervalRounding.php
2 years ago
IntervalStep.php
4 years ago
Localization.php
9 months ago
Macro.php
4 years ago
MagicParameter.php
2 years ago
Mixin.php
2 years ago
Modifiers.php
4 years ago
Mutability.php
4 years ago
ObjectInitialisation.php
4 years ago
Options.php
2 years ago
Rounding.php
2 years ago
Serialization.php
2 years ago
Test.php
9 months ago
Timestamp.php
1 year ago
ToStringFormat.php
3 years ago
Units.php
2 years ago
Week.php
4 years ago
index.php
3 years ago
Localization.php
373 lines
| 1 | <?php |
| 2 | namespace MailPoetVendor\Carbon\Traits; |
| 3 | if (!defined('ABSPATH')) exit; |
| 4 | use MailPoetVendor\Carbon\CarbonInterface; |
| 5 | use MailPoetVendor\Carbon\Exceptions\InvalidTypeException; |
| 6 | use MailPoetVendor\Carbon\Exceptions\NotLocaleAwareException; |
| 7 | use MailPoetVendor\Carbon\Language; |
| 8 | use MailPoetVendor\Carbon\Translator; |
| 9 | use MailPoetVendor\Carbon\TranslatorStrongTypeInterface; |
| 10 | use Closure; |
| 11 | use MailPoetVendor\Symfony\Component\Translation\TranslatorBagInterface; |
| 12 | use MailPoetVendor\Symfony\Component\Translation\TranslatorInterface; |
| 13 | use MailPoetVendor\Symfony\Contracts\Translation\LocaleAwareInterface; |
| 14 | use MailPoetVendor\Symfony\Contracts\Translation\TranslatorInterface as ContractsTranslatorInterface; |
| 15 | // @codeCoverageIgnoreStart |
| 16 | if (\interface_exists('MailPoetVendor\\Symfony\\Contracts\\Translation\\TranslatorInterface') && !\interface_exists('MailPoetVendor\\Symfony\\Component\\Translation\\TranslatorInterface')) { |
| 17 | \class_alias('MailPoetVendor\\Symfony\\Contracts\\Translation\\TranslatorInterface', 'MailPoetVendor\\Symfony\\Component\\Translation\\TranslatorInterface'); |
| 18 | } |
| 19 | // @codeCoverageIgnoreEnd |
| 20 | trait Localization |
| 21 | { |
| 22 | protected static $translator; |
| 23 | protected $localTranslator; |
| 24 | protected static $humanDiffOptions = CarbonInterface::NO_ZERO_DIFF; |
| 25 | public static function setHumanDiffOptions($humanDiffOptions) |
| 26 | { |
| 27 | static::$humanDiffOptions = $humanDiffOptions; |
| 28 | } |
| 29 | public static function enableHumanDiffOption($humanDiffOption) |
| 30 | { |
| 31 | static::$humanDiffOptions = static::getHumanDiffOptions() | $humanDiffOption; |
| 32 | } |
| 33 | public static function disableHumanDiffOption($humanDiffOption) |
| 34 | { |
| 35 | static::$humanDiffOptions = static::getHumanDiffOptions() & ~$humanDiffOption; |
| 36 | } |
| 37 | public static function getHumanDiffOptions() |
| 38 | { |
| 39 | return static::$humanDiffOptions; |
| 40 | } |
| 41 | public static function getTranslator() |
| 42 | { |
| 43 | return static::translator(); |
| 44 | } |
| 45 | public static function setTranslator(TranslatorInterface $translator) |
| 46 | { |
| 47 | static::$translator = $translator; |
| 48 | } |
| 49 | public function hasLocalTranslator() |
| 50 | { |
| 51 | return isset($this->localTranslator); |
| 52 | } |
| 53 | public function getLocalTranslator() |
| 54 | { |
| 55 | return $this->localTranslator ?: static::translator(); |
| 56 | } |
| 57 | public function setLocalTranslator(TranslatorInterface $translator) |
| 58 | { |
| 59 | $this->localTranslator = $translator; |
| 60 | return $this; |
| 61 | } |
| 62 | public static function getTranslationMessageWith($translator, string $key, ?string $locale = null, ?string $default = null) |
| 63 | { |
| 64 | if (!($translator instanceof TranslatorBagInterface && $translator instanceof TranslatorInterface)) { |
| 65 | throw new InvalidTypeException('Translator does not implement ' . TranslatorInterface::class . ' and ' . TranslatorBagInterface::class . '. ' . (\is_object($translator) ? \get_class($translator) : \gettype($translator)) . ' has been given.'); |
| 66 | } |
| 67 | if (!$locale && $translator instanceof LocaleAwareInterface) { |
| 68 | $locale = $translator->getLocale(); |
| 69 | } |
| 70 | $result = self::getFromCatalogue($translator, $translator->getCatalogue($locale), $key); |
| 71 | return $result === $key ? $default : $result; |
| 72 | } |
| 73 | public function getTranslationMessage(string $key, ?string $locale = null, ?string $default = null, $translator = null) |
| 74 | { |
| 75 | return static::getTranslationMessageWith($translator ?: $this->getLocalTranslator(), $key, $locale, $default); |
| 76 | } |
| 77 | public static function translateWith(TranslatorInterface $translator, string $key, array $parameters = [], $number = null) : string |
| 78 | { |
| 79 | $message = static::getTranslationMessageWith($translator, $key, null, $key); |
| 80 | if ($message instanceof Closure) { |
| 81 | return (string) $message(...\array_values($parameters)); |
| 82 | } |
| 83 | if ($number !== null) { |
| 84 | $parameters['%count%'] = $number; |
| 85 | } |
| 86 | if (isset($parameters['%count%'])) { |
| 87 | $parameters[':count'] = $parameters['%count%']; |
| 88 | } |
| 89 | // @codeCoverageIgnoreStart |
| 90 | $choice = $translator instanceof ContractsTranslatorInterface ? $translator->trans($key, $parameters) : $translator->transChoice($key, $number, $parameters); |
| 91 | // @codeCoverageIgnoreEnd |
| 92 | return (string) $choice; |
| 93 | } |
| 94 | public function translate(string $key, array $parameters = [], $number = null, ?TranslatorInterface $translator = null, bool $altNumbers = \false) : string |
| 95 | { |
| 96 | $translation = static::translateWith($translator ?: $this->getLocalTranslator(), $key, $parameters, $number); |
| 97 | if ($number !== null && $altNumbers) { |
| 98 | return \str_replace($number, $this->translateNumber($number), $translation); |
| 99 | } |
| 100 | return $translation; |
| 101 | } |
| 102 | public function translateNumber(int $number) : string |
| 103 | { |
| 104 | $translateKey = "alt_numbers.{$number}"; |
| 105 | $symbol = $this->translate($translateKey); |
| 106 | if ($symbol !== $translateKey) { |
| 107 | return $symbol; |
| 108 | } |
| 109 | if ($number > 99 && $this->translate('alt_numbers.99') !== 'alt_numbers.99') { |
| 110 | $start = ''; |
| 111 | foreach ([10000, 1000, 100] as $exp) { |
| 112 | $key = "alt_numbers_pow.{$exp}"; |
| 113 | if ($number >= $exp && $number < $exp * 10 && ($pow = $this->translate($key)) !== $key) { |
| 114 | $unit = \floor($number / $exp); |
| 115 | $number -= $unit * $exp; |
| 116 | $start .= ($unit > 1 ? $this->translate("alt_numbers.{$unit}") : '') . $pow; |
| 117 | } |
| 118 | } |
| 119 | $result = ''; |
| 120 | while ($number) { |
| 121 | $chunk = $number % 100; |
| 122 | $result = $this->translate("alt_numbers.{$chunk}") . $result; |
| 123 | $number = \floor($number / 100); |
| 124 | } |
| 125 | return "{$start}{$result}"; |
| 126 | } |
| 127 | if ($number > 9 && $this->translate('alt_numbers.9') !== 'alt_numbers.9') { |
| 128 | $result = ''; |
| 129 | while ($number) { |
| 130 | $chunk = $number % 10; |
| 131 | $result = $this->translate("alt_numbers.{$chunk}") . $result; |
| 132 | $number = \floor($number / 10); |
| 133 | } |
| 134 | return $result; |
| 135 | } |
| 136 | return (string) $number; |
| 137 | } |
| 138 | public static function translateTimeString($timeString, $from = null, $to = null, $mode = CarbonInterface::TRANSLATE_ALL) |
| 139 | { |
| 140 | // Fallback source and destination locales |
| 141 | $from = $from ?: static::getLocale(); |
| 142 | $to = $to ?: 'en'; |
| 143 | if ($from === $to) { |
| 144 | return $timeString; |
| 145 | } |
| 146 | // Standardize apostrophe |
| 147 | $timeString = \strtr($timeString, ['’' => "'"]); |
| 148 | $fromTranslations = []; |
| 149 | $toTranslations = []; |
| 150 | foreach (['from', 'to'] as $key) { |
| 151 | $language = ${$key}; |
| 152 | $translator = Translator::get($language); |
| 153 | $translations = $translator->getMessages(); |
| 154 | if (!isset($translations[$language])) { |
| 155 | return $timeString; |
| 156 | } |
| 157 | $translationKey = $key . 'Translations'; |
| 158 | $messages = $translations[$language]; |
| 159 | $months = $messages['months'] ?? []; |
| 160 | $weekdays = $messages['weekdays'] ?? []; |
| 161 | $meridiem = $messages['meridiem'] ?? ['AM', 'PM']; |
| 162 | if (isset($messages['ordinal_words'])) { |
| 163 | $timeString = self::replaceOrdinalWords($timeString, $key === 'from' ? \array_flip($messages['ordinal_words']) : $messages['ordinal_words']); |
| 164 | } |
| 165 | if ($key === 'from') { |
| 166 | foreach (['months', 'weekdays'] as $variable) { |
| 167 | $list = $messages[$variable . '_standalone'] ?? null; |
| 168 | if ($list) { |
| 169 | foreach (${$variable} as $index => &$name) { |
| 170 | $name .= '|' . $messages[$variable . '_standalone'][$index]; |
| 171 | } |
| 172 | } |
| 173 | } |
| 174 | } |
| 175 | ${$translationKey} = \array_merge($mode & CarbonInterface::TRANSLATE_MONTHS ? static::getTranslationArray($months, 12, $timeString) : [], $mode & CarbonInterface::TRANSLATE_MONTHS ? static::getTranslationArray($messages['months_short'] ?? [], 12, $timeString) : [], $mode & CarbonInterface::TRANSLATE_DAYS ? static::getTranslationArray($weekdays, 7, $timeString) : [], $mode & CarbonInterface::TRANSLATE_DAYS ? static::getTranslationArray($messages['weekdays_short'] ?? [], 7, $timeString) : [], $mode & CarbonInterface::TRANSLATE_DIFF ? static::translateWordsByKeys(['diff_now', 'diff_today', 'diff_yesterday', 'diff_tomorrow', 'diff_before_yesterday', 'diff_after_tomorrow'], $messages, $key) : [], $mode & CarbonInterface::TRANSLATE_UNITS ? static::translateWordsByKeys(['year', 'month', 'week', 'day', 'hour', 'minute', 'second'], $messages, $key) : [], $mode & CarbonInterface::TRANSLATE_MERIDIEM ? \array_map(function ($hour) use($meridiem) { |
| 176 | if (\is_array($meridiem)) { |
| 177 | return $meridiem[$hour < 12 ? 0 : 1]; |
| 178 | } |
| 179 | return $meridiem($hour, 0, \false); |
| 180 | }, \range(0, 23)) : []); |
| 181 | } |
| 182 | return \substr(\preg_replace_callback('/(?<=[\\d\\s+.\\/,_-])(' . \implode('|', $fromTranslations) . ')(?=[\\d\\s+.\\/,_-])/iu', function ($match) use($fromTranslations, $toTranslations) { |
| 183 | [$chunk] = $match; |
| 184 | foreach ($fromTranslations as $index => $word) { |
| 185 | if (\preg_match("/^{$word}\$/iu", $chunk)) { |
| 186 | return $toTranslations[$index] ?? ''; |
| 187 | } |
| 188 | } |
| 189 | return $chunk; |
| 190 | // @codeCoverageIgnore |
| 191 | }, " {$timeString} "), 1, -1); |
| 192 | } |
| 193 | public function translateTimeStringTo($timeString, $to = null) |
| 194 | { |
| 195 | return static::translateTimeString($timeString, $this->getTranslatorLocale(), $to); |
| 196 | } |
| 197 | public function locale(?string $locale = null, ...$fallbackLocales) |
| 198 | { |
| 199 | if ($locale === null) { |
| 200 | return $this->getTranslatorLocale(); |
| 201 | } |
| 202 | if (!$this->localTranslator || $this->getTranslatorLocale($this->localTranslator) !== $locale) { |
| 203 | $translator = Translator::get($locale); |
| 204 | if (!empty($fallbackLocales)) { |
| 205 | $translator->setFallbackLocales($fallbackLocales); |
| 206 | foreach ($fallbackLocales as $fallbackLocale) { |
| 207 | $messages = Translator::get($fallbackLocale)->getMessages(); |
| 208 | if (isset($messages[$fallbackLocale])) { |
| 209 | $translator->setMessages($fallbackLocale, $messages[$fallbackLocale]); |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | $this->localTranslator = $translator; |
| 214 | } |
| 215 | return $this; |
| 216 | } |
| 217 | public static function getLocale() |
| 218 | { |
| 219 | return static::getLocaleAwareTranslator()->getLocale(); |
| 220 | } |
| 221 | public static function setLocale($locale) |
| 222 | { |
| 223 | return static::getLocaleAwareTranslator()->setLocale($locale) !== \false; |
| 224 | } |
| 225 | public static function setFallbackLocale($locale) |
| 226 | { |
| 227 | $translator = static::getTranslator(); |
| 228 | if (\method_exists($translator, 'setFallbackLocales')) { |
| 229 | $translator->setFallbackLocales([$locale]); |
| 230 | if ($translator instanceof Translator) { |
| 231 | $preferredLocale = $translator->getLocale(); |
| 232 | $translator->setMessages($preferredLocale, \array_replace_recursive($translator->getMessages()[$locale] ?? [], Translator::get($locale)->getMessages()[$locale] ?? [], $translator->getMessages($preferredLocale))); |
| 233 | } |
| 234 | } |
| 235 | } |
| 236 | public static function getFallbackLocale() |
| 237 | { |
| 238 | $translator = static::getTranslator(); |
| 239 | if (\method_exists($translator, 'getFallbackLocales')) { |
| 240 | return $translator->getFallbackLocales()[0] ?? null; |
| 241 | } |
| 242 | return null; |
| 243 | } |
| 244 | public static function executeWithLocale($locale, $func) |
| 245 | { |
| 246 | $currentLocale = static::getLocale(); |
| 247 | $result = $func(static::setLocale($locale) ? static::getLocale() : \false, static::translator()); |
| 248 | static::setLocale($currentLocale); |
| 249 | return $result; |
| 250 | } |
| 251 | public static function localeHasShortUnits($locale) |
| 252 | { |
| 253 | return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { |
| 254 | return $newLocale && (($y = static::translateWith($translator, 'y')) !== 'y' && $y !== static::translateWith($translator, 'year')) || ($y = static::translateWith($translator, 'd')) !== 'd' && $y !== static::translateWith($translator, 'day') || ($y = static::translateWith($translator, 'h')) !== 'h' && $y !== static::translateWith($translator, 'hour'); |
| 255 | }); |
| 256 | } |
| 257 | public static function localeHasDiffSyntax($locale) |
| 258 | { |
| 259 | return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { |
| 260 | if (!$newLocale) { |
| 261 | return \false; |
| 262 | } |
| 263 | foreach (['ago', 'from_now', 'before', 'after'] as $key) { |
| 264 | if ($translator instanceof TranslatorBagInterface && self::getFromCatalogue($translator, $translator->getCatalogue($newLocale), $key) instanceof Closure) { |
| 265 | continue; |
| 266 | } |
| 267 | if ($translator->trans($key) === $key) { |
| 268 | return \false; |
| 269 | } |
| 270 | } |
| 271 | return \true; |
| 272 | }); |
| 273 | } |
| 274 | public static function localeHasDiffOneDayWords($locale) |
| 275 | { |
| 276 | return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { |
| 277 | return $newLocale && $translator->trans('diff_now') !== 'diff_now' && $translator->trans('diff_yesterday') !== 'diff_yesterday' && $translator->trans('diff_tomorrow') !== 'diff_tomorrow'; |
| 278 | }); |
| 279 | } |
| 280 | public static function localeHasDiffTwoDayWords($locale) |
| 281 | { |
| 282 | return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { |
| 283 | return $newLocale && $translator->trans('diff_before_yesterday') !== 'diff_before_yesterday' && $translator->trans('diff_after_tomorrow') !== 'diff_after_tomorrow'; |
| 284 | }); |
| 285 | } |
| 286 | public static function localeHasPeriodSyntax($locale) |
| 287 | { |
| 288 | return static::executeWithLocale($locale, function ($newLocale, TranslatorInterface $translator) { |
| 289 | return $newLocale && $translator->trans('period_recurrences') !== 'period_recurrences' && $translator->trans('period_interval') !== 'period_interval' && $translator->trans('period_start_date') !== 'period_start_date' && $translator->trans('period_end_date') !== 'period_end_date'; |
| 290 | }); |
| 291 | } |
| 292 | public static function getAvailableLocales() |
| 293 | { |
| 294 | $translator = static::getLocaleAwareTranslator(); |
| 295 | return $translator instanceof Translator ? $translator->getAvailableLocales() : [$translator->getLocale()]; |
| 296 | } |
| 297 | public static function getAvailableLocalesInfo() |
| 298 | { |
| 299 | $languages = []; |
| 300 | foreach (static::getAvailableLocales() as $id) { |
| 301 | $languages[$id] = new Language($id); |
| 302 | } |
| 303 | return $languages; |
| 304 | } |
| 305 | protected static function translator() |
| 306 | { |
| 307 | if (static::$translator === null) { |
| 308 | static::$translator = Translator::get(); |
| 309 | } |
| 310 | return static::$translator; |
| 311 | } |
| 312 | protected function getTranslatorLocale($translator = null) : ?string |
| 313 | { |
| 314 | if (\func_num_args() === 0) { |
| 315 | $translator = $this->getLocalTranslator(); |
| 316 | } |
| 317 | $translator = static::getLocaleAwareTranslator($translator); |
| 318 | return $translator ? $translator->getLocale() : null; |
| 319 | } |
| 320 | protected static function getLocaleAwareTranslator($translator = null) |
| 321 | { |
| 322 | if (\func_num_args() === 0) { |
| 323 | $translator = static::translator(); |
| 324 | } |
| 325 | if ($translator && !($translator instanceof LocaleAwareInterface || \method_exists($translator, 'getLocale'))) { |
| 326 | throw new NotLocaleAwareException($translator); |
| 327 | // @codeCoverageIgnore |
| 328 | } |
| 329 | return $translator; |
| 330 | } |
| 331 | private static function getFromCatalogue($translator, $catalogue, string $id, string $domain = 'messages') |
| 332 | { |
| 333 | return $translator instanceof TranslatorStrongTypeInterface ? $translator->getFromCatalogue($catalogue, $id, $domain) : $catalogue->get($id, $domain); |
| 334 | } |
| 335 | private static function cleanWordFromTranslationString($word) |
| 336 | { |
| 337 | $word = \str_replace([':count', '%count', ':time'], '', $word); |
| 338 | $word = \strtr($word, ['’' => "'"]); |
| 339 | $word = \preg_replace('/({\\d+(,(\\d+|Inf))?}|[\\[\\]]\\d+(,(\\d+|Inf))?[\\[\\]])/', '', $word); |
| 340 | return \trim($word); |
| 341 | } |
| 342 | private static function translateWordsByKeys($keys, $messages, $key) : array |
| 343 | { |
| 344 | return \array_map(function ($wordKey) use($messages, $key) { |
| 345 | $message = $key === 'from' && isset($messages[$wordKey . '_regexp']) ? $messages[$wordKey . '_regexp'] : $messages[$wordKey] ?? null; |
| 346 | if (!$message) { |
| 347 | return '>>DO NOT REPLACE<<'; |
| 348 | } |
| 349 | $parts = \explode('|', $message); |
| 350 | return $key === 'to' ? self::cleanWordFromTranslationString(\end($parts)) : '(?:' . \implode('|', \array_map([static::class, 'cleanWordFromTranslationString'], $parts)) . ')'; |
| 351 | }, $keys); |
| 352 | } |
| 353 | private static function getTranslationArray($translation, $length, $timeString) : array |
| 354 | { |
| 355 | $filler = '>>DO NOT REPLACE<<'; |
| 356 | if (\is_array($translation)) { |
| 357 | return \array_pad($translation, $length, $filler); |
| 358 | } |
| 359 | $list = []; |
| 360 | $date = static::now(); |
| 361 | for ($i = 0; $i < $length; $i++) { |
| 362 | $list[] = $translation($date, $timeString, $i) ?? $filler; |
| 363 | } |
| 364 | return $list; |
| 365 | } |
| 366 | private static function replaceOrdinalWords(string $timeString, array $ordinalWords) : string |
| 367 | { |
| 368 | return \preg_replace_callback('/(?<![a-z])[a-z]+(?![a-z])/i', function (array $match) use($ordinalWords) { |
| 369 | return $ordinalWords[\mb_strtolower($match[0])] ?? $match[0]; |
| 370 | }, $timeString); |
| 371 | } |
| 372 | } |
| 373 |