Skip to content

Instantly share code, notes, and snippets.

@monteiro
Last active March 3, 2024 14:23
Show Gist options
  • Save monteiro/de139f72f2047fec4e80420d9bd98ffe to your computer and use it in GitHub Desktop.
Save monteiro/de139f72f2047fec4e80420d9bd98ffe to your computer and use it in GitHub Desktop.
Redirect to page according to the Accept-Language header using Symfony 5
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class HomepageController extends AbstractController
{
/**
* @Route("/", name="homepage")
*/
public function index(Request $request)
{
return $this->redirect($this->generateUrl('menu', [
'_locale' => $request->getLocale()
]));
}
}
<?php
namespace App\Tests\Controller;
use App\Controller\HomepageController;
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class HomepageControllerTest extends WebTestCase
{
/**
* @test
* @dataProvider localesProvider
*/
public function itShouldRedirectFromAcceptLanguageHeader(string $locale, $expectedUri)
{
$client = static::createClient();
$client->request(
'GET',
'/',
[],
[],
[
'HTTP_ACCEPT_LANGUAGE' => $locale
]
);
$location = $client->getResponse()->headers->get('Location');
if ($locale !== '') {
$this->assertEquals($expectedUri, $location);
} else {
$expectedUri = '/en/ementa';
$this->assertEquals($expectedUri, $location);
}
}
public function localesProvider(): array
{
return [
'english' => [
'en',
'/en/ementa'
],
'english en-US' => [
'en-US',
'/en/ementa'
],
'french' => [
'fr',
'/fr/ementa'
],
'german' => [
'de',
'/de/ementa'
],
'portuguese' => [
'pt',
'/pt/ementa',
],
'portuguese brazillian' => [
'pt-BR',
'/pt/ementa'
],
'spanish' => [
'es',
'/es/ementa'
],
'empty header should give default language english' => [
'',
'/en/ementa'
]
];
}
}
<?php
namespace App\EventSubscriber;
use Negotiation\AcceptLanguage;
use Negotiation\LanguageNegotiator;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\AcceptHeader;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Routing\RouterInterface;
class LanguageListenerSubscriber implements EventSubscriberInterface
{
private array $locales;
private const DEFAULT_LOCALE = 'en';
/**
* @var RouterInterface
*/
private RouterInterface $router;
private array $priorities;
public function __construct(array $locales, array $priorities, RouterInterface $router)
{
$this->locales = $locales;
$this->priorities = $priorities;
$this->router = $router;
}
public function onKernelRequest(RequestEvent $event)
{
$request = $event->getRequest();
// ignore request because of language switcher
if ($request->getRequestUri() === $this->router->generate('menu')) {
return;
}
$request->setLocale(self::DEFAULT_LOCALE);
$acceptLanguageHeader = $request->headers->get('Accept-Language');
if (empty($acceptLanguageHeader)) {
return;
}
$priorities = $this->priorities;
$bestLanguage = (new LanguageNegotiator())->getBest(
$acceptLanguageHeader,
$priorities
);
if ($bestLanguage !== null) {
$bestLanguageLocale = $bestLanguage->getValue();
$bestLanguageLocaleFirst = explode('-', $bestLanguageLocale)[0] ?? '';
if ($this->languageSupported($this->locales, $bestLanguageLocaleFirst)) {
$request->setLocale($bestLanguageLocaleFirst);
}
}
}
private function languageSupported(array $locales, string $selectedLocale): bool
{
foreach ($locales as $locale) {
if ($locale['locale'] === $selectedLocale) {
return true;
}
}
return false;
}
public static function getSubscribedEvents()
{
return [
'kernel.request' => 'onKernelRequest',
];
}
}
<?php
namespace App\Controller;
use App\Repository\CategoryRepository;
use App\Repository\MealItemRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class MenuController extends AbstractController
{
/**
* @Route("/{_locale}/ementa", name="menu",
* locale="pt",
* requirements={
* "_locale": "pt|es|en|fr|it|de"
* }
* )
*/
public function indexWithLanguage(
MealItemRepository $mealItemRepository,
CategoryRepository $categoryRepository,
Request $request
) {
$languageSelected = $request->getLocale();
$mealItemsByCategory = $this->getMealItemsByCategory($categoryRepository, $mealItemRepository, $languageSelected);
return $this->render('menu/index.html.twig', [
'mealItemsByCategory' => $mealItemsByCategory,
'languageSelected' => $languageSelected
]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment