Skip to content

Commit 36092eb

Browse files
committed
feature symfony#53425 [Translation] Allow default parameters (Jean-Beru)
This PR was squashed before being merged into the 7.3 branch. Discussion ---------- [Translation] Allow default parameters | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | Fix symfony#48182 | License | MIT This PRs adds a `GlobalsTranslator` which decorates the `Translator` when global parameters are given. Usage: ```yaml # config/framework.yaml framework: # ... translator: # ... globals: '{version}': '1.2.3' '%%application_name%%': 'My app' # "%" must be escaped if it is not a DI parameter ``` ```yaml # messages.en.yaml app: version: 'Application version: {version}' name: 'Application name: %application_name%' ``` ```twig # twig template {{ 'app.version'|trans }} # Displays "Application version: 1.2.3" {{ 'app.version'|trans({ '{version}': '2.3.4' }) }} # Displays "Application version: 2.3.4" {{ 'app.name'|trans }} # Displays "Application name: My app" {{ 'app.name'|trans({ '{application_name}': 'My new app' }) }} # Displays "Application name: My new app" ``` Profiler view: ![image](https://github.com/user-attachments/assets/6ba13995-ca91-4098-a87d-daea89864ab6) Commits ------- 652c658 [Translation] Allow default parameters
2 parents c11abf2 + 652c658 commit 36092eb

20 files changed

+312
-3
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

+28
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,7 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $e
967967
->fixXmlConfig('fallback')
968968
->fixXmlConfig('path')
969969
->fixXmlConfig('provider')
970+
->fixXmlConfig('global')
970971
->children()
971972
->arrayNode('fallbacks')
972973
->info('Defaults to the value of "default_locale".')
@@ -1021,6 +1022,33 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $e
10211022
->end()
10221023
->defaultValue([])
10231024
->end()
1025+
->arrayNode('globals')
1026+
->info('Global parameters.')
1027+
->example(['app_version' => 3.14])
1028+
->normalizeKeys(false)
1029+
->useAttributeAsKey('name')
1030+
->arrayPrototype()
1031+
->fixXmlConfig('parameter')
1032+
->children()
1033+
->variableNode('value')->end()
1034+
->stringNode('message')->end()
1035+
->arrayNode('parameters')
1036+
->normalizeKeys(false)
1037+
->useAttributeAsKey('name')
1038+
->scalarPrototype()->end()
1039+
->end()
1040+
->stringNode('domain')->end()
1041+
->end()
1042+
->beforeNormalization()
1043+
->ifTrue(static fn ($v) => !\is_array($v))
1044+
->then(static fn ($v) => ['value' => $v])
1045+
->end()
1046+
->validate()
1047+
->ifTrue(static fn ($v) => !(isset($v['value']) xor isset($v['message'])))
1048+
->thenInvalid('The "globals" parameter should be either a string or an array with a "value" or a "message" key')
1049+
->end()
1050+
->end()
1051+
->end()
10241052
->end()
10251053
->end()
10261054
->end()

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

+5
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@
186186
use Symfony\Component\Translation\Extractor\PhpAstExtractor;
187187
use Symfony\Component\Translation\LocaleSwitcher;
188188
use Symfony\Component\Translation\PseudoLocalizationTranslator;
189+
use Symfony\Component\Translation\TranslatableMessage;
189190
use Symfony\Component\Translation\Translator;
190191
use Symfony\Component\TypeInfo\Type;
191192
use Symfony\Component\TypeInfo\TypeResolver\PhpDocAwareReflectionTypeResolver;
@@ -1615,6 +1616,10 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder
16151616
$translator->replaceArgument(4, $options);
16161617
}
16171618

