diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index fd77de26e6bc6..17b4a438b2aec 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -967,6 +967,7 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $e ->fixXmlConfig('fallback') ->fixXmlConfig('path') ->fixXmlConfig('provider') + ->fixXmlConfig('global') ->children() ->arrayNode('fallbacks') ->info('Defaults to the value of "default_locale".') @@ -1021,6 +1022,33 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $e ->end() ->defaultValue([]) ->end() + ->arrayNode('globals') + ->info('Global parameters.') + ->example(['app_version' => 3.14]) + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->arrayPrototype() + ->fixXmlConfig('parameter') + ->children() + ->variableNode('value')->end() + ->stringNode('message')->end() + ->arrayNode('parameters') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->scalarPrototype()->end() + ->end() + ->stringNode('domain')->end() + ->end() + ->beforeNormalization() + ->ifTrue(static fn ($v) => !\is_array($v)) + ->then(static fn ($v) => ['value' => $v]) + ->end() + ->validate() + ->ifTrue(static fn ($v) => !(isset($v['value']) xor isset($v['message']))) + ->thenInvalid('The "globals" parameter should be either a string or an array with a "value" or a "message" key') + ->end() + ->end() + ->end() ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 418ee739c6863..fdcae2a344981 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -185,6 +185,7 @@ use Symfony\Component\Translation\Extractor\PhpAstExtractor; use Symfony\Component\Translation\LocaleSwitcher; use Symfony\Component\Translation\PseudoLocalizationTranslator; +use Symfony\Component\Translation\TranslatableMessage; use Symfony\Component\Translation\Translator; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\TypeResolver\PhpDocAwareReflectionTypeResolver; @@ -1614,6 +1615,10 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $translator->replaceArgument(4, $options); } + foreach ($config['globals'] as $name => $global) { + $translator->addMethodCall('addGlobalParameter', [$name, $global['value'] ?? new Definition(TranslatableMessage::class, [$global['message'], $global['parameters'] ?? [], $global['domain'] ?? null])]); + } + if ($config['pseudo_localization']['enabled']) { $options = $config['pseudo_localization']; unset($options['enabled']); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index ae69a5aa9a66c..8661384e2d5b8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -256,6 +256,7 @@ + @@ -285,6 +286,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index e4ec77451724b..babfb2eab0b5b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -785,6 +785,7 @@ protected static function getBundleDefaultConfig() 'localizable_html_attributes' => [], ], 'providers' => [], + 'globals' => [], ], 'validation' => [ 'enabled' => !class_exists(FullStack::class), diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/translator_globals.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/translator_globals.php new file mode 100644 index 0000000000000..8ee438ff906d1 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/translator_globals.php @@ -0,0 +1,15 @@ +loadFromExtension('framework', [ + 'annotations' => false, + 'http_method_override' => false, + 'handle_all_throwables' => true, + 'php_errors' => ['log' => true], + 'translator' => [ + 'globals' => [ + '%%app_name%%' => 'My application', + '{app_version}' => '1.2.3', + '{url}' => ['message' => 'url', 'parameters' => ['scheme' => 'https://'], 'domain' => 'global'], + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/translator_without_globals.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/translator_without_globals.php new file mode 100644 index 0000000000000..fcc65c9682650 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/translator_without_globals.php @@ -0,0 +1,9 @@ +loadFromExtension('framework', [ + 'annotations' => false, + 'http_method_override' => false, + 'handle_all_throwables' => true, + 'php_errors' => ['log' => true], + 'translator' => ['globals' => []], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/translator_globals.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/translator_globals.xml new file mode 100644 index 0000000000000..017fd9393b85c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/translator_globals.xml @@ -0,0 +1,20 @@ + + + + + + + + + My application + + + https:// + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/translator_without_globals.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/translator_without_globals.xml new file mode 100644 index 0000000000000..6c686bd30b210 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/translator_without_globals.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/translator_globals.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/translator_globals.yml new file mode 100644 index 0000000000000..ed42b676c8fd5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/translator_globals.yml @@ -0,0 +1,11 @@ +framework: + annotations: false + http_method_override: false + handle_all_throwables: true + php_errors: + log: true + translator: + globals: + '%%app_name%%': 'My application' + '{app_version}': '1.2.3' + '{url}': { message: 'url', parameters: { scheme: 'https://' }, domain: 'global' } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/translator_without_globals.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/translator_without_globals.yml new file mode 100644 index 0000000000000..dc7323868d762 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/translator_without_globals.yml @@ -0,0 +1,8 @@ +framework: + annotations: false + http_method_override: false + handle_all_throwables: true + php_errors: + log: true + translator: + globals: [] diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php index 3a719b8634b77..fdc586cc922ba 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php @@ -83,6 +83,7 @@ use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\DependencyInjection\TranslatorPass; use Symfony\Component\Translation\LocaleSwitcher; +use Symfony\Component\Translation\TranslatableMessage; use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass; use Symfony\Component\Validator\Validation; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -1241,6 +1242,36 @@ public function testTranslatorCacheDirDisabled() $this->assertNull($options['cache_dir']); } + public function testTranslatorGlobals() + { + $container = $this->createContainerFromFile('translator_globals'); + + $calls = $container->getDefinition('translator.default')->getMethodCalls(); + + $this->assertCount(5, $calls); + $this->assertSame( + ['addGlobalParameter', ['%%app_name%%', 'My application']], + $calls[2], + ); + $this->assertSame( + ['addGlobalParameter', ['{app_version}', '1.2.3']], + $calls[3], + ); + $this->assertEquals( + ['addGlobalParameter', ['{url}', new Definition(TranslatableMessage::class, ['url', ['scheme' => 'https://'], 'global'])]], + $calls[4], + ); + } + + public function testTranslatorWithoutGlobals() + { + $container = $this->createContainerFromFile('translator_without_globals'); + + $calls = $container->getDefinition('translator.default')->getMethodCalls(); + + $this->assertCount(2, $calls); + } + public function testValidation() { $container = $this->createContainerFromFile('full'); diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 999dd7fedcdb7..72d33ed884df2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -62,7 +62,7 @@ "symfony/serializer": "^7.1", "symfony/stopwatch": "^6.4|^7.0", "symfony/string": "^6.4|^7.0", - "symfony/translation": "^6.4.3|^7.0", + "symfony/translation": "^7.3", "symfony/twig-bundle": "^6.4|^7.0", "symfony/type-info": "^7.1", "symfony/validator": "^6.4|^7.0", @@ -101,7 +101,7 @@ "symfony/security-core": "<6.4", "symfony/serializer": "<7.1", "symfony/stopwatch": "<6.4", - "symfony/translation": "<6.4.3", + "symfony/translation": "<7.3", "symfony/twig-bridge": "<6.4", "symfony/twig-bundle": "<6.4", "symfony/validator": "<6.4", diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig index eeb8a06a88dee..53560cf306713 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig @@ -156,6 +156,27 @@ {% endblock messages %} {% endif %} + {% if collector.globalParameters|default([]) %} +

Global parameters

+ + + + + + + + + + {% for id, value in collector.globalParameters %} + + + + + {% endfor %} + +
Message IDValue
{{ id }}{{ profiler_dump(value) }}
+ {% endif %} + {% endblock %} {% macro render_table(messages, is_fallback) %} diff --git a/src/Symfony/Component/Translation/CHANGELOG.md b/src/Symfony/Component/Translation/CHANGELOG.md index 622c7f75dd04a..365c5cf1cdc7e 100644 --- a/src/Symfony/Component/Translation/CHANGELOG.md +++ b/src/Symfony/Component/Translation/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.3 +--- + + * Add `Translator::addGlobalParameter()` to allow defining global translation parameters + 7.2 --- diff --git a/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php b/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php index e6bd619db5f02..e2e597a705e86 100644 --- a/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php +++ b/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php @@ -44,6 +44,7 @@ public function collect(Request $request, Response $response, ?\Throwable $excep { $this->data['locale'] = $this->translator->getLocale(); $this->data['fallback_locales'] = $this->translator->getFallbackLocales(); + $this->data['global_parameters'] = $this->translator->getGlobalParameters(); } public function reset(): void @@ -84,6 +85,14 @@ public function getFallbackLocales(): Data|array return (isset($this->data['fallback_locales']) && \count($this->data['fallback_locales']) > 0) ? $this->data['fallback_locales'] : []; } + /** + * @internal + */ + public function getGlobalParameters(): Data|array + { + return $this->data['global_parameters'] ?? []; + } + public function getName(): string { return 'translation'; diff --git a/src/Symfony/Component/Translation/DataCollectorTranslator.php b/src/Symfony/Component/Translation/DataCollectorTranslator.php index dcabedeb85333..c85318f772c6d 100644 --- a/src/Symfony/Component/Translation/DataCollectorTranslator.php +++ b/src/Symfony/Component/Translation/DataCollectorTranslator.php @@ -82,6 +82,15 @@ public function getFallbackLocales(): array return []; } + public function getGlobalParameters(): array + { + if ($this->translator instanceof Translator || method_exists($this->translator, 'getGlobalParameters')) { + return $this->translator->getGlobalParameters(); + } + + return []; + } + public function __call(string $method, array $args): mixed { return $this->translator->{$method}(...$args); diff --git a/src/Symfony/Component/Translation/Tests/DataCollector/TranslationDataCollectorTest.php b/src/Symfony/Component/Translation/Tests/DataCollector/TranslationDataCollectorTest.php index 149ccacdc717f..64af1284c21e8 100644 --- a/src/Symfony/Component/Translation/Tests/DataCollector/TranslationDataCollectorTest.php +++ b/src/Symfony/Component/Translation/Tests/DataCollector/TranslationDataCollectorTest.php @@ -137,17 +137,20 @@ public function testCollectAndReset() $translator = $this->getTranslator(); $translator->method('getLocale')->willReturn('fr'); $translator->method('getFallbackLocales')->willReturn(['en']); + $translator->method('getGlobalParameters')->willReturn(['welcome' => 'Welcome {name}!']); $dataCollector = new TranslationDataCollector($translator); $dataCollector->collect($this->createMock(Request::class), $this->createMock(Response::class)); $this->assertSame('fr', $dataCollector->getLocale()); $this->assertSame(['en'], $dataCollector->getFallbackLocales()); + $this->assertSame(['welcome' => 'Welcome {name}!'], $dataCollector->getGlobalParameters()); $dataCollector->reset(); $this->assertNull($dataCollector->getLocale()); $this->assertSame([], $dataCollector->getFallbackLocales()); + $this->assertSame([], $dataCollector->getGlobalParameters()); } private function getTranslator() diff --git a/src/Symfony/Component/Translation/Tests/DataCollectorTranslatorTest.php b/src/Symfony/Component/Translation/Tests/DataCollectorTranslatorTest.php index c6cdbbea2b3af..fe37566a65138 100644 --- a/src/Symfony/Component/Translation/Tests/DataCollectorTranslatorTest.php +++ b/src/Symfony/Component/Translation/Tests/DataCollectorTranslatorTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Translation\DataCollectorTranslator; use Symfony\Component\Translation\Loader\ArrayLoader; +use Symfony\Component\Translation\TranslatableMessage; use Symfony\Component\Translation\Translator; class DataCollectorTranslatorTest extends TestCase @@ -84,6 +85,18 @@ public function testCollectMessages() $this->assertEquals($expectedMessages, $collector->getCollectedMessages()); } + public function testGetGlobalParameters() + { + $translatable = new TranslatableMessage('url.front'); + + $translator = new Translator('en'); + $translator->addGlobalParameter('app', 'My app'); + $translator->addGlobalParameter('url', $translatable); + $collector = new DataCollectorTranslator($translator); + + $this->assertEquals(['app' => 'My app', 'url' => $translatable], $collector->getGlobalParameters()); + } + private function createCollector() { $translator = new Translator('en'); diff --git a/src/Symfony/Component/Translation/Tests/TranslatorTest.php b/src/Symfony/Component/Translation/Tests/TranslatorTest.php index 27f47d2865d6d..c4dee1bb34fe7 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorTest.php @@ -602,6 +602,56 @@ public function testMissingLoaderForResourceError() $translator->getCatalogue('en'); } + + public function testTransWithGlobalParameters() + { + $translator = new Translator('en'); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', ['welcome' => 'Welcome {name}!'], 'en'); + $translator->addResource('array', ['welcome' => 'Bienvenue {name}!'], 'fr'); + $translator->addGlobalParameter('{name}', 'Global name'); + + $this->assertSame('Welcome Global name!', $translator->trans('welcome')); + $this->assertSame('Bienvenue Global name!', $translator->trans('welcome', [], null, 'fr')); + $this->assertSame('Welcome John!', $translator->trans('welcome', ['{name}' => 'John'])); + $this->assertSame('Bienvenue Jean!', $translator->trans('welcome', ['{name}' => 'Jean'], null, 'fr')); + } + + public function testTransWithGlobalTranslatableParameters() + { + $translator = new Translator('en'); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', ['welcome' => 'Welcome on {link}!'], 'en'); + $translator->addResource('array', ['welcome' => 'Bienvenue sur {link}!'], 'fr'); + + $translator->addResource('array', ['url' => 'example.com/admin'], 'en', 'globals'); + $translator->addResource('array', ['url' => 'example.fr/admin'], 'fr', 'globals'); + + $translator->addGlobalParameter('{link}', new TranslatableMessage('url', [], 'globals')); + + $this->assertSame('Welcome on example.com/admin!', $translator->trans('welcome')); + $this->assertSame('Bienvenue sur example.fr/admin!', $translator->trans('welcome', [], null, 'fr')); + $this->assertSame('Welcome on other.com!', $translator->trans('welcome', ['{link}' => 'other.com'])); + $this->assertSame('Bienvenue sur autre.fr!', $translator->trans('welcome', ['{link}' => 'autre.fr'], null, 'fr')); + } + + /** + * @requires extension intl + */ + public function testTransICUWithGlobalParameters() + { + $domain = 'test.'.MessageCatalogue::INTL_DOMAIN_SUFFIX; + + $translator = new Translator('en'); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', [ + 'apples' => '{apples, plural, =0 {There are no apples} one {There is one apple} other {There are # apples}}', + ], 'en', $domain); + $translator->addGlobalParameter('{apples}', 42); + + $this->assertSame('There are 42 apples', $translator->trans('apples', [], $domain)); + $this->assertSame('There is one apple', $translator->trans('apples', ['{apples}' => 1], $domain)); + } } class StringClass diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index 7c0a458e8afac..4ce3edad3e97b 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -60,6 +60,16 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA private bool $hasIntlFormatter; + /** + * @var array + */ + private array $globalParameters = []; + + /** + * @var array + */ + private array $globalTranslatedParameters = []; + /** * @throws InvalidArgumentException If a locale contains invalid characters */ @@ -155,6 +165,17 @@ public function getFallbackLocales(): array return $this->fallbackLocales; } + public function addGlobalParameter(string $id, string|int|float|TranslatableInterface $value): void + { + $this->globalParameters[$id] = $value; + $this->globalTranslatedParameters = []; + } + + public function getGlobalParameters(): array + { + return $this->globalParameters; + } + public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string { if (null === $id || '' === $id) { @@ -174,7 +195,24 @@ public function trans(?string $id, array $parameters = [], ?string $domain = nul } } - $parameters = array_map(fn ($parameter) => $parameter instanceof TranslatableInterface ? $parameter->trans($this, $locale) : $parameter, $parameters); + foreach ($parameters as $key => $value) { + if ($value instanceof TranslatableInterface) { + $parameters[$key] = $value->trans($this, $locale); + } + } + + if (null === $globalParameters =& $this->globalTranslatedParameters[$locale]) { + $globalParameters = $this->globalParameters; + foreach ($globalParameters as $key => $value) { + if ($value instanceof TranslatableInterface) { + $globalParameters[$key] = $value->trans($this, $locale); + } + } + } + + if ($globalParameters) { + $parameters += $globalParameters; + } $len = \strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX); if ($this->hasIntlFormatter pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy