Skip to content

Commit 2c0c879

Browse files
GregoireHebertchalasr
authored andcommitted
[HttpKernel] Add basic support for language negotiation
1 parent 5306f8a commit 2c0c879

File tree

4 files changed

+140
-2
lines changed

4 files changed

+140
-2
lines changed

EventListener/LocaleListener.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,16 @@ class LocaleListener implements EventSubscriberInterface
3232
private $router;
3333
private $defaultLocale;
3434
private $requestStack;
35+
private $useAcceptLanguageHeader;
36+
private $enabledLocales;
3537

36-
public function __construct(RequestStack $requestStack, string $defaultLocale = 'en', RequestContextAwareInterface $router = null)
38+
public function __construct(RequestStack $requestStack, string $defaultLocale = 'en', RequestContextAwareInterface $router = null, bool $useAcceptLanguageHeader = false, array $enabledLocales = [])
3739
{
3840
$this->defaultLocale = $defaultLocale;
3941
$this->requestStack = $requestStack;
4042
$this->router = $router;
43+
$this->useAcceptLanguageHeader = $useAcceptLanguageHeader;
44+
$this->enabledLocales = $enabledLocales;
4145
}
4246

4347
public function setDefaultLocale(KernelEvent $event)
@@ -64,6 +68,8 @@ private function setLocale(Request $request)
6468
{
6569
if ($locale = $request->attributes->get('_locale')) {
6670
$request->setLocale($locale);
71+
} elseif ($this->useAcceptLanguageHeader && $this->enabledLocales && ($preferredLanguage = $request->getPreferredLanguage($this->enabledLocales))) {
72+
$request->setLocale($preferredLanguage);
6773
}
6874
}
6975

