From aa6a43327d69f57df319560283afa0fba144c199 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Fri, 25 Feb 2022 00:05:42 +0100 Subject: [PATCH 01/10] Extract locale fallback computation into a dedicated class --- .../Translation/FallbackLocaleProvider.php | 82 +++++++++++++++++++ .../Component/Translation/Translator.php | 62 +++----------- 2 files changed, 94 insertions(+), 50 deletions(-) create mode 100644 src/Symfony/Component/Translation/FallbackLocaleProvider.php diff --git a/src/Symfony/Component/Translation/FallbackLocaleProvider.php b/src/Symfony/Component/Translation/FallbackLocaleProvider.php new file mode 100644 index 0000000000000..1cd81fe35eef4 --- /dev/null +++ b/src/Symfony/Component/Translation/FallbackLocaleProvider.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +/** + * Contains a configured list of fallback locales and provides the fallback + * chain for a given locale. + * + * @author Matthias Pigulla + */ +class FallbackLocaleProvider +{ + /** + * @var string[] + */ + private array $fallbackLocales = []; + + private array $parentLocales; + + public function setFallbackLocales(array $locales) + { + $this->fallbackLocales = $locales; + } + + /** + * @internal + */ + public function getFallbackLocales(): array + { + return $this->fallbackLocales; + } + + public function computeFallbackLocales(string $locale) + { + $this->parentLocales ??= json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true); + + $originLocale = $locale; + $locales = []; + + while ($locale) { + $parent = $this->parentLocales[$locale] ?? null; + + if ($parent) { + $locale = 'root' !== $parent ? $parent : null; + } elseif (\function_exists('locale_parse')) { + $localeSubTags = locale_parse($locale); + $locale = null; + if (1 < \count($localeSubTags)) { + array_pop($localeSubTags); + $locale = locale_compose($localeSubTags) ?: null; + } + } elseif ($i = strrpos($locale, '_') ?: strrpos($locale, '-')) { + $locale = substr($locale, 0, $i); + } else { + $locale = null; + } + + if (null !== $locale) { + $locales[] = $locale; + } + } + + foreach ($this->fallbackLocales as $fallback) { + if ($fallback === $originLocale) { + continue; + } + + $locales[] = $fallback; + } + + return array_unique($locales); + } +} diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index d2e5c73f24a70..3a0f0857d0692 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -40,11 +40,6 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA private string $locale; - /** - * @var string[] - */ - private array $fallbackLocales = []; - /** * @var LoaderInterface[] */ @@ -62,14 +57,17 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA private ?ConfigCacheFactoryInterface $configCacheFactory; - private array $parentLocales; - private bool $hasIntlFormatter; + /** + * @var FallbackLocaleProvider + */ + private $fallbackLocaleProvider; + /** * @throws InvalidArgumentException If a locale contains invalid characters */ - public function __construct(string $locale, MessageFormatterInterface $formatter = null, string $cacheDir = null, bool $debug = false, array $cacheVary = []) + public function __construct(string $locale, MessageFormatterInterface $formatter = null, string $cacheDir = null, bool $debug = false, array $cacheVary = [], FallbackLocaleProvider $fallbackLocaleProvider = null) { $this->setLocale($locale); @@ -82,6 +80,7 @@ public function __construct(string $locale, MessageFormatterInterface $formatter $this->debug = $debug; $this->cacheVary = $cacheVary; $this->hasIntlFormatter = $formatter instanceof IntlFormatterInterface; + $this->fallbackLocaleProvider = $fallbackLocaleProvider ?? new FallbackLocaleProvider(); } public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory) @@ -118,7 +117,7 @@ public function addResource(string $format, mixed $resource, string $locale, str $this->resources[$locale][] = [$format, $resource, $domain]; - if (\in_array($locale, $this->fallbackLocales)) { + if (\in_array($locale, $this->fallbackLocaleProvider->getFallbackLocales())) { $this->catalogues = []; } else { unset($this->catalogues[$locale]); @@ -158,17 +157,16 @@ public function setFallbackLocales(array $locales) $this->assertValidLocale($locale); } - $this->fallbackLocales = $this->cacheVary['fallback_locales'] = $locales; + $this->fallbackLocaleProvider->setFallbackLocales($locales); + $this->cacheVary['fallback_locales'] = $locales; } /** - * Gets the fallback locales. - * * @internal */ public function getFallbackLocales(): array { - return $this->fallbackLocales; + return $this->fallbackLocaleProvider->getFallbackLocales(); } /** @@ -393,43 +391,7 @@ private function loadFallbackCatalogues(string $locale): void protected function computeFallbackLocales(string $locale) { - $this->parentLocales ??= json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true); - - $originLocale = $locale; - $locales = []; - - while ($locale) { - $parent = $this->parentLocales[$locale] ?? null; - - if ($parent) { - $locale = 'root' !== $parent ? $parent : null; - } elseif (\function_exists('locale_parse')) { - $localeSubTags = locale_parse($locale); - $locale = null; - if (1 < \count($localeSubTags)) { - array_pop($localeSubTags); - $locale = locale_compose($localeSubTags) ?: null; - } - } elseif ($i = strrpos($locale, '_') ?: strrpos($locale, '-')) { - $locale = substr($locale, 0, $i); - } else { - $locale = null; - } - - if (null !== $locale) { - $locales[] = $locale; - } - } - - foreach ($this->fallbackLocales as $fallback) { - if ($fallback === $originLocale) { - continue; - } - - $locales[] = $fallback; - } - - return array_unique($locales); + return $this->fallbackLocaleProvider->computeFallbackLocales($locale); } /** From 2afae5e75d594fcd1b6160cb9913ae2504a78b66 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Mon, 28 Feb 2022 08:29:28 +0100 Subject: [PATCH 02/10] Add FallbackLocaleProviderInterface --- .../Translation/FallbackLocaleProvider.php | 13 ++++--- .../FallbackLocaleProviderInterface.php | 34 +++++++++++++++++++ .../Component/Translation/Translator.php | 7 ++-- 3 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php diff --git a/src/Symfony/Component/Translation/FallbackLocaleProvider.php b/src/Symfony/Component/Translation/FallbackLocaleProvider.php index 1cd81fe35eef4..22bd7a23d3c31 100644 --- a/src/Symfony/Component/Translation/FallbackLocaleProvider.php +++ b/src/Symfony/Component/Translation/FallbackLocaleProvider.php @@ -12,12 +12,12 @@ namespace Symfony\Component\Translation; /** - * Contains a configured list of fallback locales and provides the fallback - * chain for a given locale. + * Derives fallback locales based on ICU parent locale information, by shortening locale + * sub tags and ultimately by going through a list of configured fallbacl locales. * * @author Matthias Pigulla */ -class FallbackLocaleProvider +class FallbackLocaleProvider implements FallbackLocaleProviderInterface { /** * @var string[] @@ -26,7 +26,7 @@ class FallbackLocaleProvider private array $parentLocales; - public function setFallbackLocales(array $locales) + public function setFallbackLocales(array $locales): void { $this->fallbackLocales = $locales; } @@ -39,7 +39,10 @@ public function getFallbackLocales(): array return $this->fallbackLocales; } - public function computeFallbackLocales(string $locale) + /** + * @return string[] + */ + public function computeFallbackLocales(string $locale): array { $this->parentLocales ??= json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true); diff --git a/src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php b/src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php new file mode 100644 index 0000000000000..6a3e1be536b46 --- /dev/null +++ b/src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +/** + * For a given locale, implementations provide the list of alternative locales to + * try when a translation cannot be found. + * + * @author Matthias Pigulla + */ +interface FallbackLocaleProviderInterface +{ + public function setFallbackLocales(array $locales): void; + + /** + * @internal + * @return string[] + */ + public function getFallbackLocales(): array; + + /** + * @return string[] + */ + public function computeFallbackLocales(string $locale): array; +} diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index 3a0f0857d0692..831a2bf1e66c7 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -59,15 +59,12 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA private bool $hasIntlFormatter; - /** - * @var FallbackLocaleProvider - */ - private $fallbackLocaleProvider; + private FallbackLocaleProviderInterface $fallbackLocaleProvider; /** * @throws InvalidArgumentException If a locale contains invalid characters */ - public function __construct(string $locale, MessageFormatterInterface $formatter = null, string $cacheDir = null, bool $debug = false, array $cacheVary = [], FallbackLocaleProvider $fallbackLocaleProvider = null) + public function __construct(string $locale, MessageFormatterInterface $formatter = null, string $cacheDir = null, bool $debug = false, array $cacheVary = [], FallbackLocaleProviderInterface $fallbackLocaleProvider = null) { $this->setLocale($locale); From 54e6a4a94c01e53c40fce8ea53df234480727ad0 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Mon, 28 Feb 2022 08:39:06 +0100 Subject: [PATCH 03/10] Move locale validation out of Translator and use it to validate locales in FallbackLocaleProvider as well --- .../Translation/FallbackLocaleProvider.php | 8 ++++- .../FallbackLocaleProviderInterface.php | 12 +++++++ .../Component/Translation/LocaleValidator.php | 34 +++++++++++++++++++ .../Component/Translation/Translator.php | 12 ++----- 4 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 src/Symfony/Component/Translation/LocaleValidator.php diff --git a/src/Symfony/Component/Translation/FallbackLocaleProvider.php b/src/Symfony/Component/Translation/FallbackLocaleProvider.php index 22bd7a23d3c31..f5ebdf526e7c5 100644 --- a/src/Symfony/Component/Translation/FallbackLocaleProvider.php +++ b/src/Symfony/Component/Translation/FallbackLocaleProvider.php @@ -13,7 +13,7 @@ /** * Derives fallback locales based on ICU parent locale information, by shortening locale - * sub tags and ultimately by going through a list of configured fallbacl locales. + * sub tags and ultimately by going through a list of configured fallback locales. * * @author Matthias Pigulla */ @@ -28,6 +28,10 @@ class FallbackLocaleProvider implements FallbackLocaleProviderInterface public function setFallbackLocales(array $locales): void { + foreach ($locales as $locale) { + LocaleValidator::assertValidLocale($locale); + } + $this->fallbackLocales = $locales; } @@ -44,6 +48,8 @@ public function getFallbackLocales(): array */ public function computeFallbackLocales(string $locale): array { + LocaleValidator::assertValidLocale($locale); + $this->parentLocales ??= json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true); $originLocale = $locale; diff --git a/src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php b/src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php index 6a3e1be536b46..5354c36d8e818 100644 --- a/src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php +++ b/src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Translation; +use Symfony\Component\Translation\Exception\InvalidArgumentException; + /** * For a given locale, implementations provide the list of alternative locales to * try when a translation cannot be found. @@ -19,6 +21,13 @@ */ interface FallbackLocaleProviderInterface { + /** + * Sets the fallback locales. + * + * @param string[] $locales + * + * @throws InvalidArgumentException If a locale contains invalid characters + */ public function setFallbackLocales(array $locales): void; /** @@ -28,6 +37,9 @@ public function setFallbackLocales(array $locales): void; public function getFallbackLocales(): array; /** + * For a given locale, this method provides the ordered list of alternative (fallback) locales + * to try. + * * @return string[] */ public function computeFallbackLocales(string $locale): array; diff --git a/src/Symfony/Component/Translation/LocaleValidator.php b/src/Symfony/Component/Translation/LocaleValidator.php new file mode 100644 index 0000000000000..41ab59c630497 --- /dev/null +++ b/src/Symfony/Component/Translation/LocaleValidator.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * Asserts (syntactical) validity for given locale identifiers. + * + * @author Matthias Pigulla + */ +class LocaleValidator +{ + /** + * Asserts that the locale is valid, throws an Exception if not. + * + * @throws InvalidArgumentException If the locale contains invalid characters + */ + public static function assertValidLocale(string $locale): void + { + if (!preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) { + throw new InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale)); + } + } +} diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index 831a2bf1e66c7..d254a89391218 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -147,14 +147,10 @@ public function getLocale(): string */ public function setFallbackLocales(array $locales) { + $this->fallbackLocaleProvider->setFallbackLocales($locales); + // needed as the fallback locales are linked to the already loaded catalogues $this->catalogues = []; - - foreach ($locales as $locale) { - $this->assertValidLocale($locale); - } - - $this->fallbackLocaleProvider->setFallbackLocales($locales); $this->cacheVary['fallback_locales'] = $locales; } @@ -398,9 +394,7 @@ protected function computeFallbackLocales(string $locale) */ protected function assertValidLocale(string $locale) { - if (!preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) { - throw new InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale)); - } + LocaleValidator::assertValidLocale($locale); } /** From 042757af0e48be296a33ea9e2c030f3cdca28d19 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Mon, 28 Feb 2022 08:43:24 +0100 Subject: [PATCH 04/10] Add a constructor for FallbackLocaleProvider --- .../Component/Translation/FallbackLocaleProvider.php | 12 ++++++++++++ .../Translation/FallbackLocaleProviderInterface.php | 1 + 2 files changed, 13 insertions(+) diff --git a/src/Symfony/Component/Translation/FallbackLocaleProvider.php b/src/Symfony/Component/Translation/FallbackLocaleProvider.php index f5ebdf526e7c5..f92340ca1d46e 100644 --- a/src/Symfony/Component/Translation/FallbackLocaleProvider.php +++ b/src/Symfony/Component/Translation/FallbackLocaleProvider.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Translation; +use Symfony\Component\Translation\Exception\InvalidArgumentException; + /** * Derives fallback locales based on ICU parent locale information, by shortening locale * sub tags and ultimately by going through a list of configured fallback locales. @@ -26,6 +28,16 @@ class FallbackLocaleProvider implements FallbackLocaleProviderInterface private array $parentLocales; + /** + * @param string[] $locales + * + * @throws InvalidArgumentException If a locale contains invalid characters + */ + public function __construct(array $locales = []) + { + $this->setFallbackLocales($locales); + } + public function setFallbackLocales(array $locales): void { foreach ($locales as $locale) { diff --git a/src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php b/src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php index 5354c36d8e818..82415a45edba6 100644 --- a/src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php +++ b/src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php @@ -32,6 +32,7 @@ public function setFallbackLocales(array $locales): void; /** * @internal + * * @return string[] */ public function getFallbackLocales(): array; From 2455bac44d26f16b08e3a761d01fe9c66e578658 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Mon, 28 Feb 2022 08:52:12 +0100 Subject: [PATCH 05/10] Add a CHANGELOG entry --- src/Symfony/Component/Translation/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Component/Translation/CHANGELOG.md b/src/Symfony/Component/Translation/CHANGELOG.md index 720298d8e06c2..d212670de449b 100644 --- a/src/Symfony/Component/Translation/CHANGELOG.md +++ b/src/Symfony/Component/Translation/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Parameters implementing `TranslatableInterface` are processed * Add the file extension to the `XliffFileDumper` constructor + * Added `FallbackLocaleProvider` to configure the fallback locale chain in `Translator` 5.4 --- From 532a72c74a65c513ad1e7e2a35e733a15655aecd Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Mon, 28 Feb 2022 08:57:12 +0100 Subject: [PATCH 06/10] Make FrameworkBundle configure FallbackLocaleProvider in the container --- .../Bundle/FrameworkBundle/Resources/config/translation.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php index 7758078f277cf..2d504a3235089 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php @@ -27,6 +27,8 @@ use Symfony\Component\Translation\Extractor\ChainExtractor; use Symfony\Component\Translation\Extractor\ExtractorInterface; use Symfony\Component\Translation\Extractor\PhpExtractor; +use Symfony\Component\Translation\FallbackLocaleProvider; +use Symfony\Component\Translation\FallbackLocaleProviderInterface; use Symfony\Component\Translation\Formatter\MessageFormatter; use Symfony\Component\Translation\Loader\CsvFileLoader; use Symfony\Component\Translation\Loader\IcuDatFileLoader; @@ -59,6 +61,7 @@ 'debug' => param('kernel.debug'), ], abstract_arg('enabled locales'), + service('translation.fallback_locale_provider') ]) ->call('setConfigCacheFactory', [service('config_cache_factory')]) ->tag('kernel.locale_aware') @@ -75,6 +78,9 @@ ->set('translator.formatter.default', MessageFormatter::class) ->args([service('identity_translator')]) + ->set('translation.fallback_locale_provider', FallbackLocaleProvider::class) + ->alias(FallbackLocaleProviderInterface::class, 'translation.fallback_locale_provider') + ->set('translation.loader.php', PhpFileLoader::class) ->tag('translation.loader', ['alias' => 'php']) From 47fe72ae82cf82699c1ef7224f431d1b5dc4b87e Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Mon, 28 Feb 2022 09:06:09 +0100 Subject: [PATCH 07/10] Try to get away without requirement bumps in FrameworkBundle --- .../FrameworkBundle/Resources/config/translation.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php index 2d504a3235089..6903e4ca4f25b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.php @@ -61,7 +61,7 @@ 'debug' => param('kernel.debug'), ], abstract_arg('enabled locales'), - service('translation.fallback_locale_provider') + service('translation.fallback_locale_provider')->ignoreOnInvalid(), ]) ->call('setConfigCacheFactory', [service('config_cache_factory')]) ->tag('kernel.locale_aware') @@ -78,9 +78,6 @@ ->set('translator.formatter.default', MessageFormatter::class) ->args([service('identity_translator')]) - ->set('translation.fallback_locale_provider', FallbackLocaleProvider::class) - ->alias(FallbackLocaleProviderInterface::class, 'translation.fallback_locale_provider') - ->set('translation.loader.php', PhpFileLoader::class) ->tag('translation.loader', ['alias' => 'php']) @@ -169,4 +166,10 @@ ->tag('container.service_subscriber', ['id' => 'translator']) ->tag('kernel.cache_warmer') ; + + if (class_exists(FallbackLocaleProvider::class)) { + $container->services() + ->set('translation.fallback_locale_provider', FallbackLocaleProvider::class) + ->alias(FallbackLocaleProviderInterface::class, 'translation.fallback_locale_provider'); + } }; From 0c19e4f3172b15e88f9470bdca1849a09c96e55a Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Mon, 1 Aug 2022 09:28:20 +0200 Subject: [PATCH 08/10] Update CHANGELOG as suggested in GH Review Co-authored-by: Mathieu Santostefano --- src/Symfony/Component/Translation/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Translation/CHANGELOG.md b/src/Symfony/Component/Translation/CHANGELOG.md index d212670de449b..6b716567f680e 100644 --- a/src/Symfony/Component/Translation/CHANGELOG.md +++ b/src/Symfony/Component/Translation/CHANGELOG.md @@ -6,7 +6,7 @@ CHANGELOG * Parameters implementing `TranslatableInterface` are processed * Add the file extension to the `XliffFileDumper` constructor - * Added `FallbackLocaleProvider` to configure the fallback locale chain in `Translator` + * Add `FallbackLocaleProvider` to configure the fallback locale chain in `Translator` 5.4 --- From c2ac4a2bc6e2fa68569c95ebbf5aa44127fb9269 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Mon, 1 Aug 2022 09:29:11 +0200 Subject: [PATCH 09/10] Rename method as suggested in GH Review --- src/Symfony/Component/Translation/FallbackLocaleProvider.php | 4 ++-- src/Symfony/Component/Translation/LocaleValidator.php | 2 +- src/Symfony/Component/Translation/Translator.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Translation/FallbackLocaleProvider.php b/src/Symfony/Component/Translation/FallbackLocaleProvider.php index f92340ca1d46e..50269f85db5ef 100644 --- a/src/Symfony/Component/Translation/FallbackLocaleProvider.php +++ b/src/Symfony/Component/Translation/FallbackLocaleProvider.php @@ -41,7 +41,7 @@ public function __construct(array $locales = []) public function setFallbackLocales(array $locales): void { foreach ($locales as $locale) { - LocaleValidator::assertValidLocale($locale); + LocaleValidator::validate($locale); } $this->fallbackLocales = $locales; @@ -60,7 +60,7 @@ public function getFallbackLocales(): array */ public function computeFallbackLocales(string $locale): array { - LocaleValidator::assertValidLocale($locale); + LocaleValidator::validate($locale); $this->parentLocales ??= json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true); diff --git a/src/Symfony/Component/Translation/LocaleValidator.php b/src/Symfony/Component/Translation/LocaleValidator.php index 41ab59c630497..6b5004acb6be6 100644 --- a/src/Symfony/Component/Translation/LocaleValidator.php +++ b/src/Symfony/Component/Translation/LocaleValidator.php @@ -25,7 +25,7 @@ class LocaleValidator * * @throws InvalidArgumentException If the locale contains invalid characters */ - public static function assertValidLocale(string $locale): void + public static function validate(string $locale): void { if (!preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) { throw new InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale)); diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index d254a89391218..316388a772a27 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -394,7 +394,7 @@ protected function computeFallbackLocales(string $locale) */ protected function assertValidLocale(string $locale) { - LocaleValidator::assertValidLocale($locale); + LocaleValidator::validate($locale); } /** From ddb94624b87db317b08cf081b63d13a9f40b21a7 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Mon, 1 Aug 2022 10:50:11 +0200 Subject: [PATCH 10/10] Make the FallbackLocaleProvider immutable; create new instances when the fallback locale configuration changes --- .../Command/TranslationDebugCommand.php | 2 +- .../FrameworkExtension.php | 9 ++++++- .../TranslationDataCollector.php | 2 +- .../Translation/DataCollectorTranslator.php | 2 ++ .../Translation/FallbackLocaleProvider.php | 27 +++++++++---------- .../FallbackLocaleProviderInterface.php | 11 +------- .../Translation/LoggingTranslator.php | 2 ++ .../Component/Translation/Translator.php | 26 +++++++++++++----- 8 files changed, 47 insertions(+), 34 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index 906f160b4f11f..3fdc572972605 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -370,7 +370,7 @@ private function loadFallbackCatalogues(string $locale, array $transPaths): arra { $fallbackCatalogues = []; if ($this->translator instanceof Translator || $this->translator instanceof DataCollectorTranslator || $this->translator instanceof LoggingTranslator) { - foreach ($this->translator->getFallbackLocales() as $fallbackLocale) { + foreach ($this->translator->getFallbackLocaleProvider()->getUltimateFallbackLocales() as $fallbackLocale) { if ($fallbackLocale === $locale) { continue; } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index f0b15e6522aff..5deb3a2986887 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1269,7 +1269,14 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $container->setAlias('translator', 'translator.default')->setPublic(true); $container->setAlias('translator.formatter', new Alias($config['formatter'], false)); $translator = $container->findDefinition('translator.default'); - $translator->addMethodCall('setFallbackLocales', [$config['fallbacks'] ?: [$defaultLocale]]); + + if ($container->has('translation.fallback_locale_provider')) { + $container + ->findDefinition('translation.fallback_locale_provider') + ->replaceArgument(0, [$config['fallbacks'] ?: [$defaultLocale]]); + } else { + $translator->addMethodCall('setFallbackLocales', [$config['fallbacks'] ?: [$defaultLocale]]); + } $defaultOptions = $translator->getArgument(4); $defaultOptions['cache_dir'] = $config['cache_dir']; diff --git a/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php b/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php index 4244511e46afa..8793ca045cd11 100644 --- a/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php +++ b/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php @@ -51,7 +51,7 @@ public function lateCollect() public function collect(Request $request, Response $response, \Throwable $exception = null) { $this->data['locale'] = $this->translator->getLocale(); - $this->data['fallback_locales'] = $this->translator->getFallbackLocales(); + $this->data['fallback_locales'] = $this->translator->getFallbackLocaleProvider()->getUltimateFallbackLocales(); } /** diff --git a/src/Symfony/Component/Translation/DataCollectorTranslator.php b/src/Symfony/Component/Translation/DataCollectorTranslator.php index cab9874608db1..ab9c0b308a147 100644 --- a/src/Symfony/Component/Translation/DataCollectorTranslator.php +++ b/src/Symfony/Component/Translation/DataCollectorTranslator.php @@ -102,6 +102,8 @@ public function warmUp(string $cacheDir): array */ public function getFallbackLocales(): array { + trigger_deprecation('symfony/translation', '6.2', 'DataCollectorTranslator::getFallbackLocales() is deprecated. Get the FallbackLocaleProvider instead.'); + if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) { return $this->translator->getFallbackLocales(); } diff --git a/src/Symfony/Component/Translation/FallbackLocaleProvider.php b/src/Symfony/Component/Translation/FallbackLocaleProvider.php index 50269f85db5ef..e137008de55a0 100644 --- a/src/Symfony/Component/Translation/FallbackLocaleProvider.php +++ b/src/Symfony/Component/Translation/FallbackLocaleProvider.php @@ -23,36 +23,33 @@ class FallbackLocaleProvider implements FallbackLocaleProviderInterface { /** * @var string[] + * + * List of fallback locales to add _after_ the ones derived from ICU information. */ - private array $fallbackLocales = []; + private array $ultimateFallbackLocales; - private array $parentLocales; + private ?array $parentLocales = null; /** - * @param string[] $locales + * @param string[] $ultimateFallbackLocales * * @throws InvalidArgumentException If a locale contains invalid characters */ - public function __construct(array $locales = []) + public function __construct(array $ultimateFallbackLocales = []) { - $this->setFallbackLocales($locales); - } - - public function setFallbackLocales(array $locales): void - { - foreach ($locales as $locale) { + foreach ($ultimateFallbackLocales as $locale) { LocaleValidator::validate($locale); } - $this->fallbackLocales = $locales; + $this->ultimateFallbackLocales = $ultimateFallbackLocales; } /** - * @internal + * @return string[] */ - public function getFallbackLocales(): array + public function getUltimateFallbackLocales(): array { - return $this->fallbackLocales; + return $this->ultimateFallbackLocales; } /** @@ -90,7 +87,7 @@ public function computeFallbackLocales(string $locale): array } } - foreach ($this->fallbackLocales as $fallback) { + foreach ($this->ultimateFallbackLocales as $fallback) { if ($fallback === $originLocale) { continue; } diff --git a/src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php b/src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php index 82415a45edba6..ae536174f3cf5 100644 --- a/src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php +++ b/src/Symfony/Component/Translation/FallbackLocaleProviderInterface.php @@ -21,21 +21,12 @@ */ interface FallbackLocaleProviderInterface { - /** - * Sets the fallback locales. - * - * @param string[] $locales - * - * @throws InvalidArgumentException If a locale contains invalid characters - */ - public function setFallbackLocales(array $locales): void; - /** * @internal * * @return string[] */ - public function getFallbackLocales(): array; + public function getUltimateFallbackLocales(): array; /** * For a given locale, this method provides the ordered list of alternative (fallback) locales diff --git a/src/Symfony/Component/Translation/LoggingTranslator.php b/src/Symfony/Component/Translation/LoggingTranslator.php index 8dd8ecf96be29..bfac567c5d488 100644 --- a/src/Symfony/Component/Translation/LoggingTranslator.php +++ b/src/Symfony/Component/Translation/LoggingTranslator.php @@ -91,6 +91,8 @@ public function getCatalogues(): array */ public function getFallbackLocales(): array { + trigger_deprecation('symfony/translation', '6.2', 'LoggingTranslator::getFallbackLocales() is deprecated. Get the FallbackLocaleProvider instead.'); + if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) { return $this->translator->getFallbackLocales(); } diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index 316388a772a27..e4c0e5f799ae5 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -85,6 +85,20 @@ public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFa $this->configCacheFactory = $configCacheFactory; } + public function setFallbackLocaleProvider(FallbackLocaleProviderInterface $fallbackLocaleProvider) + { + $this->fallbackLocaleProvider = $fallbackLocaleProvider; + + // needed as the fallback locales are linked to the already loaded catalogues + $this->catalogues = []; + $this->cacheVary['fallback_locales'] = $fallbackLocaleProvider->getUltimateFallbackLocales(); + } + + public function getFallbackLocaleProvider(): FallbackLocaleProviderInterface + { + return $this->fallbackLocaleProvider; + } + /** * Adds a Loader. * @@ -114,7 +128,7 @@ public function addResource(string $format, mixed $resource, string $locale, str $this->resources[$locale][] = [$format, $resource, $domain]; - if (\in_array($locale, $this->fallbackLocaleProvider->getFallbackLocales())) { + if (\in_array($locale, $this->fallbackLocaleProvider->getUltimateFallbackLocales())) { $this->catalogues = []; } else { unset($this->catalogues[$locale]); @@ -147,11 +161,9 @@ public function getLocale(): string */ public function setFallbackLocales(array $locales) { - $this->fallbackLocaleProvider->setFallbackLocales($locales); + trigger_deprecation('symfony/translation', '6.2', 'Changing the fallback locales on the Translator is deprecated. Provide a new FallbackLocaleProviderInterface instance instead.'); - // needed as the fallback locales are linked to the already loaded catalogues - $this->catalogues = []; - $this->cacheVary['fallback_locales'] = $locales; + $this->setFallbackLocaleProvider(new FallbackLocaleProvider($locales)); } /** @@ -159,7 +171,9 @@ public function setFallbackLocales(array $locales) */ public function getFallbackLocales(): array { - return $this->fallbackLocaleProvider->getFallbackLocales(); + trigger_deprecation('symfony/translation', '6.2', 'Querying fallback locales on the Translator is deprecated. Get the FallbackLocaleProvider instead and either use it to compute the fallback locale order, or query its ultimate fallback locale list.'); + + return $this->fallbackLocaleProvider->getUltimateFallbackLocales(); } /**