Skip to content

[HttpFoundation] add a default Content-Language to the Response #36507

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
[HttpFoundation] add a default Content-Language to the Response
  • Loading branch information
GregoireHebert committed Apr 20, 2020
commit bb52d5d6d5638e17dd1de33b195dbbc5dbca56cb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ public function getConfigTreeBuilder()
->scalarNode('ide')->defaultNull()->end()
->booleanNode('test')->end()
->scalarNode('default_locale')->defaultValue('en')->end()
->arrayNode('available_locales')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is enabled_locales already, can't we reuse it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's in the translator section.
what if the translator is not available?
Is it still relevant to reuse it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My fear is that it will be confusing for users to have to configure this twice.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to make the translator option defaulting to the value of this new parameter if not set explicitly (and to not set it explicitly in the recipe). It’s already what we do for the default local IIRC.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or we could move the setting out of the translation subtree.
again, having twice the same setting is calling for troubles to me.

->info('An array of available locales for your application. It will help determine the locale according to the Accept-Language header.')
->beforeNormalization()->castToArray()->end()
->defaultValue([])
->prototype('scalar')->end()
->end()
->arrayNode('trusted_hosts')
->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
->prototype('scalar')->end()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ public function load(array $configs, ContainerBuilder $container)
$container->setParameter('kernel.http_method_override', $config['http_method_override']);
$container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']);
$container->setParameter('kernel.default_locale', $config['default_locale']);
$container->setParameter('kernel.available_locales', $config['available_locales']);
$container->setParameter('kernel.error_controller', $config['error_controller']);

if (!$container->hasParameter('debug.file_link_format')) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<xsd:attribute name="ide" type="xsd:string" />
<xsd:attribute name="secret" type="xsd:string" />
<xsd:attribute name="default-locale" type="xsd:string" />
<xsd:attribute name="available-locales" type="xsd:string" />
<xsd:attribute name="test" type="xsd:boolean" />
<xsd:attribute name="error-controller" type="xsd:string" />
</xsd:complexType>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
<argument type="service" id="request_stack" />
<argument>%kernel.default_locale%</argument>
<argument type="service" id="router" on-invalid="ignore" />
<argument>%kernel.available_locales%</argument>
</service>

