Skip to content

Commit 5aa6e8f

Browse files
committed
feature #43108 [HttpKernel] Add basic support for language negotiation (GregoireHebert)
This PR was merged into the 5.4 branch. Discussion ---------- [HttpKernel] Add basic support for language negotiation | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - Continuation of #36507. Thanks `@GregoireHebert`! This PR adds two options to the framework configuration: - `set_locale_from_accept_language`: Makes the `Request`' locale automatically set based on the `Accept-Language` header (restricted by a new `framework.enabled_locales` config option which replaces `framework.translator.enabled_locales`). The explicit `_locale` request attribute always wins over the `Accept-Language` header when it's set. - `set_content_language_from_locale`: Sets the `Content-Language` Response header based on the `Request`' locale. This is going to be useful for API Platform and related (e.g. Sylius/Sylius#11412). Commits ------- 904b54f1f8 [HttpKernel] Add basic support for language negotiation
2 parents 7fbf475 + 77e2c9c commit 5aa6e8f

File tree

17 files changed

+117
-10
lines changed

17 files changed

+117
-10
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ CHANGELOG
44
5.4
55
---
66

7+
* Add `set_locale_from_accept_language` config option to automatically set the request locale based on the `Accept-Language`
8+
HTTP request header and the `framework.enabled_locales` config option
9+
* Add `set_content_language_from_locale` config option to automatically set the `Content-Language` HTTP response header based on the Request locale
10+
* Deprecate the `framework.translator.enabled_locales`, use `framework.enabled_locales` instead
711
* Add autowiring alias for `HttpCache\StoreInterface`
812
* Deprecate the `AdapterInterface` autowiring alias, use `CacheItemPoolInterface` instead
913
* Deprecate the public `profiler` service to private

DependencyInjection/Configuration.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public function getConfigTreeBuilder()
7777
return $v;
7878
})
7979
->end()
80+
->fixXmlConfig('enabled_locale')
8081
->children()
8182
->scalarNode('secret')->end()
8283
->scalarNode('http_method_override')
@@ -86,6 +87,18 @@ public function getConfigTreeBuilder()
8687
->scalarNode('ide')->defaultNull()->end()
8788
->booleanNode('test')->end()
8889
->scalarNode('default_locale')->defaultValue('en')->end()
90+
->booleanNode('set_locale_from_accept_language')
91+
->info('Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed).')
92+
->defaultFalse()
93+
->end()
94+
->booleanNode('set_content_language_from_locale')
95+
->info('Whether to set the Content-Language HTTP header on the Response using the Request locale.')
96+
->defaultFalse()
97+
->end()
98+
->arrayNode('enabled_locales')
99+
->info('Defines the possible locales for the application. This list is used for generating translations files, but also to restrict which locales are allowed when it is set from Accept-Language header (using "set_locale_from_accept_language").')
100+
->prototype('scalar')->end()
101+
->end()
89102
->arrayNode('trusted_hosts')
90103
->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
91104
->prototype('scalar')->end()
@@ -814,6 +827,7 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $e
814827
->prototype('scalar')->end()
815828
->end()
816829
->arrayNode('enabled_locales')
830+
->setDeprecated('symfony/framework-bundle', '5.3', 'Option "%node%" at "%path%" is deprecated, set the "framework.enabled_locales" option instead.')
817831
->prototype('scalar')->end()
818832
->defaultValue([])
819833
->end()
@@ -848,7 +862,7 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $e
848862
->arrayNode('locales')
849863
->prototype('scalar')->end()
850864
->defaultValue([])
851-
->info('If not set, all locales listed under framework.translator.enabled_locales are used.')
865+
->info('If not set, all locales listed under framework.enabled_locales are used.')
852866
->end()
853867
->end()
854868
->end()

DependencyInjection/FrameworkExtension.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,9 @@ public function load(array $configs, ContainerBuilder $container)
275275
}
276276
}
277277