1619+
foreach ($config['globals'] as $name => $global) {
1620+
$translator->addMethodCall('addGlobalParameter', [$name, $global['value'] ?? new Definition(TranslatableMessage::class, [$global['message'], $global['parameters'] ?? [], $global['domain'] ?? null])]);
1621+
}
1622+
16181623
if ($config['pseudo_localization']['enabled']) {
16191624
$options = $config['pseudo_localization'];
16201625
unset($options['enabled']);

src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

+19
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@
256256
<xsd:element name="path" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
257257
<xsd:element name="pseudo-localization" type="pseudo_localization" minOccurs="0" maxOccurs="1" />
258258
<xsd:element name="provider" type="translation_provider" minOccurs="0" maxOccurs="unbounded" />
259+
<xsd:element name="global" type="translation_global" minOccurs="0" maxOccurs="unbounded" />
259260
</xsd:sequence>
260261
<xsd:attribute name="enabled" type="xsd:boolean" />
261262
<xsd:attribute name="fallback" type="xsd:string" />
@@ -285,6 +286,24 @@
285286
<xsd:attribute name="dsn" type="xsd:string" />
286287
</xsd:complexType>
287288

289+
<xsd:complexType name="translation_global" mixed="true">
290+
<xsd:sequence>
291+
<xsd:element name="parameter" type="translation_global_parameter" minOccurs="0" maxOccurs="unbounded" />
292+
</xsd:sequence>
293+
<xsd:attribute name="name" type="xsd:string" use="required" />
294+
<xsd:attribute name="value" type="xsd:string" />
295+
<xsd:attribute name="message" type="xsd:string" />
296+
<xsd:attribute name="domain" type="xsd:string" />
297+
</xsd:complexType>
298+
299+
<xsd:complexType name="translation_global_parameter">
300+
<xsd:simpleContent>
301+
<xsd:extension base="xsd:string">
302+
<xsd:attribute name="name" type="xsd:string" use="required" />
303+
</xsd:extension>
304+
</xsd:simpleContent>
305+
</xsd:complexType>
306+
288307
<xsd:complexType name="validation">
289308
<xsd:choice minOccurs="0" maxOccurs="unbounded">
290309
<xsd:element name="static-method" type="xsd:string" />

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,7 @@ protected static function getBundleDefaultConfig()
785785
'localizable_html_attributes' => [],
786786
],
787787
'providers' => [],
788+
'globals' => [],
788789
],
789790
'validation' => [
790791
'enabled' => !class_exists(FullStack::class),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
$container->loadFromExtension('framework', [
4+
'annotations' => false,
5+
'http_method_override' => false,
6+
'handle_all_throwables' => true,
7+
'php_errors' => ['log' => true],
8+
'translator' => [
9+
'globals' => [
10+
'%%app_name%%' => 'My application',
11+
'{app_version}' => '1.2.3',
12+
'{url}' => ['message' => 'url', 'parameters' => ['scheme' => 'https://'], 'domain' => 'global'],
13+
],
14+
],
15+
]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
$container->loadFromExtension('framework', [
4+
'annotations' => false,
5+
'http_method_override' => false,
6+
'handle_all_throwables' => true,
7+
'php_errors' => ['log' => true],
8+
'translator' => ['globals' => []],
9+
]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xmlns:framework="http://symfony.com/schema/dic/symfony"
6+
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
7+
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
8+
9+
<framework:config secret="s3cr3t" http-method-override="false" handle-all-throwables="true">
10+
<framework:annotations enabled="false" />
11+
<framework:php-errors log="true" />
12+
<framework:translator enabled="true">
13+
<framework:global name="%%app_name%%">My application</framework:global>
14+
<framework:global name="{app_version}" value="1.2.3" />
15+
<framework:global name="{url}" message="url" domain="global">
16+
<framework:parameter name="scheme">https://</framework:parameter>
17+
</framework:global>
18+
</framework:translator>
19+
</framework:config>
20+
</container>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xmlns:framework="http://symfony.com/schema/dic/symfony"
6+
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
7+
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
8+
9+
<framework:config secret="s3cr3t" http-method-override="false" handle-all-throwables="true">
10+
<framework:annotations enabled="false" />
11+
<framework:php-errors log="true" />
12+
<framework:translator enabled="true" />
13+
</framework:config>
14+
</container>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
framework:
2+
annotations: false
3+
http_method_override: false
4+
handle_all_throwables: true
5+
php_errors:
6+
log: true
7+
translator:
8+
globals:
9+
'%%app_name%%': 'My application'
10+
'{app_version}': '1.2.3'
11+
'{url}': { message: 'url', parameters: { scheme: 'https://' }, domain: 'global' }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
framework:
2+
annotations: false
3+
http_method_override: false
4+
handle_all_throwables: true
5+
php_errors:
6+
log: true
7+
translator:
8+
globals: []

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php

+31
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
use Symfony\Component\Serializer\Serializer;
8484
use Symfony\Component\Translation\DependencyInjection\TranslatorPass;
8585
use Symfony\Component\Translation\LocaleSwitcher;
86+
use Symfony\Component\Translation\TranslatableMessage;
8687
use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass;
8788
use Symfony\Component\Validator\Validation;
8889
use Symfony\Component\Validator\Validator\ValidatorInterface;
@@ -1241,6 +1242,36 @@ public function testTranslatorCacheDirDisabled()
12411242
$this->assertNull($options['cache_dir']);
12421243
}
12431244

1245+
public function testTranslatorGlobals()
1246+
{
1247+
$container = $this->createContainerFromFile('translator_globals');
1248+
1249+
$calls = $container->getDefinition('translator.default')->getMethodCalls();
1250+
1251+
$this->assertCount(5, $calls);
1252+
$this->assertSame(
1253+
['addGlobalParameter', ['%%app_name%%', 'My application']],
1254+
$calls[2],
1255+
);
1256+
$this->assertSame(
1257+
['addGlobalParameter', ['{app_version}', '1.2.3']],
1258+
$calls[3],
1259+
);
1260+
$this->assertEquals(
1261+
['addGlobalParameter', ['{url}', new Definition(TranslatableMessage::class, ['url', ['scheme' => 'https://'], 'global'])]],
1262+
$calls[4],
1263+
);
1264+
}
1265+
1266+
public function testTranslatorWithoutGlobals()
1267+
{
1268+
$container = $this->createContainerFromFile('translator_without_globals');
1269+
1270+
$calls = $container->getDefinition('translator.default')->getMethodCalls();
1271+
1272+
$this->assertCount(2, $calls);
1273+
}
1274+
12441275
public function testValidation()
12451276
{
12461277
$container = $this->createContainerFromFile('full');

src/Symfony/Bundle/FrameworkBundle/composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"symfony/serializer": "^7.1",
6363
"symfony/stopwatch": "^6.4|^7.0",
6464
"symfony/string": "^6.4|^7.0",
65-
"symfony/translation": "^6.4.3|^7.0",
65+
"symfony/translation": "^7.3",
6666
"symfony/twig-bundle": "^6.4|^7.0",
6767
"symfony/type-info": "^7.1",
6868
"symfony/validator": "^6.4|^7.0",
@@ -101,7 +101,7 @@
101101
"symfony/security-core": "<6.4",
102102
"symfony/serializer": "<7.1",
103103
"symfony/stopwatch": "<6.4",
104-
"symfony/translation": "<6.4.3",
104+
"symfony/translation": "<7.3",
105105
"symfony/twig-bridge": "<6.4",
106106
"symfony/twig-bundle": "<6.4",
107107
"symfony/validator": "<6.4",

src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/translation.html.twig

+21
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,27 @@
156156
{% endblock messages %}
157157
{% endif %}
158158

159+
{% if collector.globalParameters|default([]) %}
160+
<h2>Global parameters</h2>
161+
162+
<table>
163+
<thead>
164+
<tr>
165+
<th>Message ID</th>
166+
<th>Value</th>
167+
</tr>
168+
</thead>
169+
<tbody>
170+
{% for id, value in collector.globalParameters %}
171+
<tr>
172+
<td class="font-normal text-small nowrap">{{ id }}</td>
173+
<td class="font-normal text-small nowrap">{{ profiler_dump(value) }}</td>
174+
</tr>
175+
{% endfor %}
176+
</tbody>
177+
</table>
178+
{% endif %}
179+
159180
{% endblock %}
160181

161182
{% macro render_table(messages, is_fallback) %}

src/Symfony/Component/Translation/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.3
5+
---
6+
7+
* Add `Translator::addGlobalParameter()` to allow defining global translation parameters
8+
49
7.2
510
---
611

src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php

+9
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public function collect(Request $request, Response $response, ?\Throwable $excep
4444
{
4545
$this->data['locale'] = $this->translator->getLocale();
4646
$this->data['fallback_locales'] = $this->translator->getFallbackLocales();
47+
$this->data['global_parameters'] = $this->translator->getGlobalParameters();
4748
}
4849

4950
public function reset(): void
@@ -84,6 +85,14 @@ public function getFallbackLocales(): Data|array
8485
return (isset($this->data['fallback_locales']) && \count($this->data['fallback_locales']) > 0) ? $this->data['fallback_locales'] : [];
8586
}
8687

88+
/**
89+
* @internal
90+
*/
91+
public function getGlobalParameters(): Data|array
92+
{
93+
return $this->data['global_parameters'] ?? [];
94+
}
95+
8796
public function getName(): string
8897
{
8998
return 'translation';

src/Symfony/Component/Translation/DataCollectorTranslator.php

+9
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,15 @@ public function getFallbackLocales(): array
8282
return [];
8383
}
8484

85+
public function getGlobalParameters(): array
86+
{
87+
if ($this->translator instanceof Translator || method_exists($this->translator, 'getGlobalParameters')) {
88+
return $this->translator->getGlobalParameters();
89+
}
90+
91+
return [];
92+
}
93+
8594
public function __call(string $method, array $args): mixed
8695
{
8796
return $this->translator->{$method}(...$args);

src/Symfony/Component/Translation/Tests/DataCollector/TranslationDataCollectorTest.php

+3
Original file line numberDiff line numberDiff line change
@@ -137,17 +137,20 @@ public function testCollectAndReset()
137137
$translator = $this->getTranslator();
138138
$translator->method('getLocale')->willReturn('fr');
139139
$translator->method('getFallbackLocales')->willReturn(['en']);
140+
$translator->method('getGlobalParameters')->willReturn(['welcome' => 'Welcome {name}!']);
140141

141142
$dataCollector = new TranslationDataCollector($translator);
142143
$dataCollector->collect($this->createMock(Request::class), $this->createMock(Response::class));
143144

144145
$this->assertSame('fr', $dataCollector->getLocale());
145146
$this->assertSame(['en'], $dataCollector->getFallbackLocales());
147+
$this->assertSame(['welcome' => 'Welcome {name}!'], $dataCollector->getGlobalParameters());
146148

147149
$dataCollector->reset();
148150

149151
$this->assertNull($dataCollector->getLocale());
150152
$this->assertSame([], $dataCollector->getFallbackLocales());
153+
$this->assertSame([], $dataCollector->getGlobalParameters());
151154
}
152155

153156
private function getTranslator()

src/Symfony/Component/Translation/Tests/DataCollectorTranslatorTest.php

+13
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Translation\DataCollectorTranslator;
1616
use Symfony\Component\Translation\Loader\ArrayLoader;
17+
use Symfony\Component\Translation\TranslatableMessage;
1718
use Symfony\Component\Translation\Translator;
1819

1920
class DataCollectorTranslatorTest extends TestCase
@@ -84,6 +85,18 @@ public function testCollectMessages()
8485
$this->assertEquals($expectedMessages, $collector->getCollectedMessages());
8586
}
8687

88+
public function testGetGlobalParameters()
89+
{
90+
$translatable = new TranslatableMessage('url.front');
91+
92+
$translator = new Translator('en');
93+
$translator->addGlobalParameter('app', 'My app');
94+
$translator->addGlobalParameter('url', $translatable);
95+
$collector = new DataCollectorTranslator($translator);
96+
97+
$this->assertEquals(['app' => 'My app', 'url' => $translatable], $collector->getGlobalParameters());
98+
}
99+
87100
private function createCollector()
88101
{
89102
$translator = new Translator('en');

0 commit comments

Comments
 (0)