Here is a way to do this using direct symfony. This may seem a bit hacky, because for each action you need to specify 2 routes, so if someone can think of a better way, Iβm all ears.
First, I would define some configuration parameter for all valid locales and list the first one by default
parameters.yml.dist:
parameters: accepted_locales: [en, es, fr]
Then, make sure your controller routes match when _locale
installed and not installed. Use the same route name for both except the suffix without _locale
with a separator of type |
:
public function testAction(Request $request, $var, $_locale = null) {
Next, determine the service that will listen to the Controller event and pass the received locales to you:
<service id="kernel.listener.locale" class="My\Bundle\EventListener\LocaleListener"> <tag name="kernel.event_listener" event="kernel.controller" method="onKernelController" /> <argument>%accepted_locales%</argument> </service>
Now use the service to determine if _locale
is _locale
on your route, and if not, determine the locale based on the HTTP_ACCEPT_LANGUAGE
header and redirect the route containing it. Here is an example of a listener who will do this (I added comments to explain what I'm doing):
namespace NAB\UtilityBundle\EventListener; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\Event\FilterControllerEvent; class ControllerListener { private $acceptedLocales; public function __construct(array $acceptedLocales) { $this->acceptedLocales = $acceptedLocales; } public function onKernelController(FilterControllerEvent $event) { if (HttpKernelInterface::MASTER_REQUEST != $event->getRequestType()) { return; } $controller = $event->getController(); if (!is_array($controller)) { return; } $request = $event->getRequest(); $params = $request->attributes->get('_route_params'); // return if _locale is already set on the route if ($request->attributes->get('_locale')) { return; } // if the user has accepted languages set, set the locale on the first match found $languages = $request->server->get('HTTP_ACCEPT_LANGUAGE'); if (!empty($languages)) { foreach (explode(',', $languages) as $language) { $splits = array(); $pattern = '/^(?P<primarytag>[a-zA-Z]{2,8})(?:-(?P<subtag>[a-zA-Z]{2,8}))?(?:(?:;q=)(?P<quantifier>\d\.\d))?$/'; // if the user locale matches the accepted locales, set _locale in the route params if (preg_match($pattern, $language, $splits) && in_array($splits['primarytag'], $this->acceptedLocales)) { $params['_locale'] = $splits['primarytag']; // stop checking once the first match is found break; } } } // if no locale was found, default to the first accepted locale if (!$params['_locale']) { $params['_locale'] = $this->acceptedLocales[0]; } // drop the '|' to get the appropriate route name list($localeRoute) = explode('|', $request->attributes->get('_route')); // attempt get the redirect URL but return if it could not be found try { $redirectUrl = $controller[0]->generateUrl($localeRoute, $params); } catch (\Exception $e) { return; } // set the controller response to redirect to the route we just created $event->setController(function() use ($redirectUrl) { return new RedirectResponse($redirectUrl); }); } }
For more information about configuring the filter before the filter on the controller, check out the Symfony documentation here . If you use something like this, be very careful to correctly identify each route name.