278+
$container->getDefinition('locale_listener')->replaceArgument(3, $config['set_locale_from_accept_language']);
279+
$container->getDefinition('response_listener')->replaceArgument(1, $config['set_content_language_from_locale']);
280+
278281
// If the slugger is used but the String component is not available, we should throw an error
279282
if (!ContainerBuilder::willBeAvailable('symfony/string', SluggerInterface::class, ['symfony/framework-bundle'])) {
280283
$container->register('slugger', 'stdClass')
@@ -297,6 +300,7 @@ public function load(array $configs, ContainerBuilder $container)
297300
$container->setParameter('kernel.http_method_override', $config['http_method_override']);
298301
$container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']);
299302
$container->setParameter('kernel.default_locale', $config['default_locale']);
303+
$container->setParameter('kernel.enabled_locales', $config['enabled_locales']);
300304
$container->setParameter('kernel.error_controller', $config['error_controller']);
301305

302306
if (($config['trusted_proxies'] ?? false) && ($config['trusted_headers'] ?? false)) {
@@ -418,11 +422,13 @@ public function load(array $configs, ContainerBuilder $container)
418422
$this->registerEsiConfiguration($config['esi'], $container, $loader);
419423
$this->registerSsiConfiguration($config['ssi'], $container, $loader);
420424
$this->registerFragmentsConfiguration($config['fragments'], $container, $loader);
421-
$this->registerTranslatorConfiguration($config['translator'], $container, $loader, $config['default_locale']);
425+
$this->registerTranslatorConfiguration($config['translator'], $container, $loader, $config['default_locale'], $config['enabled_locales']);
422426
$this->registerProfilerConfiguration($config['profiler'], $container, $loader);
423427
$this->registerWorkflowConfiguration($config['workflows'], $container, $loader);
424428
$this->registerDebugConfiguration($config['php_errors'], $container, $loader);
425-
$this->registerRouterConfiguration($config['router'], $container, $loader, $config['translator']['enabled_locales'] ?? []);
429+
// @deprecated since Symfony 5.4, in 6.0 change to:
430+
// $this->registerRouterConfiguration($config['router'], $container, $loader, $config['enabled_locales']);
431+
$this->registerRouterConfiguration($config['router'], $container, $loader, $config['translator']['enabled_locales'] ?: $config['enabled_locales']);
426432
$this->registerAnnotationsConfiguration($config['annotations'], $container, $loader);
427433
$this->registerPropertyAccessConfiguration($config['property_access'], $container, $loader);
428434
$this->registerSecretsConfiguration($config['secrets'], $container, $loader);
@@ -1228,7 +1234,7 @@ private function createVersion(ContainerBuilder $container, ?string $version, ?s
12281234
return new Reference('assets.empty_version_strategy');
12291235
}
12301236

1231-
private function registerTranslatorConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader, string $defaultLocale)
1237+
private function registerTranslatorConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader, string $defaultLocale, array $enabledLocales)
12321238
{
12331239
if (!$this->isConfigEnabled($container, $config)) {
12341240
$container->removeDefinition('console.command.translation_debug');
@@ -1252,7 +1258,9 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder
12521258
$defaultOptions['cache_dir'] = $config['cache_dir'];
12531259
$translator->setArgument(4, $defaultOptions);
12541260

1255-
$translator->setArgument(5, $config['enabled_locales']);
1261+
// @deprecated since Symfony 5.4, in 6.0 change to:
1262+
// $translator->setArgument(5, $enabledLocales);
1263+
$translator->setArgument(5, $config['enabled_locales'] ?: $enabledLocales);
12561264

12571265
$container->setParameter('translator.logging', $config['logging']);
12581266
$container->setParameter('translator.default_path', $config['default_path']);
@@ -1385,7 +1393,9 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder
13851393
return;
13861394
}
13871395

1388-
$locales = $config['enabled_locales'] ?? [];
1396+
// @deprecated since Symfony 5.4, in 6.0 change to:
1397+
// $locales = $enabledLocales;
1398+
$locales = $config['enabled_locales'] ?: $enabledLocales;
13891399

13901400
foreach ($config['providers'] as $provider) {
13911401
if ($provider['locales']) {

Resources/config/schema/symfony-1.0.xsd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,15 @@
3838
<xsd:element name="rate-limiter" type="rate_limiter" minOccurs="0" maxOccurs="1" />
3939
<xsd:element name="uid" type="uid" minOccurs="0" maxOccurs="1" />
4040
<xsd:element name="notifier" type="notifier" minOccurs="0" maxOccurs="1" />
41+
<xsd:element name="enabled-locale" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
4142
</xsd:choice>
4243

4344
<xsd:attribute name="http-method-override" type="xsd:boolean" />
4445
<xsd:attribute name="ide" type="xsd:string" />
4546
<xsd:attribute name="secret" type="xsd:string" />
4647
<xsd:attribute name="default-locale" type="xsd:string" />
48+
<xsd:attribute name="set_locale_from_accept_language" type="xsd:boolean" />
49+
<xsd:attribute name="set_content_language_from_locale" type="xsd:boolean" />
4750
<xsd:attribute name="test" type="xsd:boolean" />
4851
<xsd:attribute name="error-controller" type="xsd:string" />
4952
<xsd:attribute name="trusted-hosts" type="xsd:string" />

Resources/config/web.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@
6969
->set('response_listener', ResponseListener::class)
7070
->args([
7171
param('kernel.charset'),
72+
abstract_arg('The "set_content_language_from_locale" config value'),
73+
param('kernel.enabled_locales'),
7274
])
7375
->tag('kernel.event_subscriber')
7476

@@ -80,6 +82,8 @@
8082
service('request_stack'),
8183
param('kernel.default_locale'),
8284
service('router')->ignoreOnInvalid(),
85+
abstract_arg('The "set_locale_from_accept_language" config value'),
86+
param('kernel.enabled_locales'),
8387
])
8488
->tag('kernel.event_subscriber')
8589

Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,9 @@ protected static function getBundleDefaultConfig()
370370
'http_method_override' => true,
371371
'ide' => null,
372372
'default_locale' => 'en',
373+
'enabled_locales' => [],
374+
'set_locale_from_accept_language' => false,
375+
'set_content_language_from_locale' => false,
373376
'secret' => 's3cr3t',
374377
'trusted_hosts' => [],
375378
'trusted_headers' => [

Tests/DependencyInjection/Fixtures/php/full.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
$container->loadFromExtension('framework', [
44
'secret' => 's3cr3t',
55
'default_locale' => 'fr',
6+
'enabled_locales' => ['fr', 'en'],
67
'csrf_protection' => true,
78
'form' => [
89
'csrf_protection' => [
@@ -51,7 +52,6 @@
5152
'fallback' => 'fr',
5253
'paths' => ['%kernel.project_dir%/Fixtures/translations'],
5354
'cache_dir' => '%kernel.cache_dir%/translations',
54-
'enabled_locales' => ['fr', 'en'],
5555
],
5656
'validation' => [
5757
'enabled' => true,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
$container->loadFromExtension('framework', [
4+
'secret' => 's3cr3t',
5+
'default_locale' => 'fr',
6+
'router' => [
7+
'resource' => '%kernel.project_dir%/config/routing.xml',
8+
'type' => 'xml',
9+
'utf8' => true,
10+
],
11+
'translator' => [
12+
'enabled' => true,
13+
'fallback' => 'fr',
14+
'paths' => ['%kernel.project_dir%/Fixtures/translations'],
15+
'cache_dir' => '%kernel.cache_dir%/translations',
16+
'enabled_locales' => ['fr', 'en'],
17+
],
18+
]);

Tests/DependencyInjection/Fixtures/xml/full.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
88

99
<framework:config secret="s3cr3t" ide="file%%link%%format" default-locale="fr" http-method-override="false">
10+
<framework:enabled-locale>fr</framework:enabled-locale>
11+
<framework:enabled-locale>en</framework:enabled-locale>
1012
<framework:csrf-protection />
1113
<framework:form legacy-error-messages="false">
1214
<framework:csrf-protection field-name="_csrf"/>
@@ -28,8 +30,6 @@
2830
<framework:assets version="v1" />
2931
<framework:translator enabled="true" fallback="fr" logging="true" cache-dir="%kernel.cache_dir%/translations">
3032
<framework:path>%kernel.project_dir%/Fixtures/translations</framework:path>
31-
<framework:enabled-locale>fr</framework:enabled-locale>
32-
<framework:enabled-locale>en</framework:enabled-locale>
3333
</framework:translator>
3434
<framework:validation enabled="true" />
3535
<framework:annotations cache="file" debug="true" file-cache-dir="%kernel.cache_dir%/annotations" />
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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" ide="file%%link%%format" default-locale="fr" http-method-override="false">
10+
<framework:router resource="%kernel.project_dir%/config/routing.xml" type="xml" utf8="true" />
11+
<framework:translator enabled="true" fallback="fr" logging="true" cache-dir="%kernel.cache_dir%/translations">
12+
<framework:path>%kernel.project_dir%/Fixtures/translations</framework:path>
13+
<framework:enabled-locale>fr</framework:enabled-locale>
14+
<framework:enabled-locale>en</framework:enabled-locale>
15+
</framework:translator>
16+
</framework:config>
17+
</container>

0 commit comments

Comments
 (0)