EventListener/ResponseListener.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@
2525
class ResponseListener implements EventSubscriberInterface
2626
{
2727
private $charset;
28+
private $addContentLanguageHeader;
2829

29-
public function __construct(string $charset)
30+
public function __construct(string $charset, bool $addContentLanguageHeader = false)
3031
{
3132
$this->charset = $charset;
33+
$this->addContentLanguageHeader = $addContentLanguageHeader;
3234
}
3335

3436
/**
@@ -46,6 +48,11 @@ public function onKernelResponse(ResponseEvent $event)
4648
$response->setCharset($this->charset);
4749
}
4850

51+
if ($this->addContentLanguageHeader && !$response->isInformational() && !$response->isEmpty() && !$response->headers->has('Content-Language')) {
52+
$response->headers->set('Content-Language', $event->getRequest()->getLocale());
53+
$response->setVary('Accept-Language', false);
54+
}
55+
4956
$response->prepare($event->getRequest());
5057
}
5158

Tests/EventListener/LocaleListenerTest.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,85 @@ public function testRequestLocaleIsNotOverridden()
117117
$this->assertEquals('de', $request->getLocale());
118118
}
119119

120+
public function testRequestPreferredLocaleFromAcceptLanguageHeader()
121+
{
122+
$request = Request::create('/');
123+
$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']);
124+
125+
$listener = new LocaleListener($this->requestStack, 'de', null, true, ['de', 'fr']);
126+
$event = $this->getEvent($request);
127+
128+
$listener->setDefaultLocale($event);
129+
$listener->onKernelRequest($event);
130+
$this->assertEquals('fr', $request->getLocale());
131+
}
132+
133+
public function testRequestSecondPreferredLocaleFromAcceptLanguageHeader()
134+
{
135+
$request = Request::create('/');
136+
$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']);
137+
138+
$listener = new LocaleListener($this->requestStack, 'de', null, true, ['de', 'en']);
139+
$event = $this->getEvent($request);
140+
141+
$listener->setDefaultLocale($event);
142+
$listener->onKernelRequest($event);
143+
$this->assertEquals('en', $request->getLocale());
144+
}
145+
146+
public function testDontUseAcceptLanguageHeaderIfNotEnabled()
147+
{
148+
$request = Request::create('/');
149+
$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']);
150+
151+
$listener = new LocaleListener($this->requestStack, 'de', null, false, ['de', 'en']);
152+
$event = $this->getEvent($request);
153+
154+
$listener->setDefaultLocale($event);
155+
$listener->onKernelRequest($event);
156+
$this->assertEquals('de', $request->getLocale());
157+
}
158+
159+
public function testRequestUnavailablePreferredLocaleFromAcceptLanguageHeader()
160+
{
161+
$request = Request::create('/');
162+
$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']);
163+
164+
$listener = new LocaleListener($this->requestStack, 'de', null, true, ['de', 'it']);
165+
$event = $this->getEvent($request);
166+
167+
$listener->setDefaultLocale($event);
168+
$listener->onKernelRequest($event);
169+
$this->assertEquals('de', $request->getLocale());
170+
}
171+
172+
public function testRequestNoLocaleFromAcceptLanguageHeader()
173+
{
174+
$request = Request::create('/');
175+
$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']);
176+
177+
$listener = new LocaleListener($this->requestStack, 'de', null, true);
178+
$event = $this->getEvent($request);
179+
180+
$listener->setDefaultLocale($event);
181+
$listener->onKernelRequest($event);
182+
$this->assertEquals('de', $request->getLocale());
183+
}
184+
185+
public function testRequestAttributeLocaleNotOverridenFromAcceptLanguageHeader()
186+
{
187+
$request = Request::create('/');
188+
$request->attributes->set('_locale', 'it');
189+
$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']);
190+
191+
$listener = new LocaleListener($this->requestStack, 'de', null, true, ['fr', 'en']);
192+
$event = $this->getEvent($request);
193+
194+
$listener->setDefaultLocale($event);
195+
$listener->onKernelRequest($event);
196+
$this->assertEquals('it', $request->getLocale());
197+
}
198+
120199
private function getEvent(Request $request): RequestEvent
121200
{
122201
return new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST);

Tests/EventListener/ResponseListenerTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,50 @@ public function testFiltersSetsNonDefaultCharsetIfNotOverriddenOnNonTextContentT
9292

9393
$this->assertEquals('ISO-8859-15', $response->getCharset());
9494
}
95+
96+
public function testSetContentLanguageHeaderWhenEmptyAndAtLeast2EnabledLocalesAreConfigured()
97+
{
98+
$listener = new ResponseListener('ISO-8859-15', true, ['fr', 'en']);
99+
$this->dispatcher->addListener(KernelEvents::RESPONSE, [$listener, 'onKernelResponse'], 1);
100+
101+
$response = new Response('content');
102+
$request = Request::create('/');
103+
$request->setLocale('fr');
104+
105+
$event = new ResponseEvent($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response);
106+
$this->dispatcher->dispatch($event, KernelEvents::RESPONSE);
107+
108+
$this->assertEquals('fr', $response->headers->get('Content-Language'));
109+
}
110+
111+
public function testNotOverrideContentLanguageHeaderWhenNotEmpty()
112+
{
113+
$listener = new ResponseListener('ISO-8859-15', true, ['de']);
114+
$this->dispatcher->addListener(KernelEvents::RESPONSE, [$listener, 'onKernelResponse'], 1);
115+
116+
$response = new Response('content');
117+
$response->headers->set('Content-Language', 'mi, en');
118+
$request = Request::create('/');
119+
$request->setLocale('de');
120+
121+
$event = new ResponseEvent($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response);
122+
$this->dispatcher->dispatch($event, KernelEvents::RESPONSE);
123+
124+
$this->assertEquals('mi, en', $response->headers->get('Content-Language'));
125+
}
126+
127+
public function testNotSetContentLanguageHeaderWhenDisabled()
128+
{
129+
$listener = new ResponseListener('ISO-8859-15', false);
130+
$this->dispatcher->addListener(KernelEvents::RESPONSE, [$listener, 'onKernelResponse'], 1);
131+
132+
$response = new Response('content');
133+
$request = Request::create('/');
134+
$request->setLocale('fr');
135+
136+
$event = new ResponseEvent($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response);
137+
$this->dispatcher->dispatch($event, KernelEvents::RESPONSE);
138+
139+
$this->assertNull($response->headers->get('Content-Language'));
140+
}
95141
}

0 commit comments

Comments
 (0)