Search code examples
symfonylocalizationtranslationlocale

Symfony2 locale detection: not considering _locale in session


I'm trying to implement a LocaleListener that detects user's preferred language (considering Accept-Language header) and stores it in session to avoid checking it every request. I've developed the code below to accomplish this:

public function onKernelRequest(GetResponseEvent $event) {
    $request = $event->getRequest();

    if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
        return;
    }

    $preferredLocale = $request->getPreferredLanguage($this->availableLocales);

    if ($this->container->has('session')) {
        $session = $this->container->get('session');
        if (!$session->has('_locale')) {
            $session->set('_locale', $preferredLocale);
        }
    } else {
        $request->setLocale($preferredLocale);
    }
}

The code is working, the preferred language is being stored in session, but symfony isn't considering the locale stored in session to translate strings. In my case, my preferred language was 'pt_BR' and when I escape:

{{ app.request.locale }}

symfony is escaping 'en'. Shouldn't symfony be considering the value stored in session('_locale') to define request locale? Is this a correct behavior? How can I accomplish that?


Solution

  • Here is a working language listener. the second method is to change the language to the users preferences, which the user chooses. You can omit this method, if your user haven't the facility to define their language.

    <?php
    
    namespace Acme\UserBundle\EventListener;
    use Symfony\Component\HttpFoundation\Session\Session;
    use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
    use Symfony\Component\HttpKernel\Event\GetResponseEvent;
    use Symfony\Component\HttpKernel\HttpKernelInterface;
    
    class LanguageListener
    {
        private $session;
    
        public function setSession(Session $session)
        {
            $this->session = $session;
        }
    
        /**
         * kernel.request event. If a guest user doesn't have an opened session, locale is equal to
         * "undefined" as configured by default in parameters.ini. If so, set as a locale the user's
         * preferred language.
         *
         * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
         */
        public function setLocaleForUnauthenticatedUser(GetResponseEvent $event)
        {
            if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
                return;
            }
            $request = $event->getRequest();
            if ('undefined' == $request->getLocale()) {
                if ($locale = $request->getSession()->get('_locale')) {
                    $request->setLocale($locale);
                } else {
                    $request->setLocale($request->getPreferredLanguage());
                }
            }
        }
    
        /**
         * security.interactive_login event. If a user chose a language in preferences, it would be set,
         * if not, a locale that was set by setLocaleForUnauthenticatedUser remains.
         *
         * @param \Symfony\Component\Security\Http\Event\InteractiveLoginEvent $event
         */
        public function setLocaleForAuthenticatedUser(InteractiveLoginEvent $event)
        {
            $user = $event->getAuthenticationToken()->getUser();
    
            if ($lang = $user->getLanguage()) {
                $this->session->set('_locale', $lang);
            }
        }
    }
    

    in your services.yml:

    services:
        acme.language.interactive_login_listener:
            class: Acme\UserBundle\EventListener\LanguageListener
            calls:
                - [ setSession, [@session] ]
            tags:
                - { name: kernel.event_listener, event: security.interactive_login, method: setLocaleForAuthenticatedUser }
    
        acme.language.kernel_request_listener:
            class: Acme\UserBundle\EventListener\LanguageListener
            tags:
                - { name: kernel.event_listener, event: kernel.request, method: setLocaleForUnauthenticatedUser }
    

    Oh, and you have to define an undefined fallback_language in config.yml to get it work.

    framework:
        translator:      { fallback: "undefined" }
        default_locale:  "en"