Symfony2 default locale in routing

Solution 1:

If someone is interested in, I succeeded to put a prefix on my routing.yml without using other bundles.

So now, thoses URLs work :

www.example.com/
www.example.com//home/
www.example.com/fr/home/
www.example.com/en/home/

Edit your app/config/routing.yml:

ex_example:
    resource: "@ExExampleBundle/Resources/config/routing.yml"
    prefix:   /{_locale}
    requirements:
        _locale: |fr|en # put a pipe "|" first

Then, in you app/config/parameters.yml, you have to set up a locale

parameters:
    locale: en

With this, people can access to your website without enter a specific locale.

Solution 2:

You can define multiple patterns like this:

example_default:
  pattern:   /example
  defaults:  { _controller: ExampleBundle:Example:index, _locale: fr }

example:
  pattern:   /{_locale}/example
  defaults:  { _controller: ExampleBundle:Example:index}
  requirements:
      _locale:  fr|en

You should be able to achieve the same sort of thing with annotations:

/**
 * @Route("/example", defaults={"_locale"="fr"})
 * @Route("/{_locale}/example", requirements={"_locale" = "fr|en"})
 */

Hope that helps!

Solution 3:

This is what I use for automatic locale detection and redirection, it works well and doesn't require lengthy routing annotations:

routing.yml

The locale route handles the website's root and then every other controller action is prepended with the locale.

locale:
  path: /
  defaults:  { _controller: AppCoreBundle:Core:locale }

main:
  resource: "@AppCoreBundle/Controller"
  prefix: /{_locale}
  type: annotation
  requirements:
    _locale: en|fr

CoreController.php

This detects the user's language and redirects to the route of your choice. I use home as a default as that it the most common case.

public function localeAction($route = 'home', $parameters = array())
{
    $this->getRequest()->setLocale($this->getRequest()->getPreferredLanguage(array('en', 'fr')));

    return $this->redirect($this->generateUrl($route, $parameters));
}

Then, the route annotations can simply be:

/**
 * @Route("/", name="home")
 */
public function indexAction(Request $request)
{
    // Do stuff
}

Twig

The localeAction can be used to allow the user to change the locale without navigating away from the current page:

<a href="{{ path(app.request.get('_route'), app.request.get('_route_params')|merge({'_locale': targetLocale })) }}">{{ targetLanguage }}</a>

Clean & simple!

Solution 4:

The Joseph Astrahan's solution of LocalRewriteListener works except for route with params because of $routePath == "/{_locale}".$path)

Ex : $routePath = "/{_locale}/my/route/{foo}" is different of $path = "/{_locale}/my/route/bar"

I had to use UrlMatcher (link to Symfony 2.7 api doc) for matching the actual route with the url.

I change the isLocaleSupported for using browser local code (ex : fr -> fr_FR). I use the browser locale as key and the route locale as value. I have an array like this array(['fr_FR'] => ['fr'], ['en_GB'] => 'en'...) (see the parameters file below for more information)

The changes :

  • Check if the local given in request is suported. If not, use the default locale.
  • Try to match the path with the app route collection. If not do nothing (the app throw a 404 if route doesn't exist). If yes, redirect with the right locale in route param.

Here is my code. Works for any route with or without param. This add the locale only when {_local} is set in the route.

Routing file (in my case, the one in app/config)

app:
    resource: "@AppBundle/Resources/config/routing.yml"
    prefix:   /{_locale}/
    requirements:
        _locale: '%app.locales%'
    defaults: { _locale: %locale%}

The parameter in app/config/parameters.yml file

locale: fr
app.locales: fr|gb|it|es
locale_supported:
    fr_FR: fr
    en_GB: gb
    it_IT: it
    es_ES: es

services.yml

app.eventListeners.localeRewriteListener:
    class: AppBundle\EventListener\LocaleRewriteListener
    arguments: ["@router", "%kernel.default_locale%", "%locale_supported%"]
    tags:
        - { name: kernel.event_subscriber }

LocaleRewriteListener.php

<?php
namespace AppBundle\EventListener;

use Symfony\Component\HttpFoundation\RedirectResponse;

use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;

class LocaleRewriteListener implements EventSubscriberInterface
{
    /**
     * @var Symfony\Component\Routing\RouterInterface
     */
    private $router;

    /**
    * @var routeCollection \Symfony\Component\Routing\RouteCollection
    */
    private $routeCollection;

    /**
    * @var urlMatcher \Symfony\Component\Routing\Matcher\UrlMatcher;
    */
    private $urlMatcher;

    /**
     * @var string
     */
    private $defaultLocale;

    /**
     * @var array
     */
    private $supportedLocales;

    /**
     * @var string
     */
    private $localeRouteParam;

    public function __construct(RouterInterface $router, $defaultLocale = 'fr', array $supportedLocales, $localeRouteParam = '_locale')
    {
        $this->router = $router;
        $this->routeCollection = $router->getRouteCollection();
        $this->defaultLocale = $defaultLocale;
        $this->supportedLocales = $supportedLocales;
        $this->localeRouteParam = $localeRouteParam;
        $context = new RequestContext("/");
        $this->matcher = new UrlMatcher($this->routeCollection, $context);
    }

    public function isLocaleSupported($locale)
    {
        return array_key_exists($locale, $this->supportedLocales);
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        //GOAL:
        // Redirect all incoming requests to their /locale/route equivalent when exists.
        // Do nothing if it already has /locale/ in the route to prevent redirect loops
        // Do nothing if the route requested has no locale param

        $request = $event->getRequest();
        $baseUrl = $request->getBaseUrl();
        $path = $request->getPathInfo();

        //Get the locale from the users browser.
        $locale = $request->getPreferredLanguage();

        if ($this->isLocaleSupported($locale)) {
            $locale = $this->supportedLocales[$locale];
        } else if ($locale == ""){
            $locale = $request->getDefaultLocale();
        }

        $pathLocale = "/".$locale.$path;

        //We have to catch the ResourceNotFoundException
        try {
            //Try to match the path with the local prefix
            $this->matcher->match($pathLocale);
            $event->setResponse(new RedirectResponse($baseUrl.$pathLocale));
        } catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {

        } catch (\Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {

        }
    }

    public static function getSubscribedEvents()
    {
        return array(
            // must be registered before the default Locale listener
            KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
        );
    }
}

Solution 5:

Symfony3

app:
resource: "@AppBundle/Controller/"
type:     annotation
prefix: /{_locale}
requirements:
    _locale: en|bg|  # put a pipe "|" last