<service id="validate_request_listener" class="Symfony\Component\HttpKernel\EventListener\ValidateRequestListener">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ protected static function getBundleDefaultConfig()
'http_method_override' => true,
'ide' => null,
'default_locale' => 'en',
'available_locales' => [],
'csrf_protection' => [
'enabled' => false,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
$container->loadFromExtension('framework', [
'secret' => 's3cr3t',
'default_locale' => 'fr',
'available_locales' => ['fr'],
'csrf_protection' => true,
'form' => [
'csrf_protection' => [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">

<framework:config secret="s3cr3t" ide="file%%link%%format" default-locale="fr" http-method-override="false">
<framework:config secret="s3cr3t" ide="file%%link%%format" default-locale="fr" available-locales="fr" http-method-override="false">
<framework:csrf-protection />
<framework:form>
<framework:csrf-protection field-name="_csrf"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
framework:
secret: s3cr3t
default_locale: fr
available_locales: ['fr']
csrf_protection: true
form:
csrf_protection:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ public function testSession()

$this->assertTrue($container->hasDefinition('session'), '->registerSessionConfiguration() loads session.xml');
$this->assertEquals('fr', $container->getParameter('kernel.default_locale'));
$this->assertEquals(['fr'], $container->getParameter('kernel.available_locales'));
$this->assertEquals('session.storage.native', (string) $container->getAlias('session.storage'));
$this->assertEquals('session.handler.native_file', (string) $container->getAlias('session.handler'));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ imports:
framework:
secret: '%secret%'
default_locale: '%env(LOCALE)%'
available_locales: ['%env(LOCALE)%']
session:
cookie_httponly: '%env(bool:COOKIE_HTTPONLY)%'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ imports:
framework:
secret: '%secret%'
default_locale: '%env(LOCALE)%'
available_locales: ['%env(LOCALE)%']
translator:
fallbacks:
- '%env(LOCALE)%'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ imports:
framework:
secret: '%secret%'
default_locale: '%env(LOCALE)%'
available_locales: ['%env(LOCALE)%']
translator:
fallbacks:
- '%env(LOCALE)%'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ framework:
form: true
test: true
default_locale: en
available_locales: ['en', 'fr']
session:
storage_id: session.storage.mock_file

Expand Down
4 changes: 4 additions & 0 deletions src/Symfony/Component/HttpFoundation/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,10 @@ public function prepare(Request $request)
$headers->set('Content-Length', $length);
}
}

if (!$headers->has('Content-Language')) {
$headers->set('Content-Language', $request->getLocale());
}
}

// Fix protocol
Expand Down
22 changes: 22 additions & 0 deletions src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,28 @@ public function testPrepareSetsCookiesSecure()
$this->assertTrue($cookie->isSecure());
}

public function testPrepareSetsContentLanguage()
{
$response = new Response('foo');
$request = new Request();
$request->setLocale('fr');
$response->prepare($request);

$this->assertSame('fr', $response->headers->get('Content-Language'));
}

public function testPrepareDoNotOverrideContentLanguage()
{
$response = new Response('foo');
$response->headers->set('Content-Language', 'fr');

$request = new Request();
$request->setLocale('de');
$response->prepare($request);

$this->assertSame('fr', $response->headers->get('Content-Language'));
}

public function testSetCache()
{
$response = new Response();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ class LocaleListener implements EventSubscriberInterface
{
private $router;
private $defaultLocale;
private $availableLocales;
private $requestStack;

public function __construct(RequestStack $requestStack, string $defaultLocale = 'en', RequestContextAwareInterface $router = null)
public function __construct(RequestStack $requestStack, string $defaultLocale = 'en', RequestContextAwareInterface $router = null, ?array $availableLocales = null)
{
$this->availableLocales = $availableLocales;
$this->defaultLocale = $defaultLocale;
$this->requestStack = $requestStack;
$this->router = $router;
Expand Down Expand Up @@ -65,6 +67,11 @@ private function setLocale(Request $request)
if ($locale = $request->attributes->get('_locale')) {
$request->setLocale($locale);
}

if (null === $locale && null !== $this->availableLocales &&
$preferredLanguage = $request->getPreferredLanguage($this->availableLocales)) {
$request->setLocale($preferredLanguage);
}
}

private function setRouterContext(Request $request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,72 @@ public function testRequestLocaleIsNotOverridden()
$this->assertEquals('de', $request->getLocale());
}

public function testRequestPreferredLocaleFromAcceptLanguageHeader()
{
$request = Request::create('/');
$request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']);

$listener = new LocaleListener($this->requestStack, 'de', null, ['de', 'fr']);
$event = $this->getEvent($request);

$listener->setDefaultLocale($event);
$listener->onKernelRequest($event);
$this->assertEquals('fr', $request->getLocale());
}

public function testRequestSecondPreferredLocaleFromAcceptLanguageHeader()
{
$request = Request::create('/');
$request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']);

$listener = new LocaleListener($this->requestStack, 'de', null, ['de', 'en']);
$event = $this->getEvent($request);

$listener->setDefaultLocale($event);
$listener->onKernelRequest($event);
$this->assertEquals('en', $request->getLocale());
}

public function testRequestUnavailablePreferredLocaleFromAcceptLanguageHeader()
{
$request = Request::create('/');
$request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']);

$listener = new LocaleListener($this->requestStack, 'de', null, ['de', 'it']);
$event = $this->getEvent($request);

$listener->setDefaultLocale($event);
$listener->onKernelRequest($event);
$this->assertEquals('de', $request->getLocale());
}

public function testRequestNoLocaleFromAcceptLanguageHeader()
{
$request = Request::create('/');
$request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']);

$listener = new LocaleListener($this->requestStack, 'de');
$event = $this->getEvent($request);

$listener->setDefaultLocale($event);
$listener->onKernelRequest($event);
$this->assertEquals('de', $request->getLocale());
}

public function testRequestAttributeLocaleNotOverridenFromAcceptLanguageHeader()
{
$request = Request::create('/');
$request->attributes->set('_locale', 'it');
$request->headers->set('Accept-Language', ['Accept-Language: fr-FR,fr;q=0.9,en-GB;q=0.8,en;q=0.7,en-US;q=0.6,es;q=0.5']);

$listener = new LocaleListener($this->requestStack, 'de', null, ['fr', 'en']);
$event = $this->getEvent($request);

$listener->setDefaultLocale($event);
$listener->onKernelRequest($event);
$this->assertEquals('it', $request->getLocale());
}

private function getEvent(Request $request): RequestEvent
{
return new RequestEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $request, HttpKernelInterface::MASTER_REQUEST);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,19 @@ public function testFiltersSetsNonDefaultCharsetIfNotOverriddenOnNonTextContentT

$this->assertEquals('ISO-8859-15', $response->getCharset());
}

public function testSetContentLanguageHeaderWhenEmptyAccceptLocaleHeaders()
{
$listener = new ResponseListener('ISO-8859-15');
$this->dispatcher->addListener(KernelEvents::RESPONSE, [$listener, 'onKernelResponse'], 1);

$response = new Response('content');
$request = Request::create('/');
$request->setLocale('fr');

$event = new ResponseEvent($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response);
$this->dispatcher->dispatch($event, KernelEvents::RESPONSE);

$this->assertEquals('fr', $response->headers->get('Content-Language'));
}
}