Redirects to browser preferred locale if this locale is supported by the application.
config/services.yml
App\EventListener\LocaleRedirectToClientPreference:
arguments:
$router: '@router'
$defaultLocale: '%kernel.default_locale%' # framework.default_locale in config/packages/translation.yaml
$supportedLocales: '%app.supported_locales%'
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
config/services.yaml
parameters:
app.supported_locales:
- en
- fr
src/EventListener/LocaleRedirectToClientPreference.php
<?php
namespace App\EventListener;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;
/**
* Class LocaleRedirectToClientPreference
* @package AppBundle\EventListener
*
* Redirects to browser preferred locale if this locale is supported by the application.
*/
class LocaleRedirectToClientPreference
{
/**
* @var string
*/
private $defaultLocale;
/**
* @var RouterInterface
*/
private $router;
/**
* @var RouteCollection
*/
private $routeCollection;
/**
* @var array
*/
private $supportedLocales;
/**
* LocaleRedirectToClientPreference constructor.
* @param RouterInterface $router
* @param string $defaultLocale
* @param array $supportedLocales
*/
public function __construct(RouterInterface $router, string $defaultLocale, array $supportedLocales)
{
$this->router = $router;
$this->routeCollection = $router->getRouteCollection();
$this->defaultLocale = $defaultLocale;
$this->supportedLocales = $supportedLocales;
}
/*
* Checks client preferred locales against locales supported by the application and returns first match.
* Returns empty string if no match is found.
*/
private function localeResolver(array $clientPreferredLocales): string
{
foreach ($clientPreferredLocales as $value) {
$locale = substr($value, 0, 2);
if (in_array($locale, $this->supportedLocales)) {
return $locale;
}
}
return '';
}
/*
* Redirects all incoming requests to their /locale/route equivalent as long as the route supports _locale element.
* Does nothing if it already has /locale/ in the route to prevent redirect loops or forcing a locale unto an user
* whom already chose one.
*/
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if ($request->isMethod('get') === false) {
return;
}
$path = $request->getPathInfo();
$routeExists = false;
// Checks if requested route exists.
foreach ($this->routeCollection as $routeObject) {
$routePath = $routeObject->getPath();
if ($routePath === '/{_locale}' . $path) {
$routeExists = true;
break;
}
}
if ($routeExists === false) {
return;
}
// Gets the preferred locales from client.
$clientPreferredLocales = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
$resolvedLocale = $this->localeResolver($clientPreferredLocales);
// If client preferred locale is not in the list of supported locales then sets to defaultLocale from config.yml.
if (empty($resolvedLocale)) {
$resolvedLocale = $request->getDefaultLocale();
}
$event->setResponse(new RedirectResponse('/' . $resolvedLocale . $path));
}
}