Symfony – persistant translate with FOSUserBundle

This page is about setting a choice between different languages for anonymous users, and make it persistant during navigation. And how to set the language defined by a FOS User.

Use JMSI18nRoutingBundle

Usage

JMSI18nRoutingBundle is a good bundle for translation. It can generate dictionaries from Controllers and from routing.
Go to the JMSI18nRoutingBundle website for more details.
Here are good command lines to generate dictionaries
Only translation:

# php app/console translation:extract en  --bundle=AcmeSiteBundle --output-dir=./src/Acme/SiteBundle/Resources/translations --default-output-format="yml"

Translation and routing:

# php app/console translation:extract en  --bundle=AcmeSiteBundle --output-dir=./src/Amce/SiteBundle/Resources/translations --enable-extractor=jms_i18n_routing --default-output-format="yml"

And a way to replace translate current url

{{ path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')|merge({'_locale': 'fr'})) }}

Installation

In order to install JMSI18nRoutingBundle, don’t forget to add in AppKernel.php:

// Translation
new JMS\I18nRoutingBundle\JMSI18nRoutingBundle(),
new JMS\TranslationBundle\JMSTranslationBundle(),

And add in your confing.yml configuration file:

jms_i18n_routing:
    default_locale: en
    locales: [fr, de, en]
    strategy: prefix_except_default

see the official documentation for other configurations

Persistant translation

in the src/AppBundle/EventListener directory, create  LocaleListener.php file, and add

# src/AppBundle/EventListener/LocaleListener.php
namespace AppBundle\EventListener;

use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class LocaleListener implements EventSubscriberInterface
{
    private $defaultLocale;
    private $localeList;

    public function __construct($defaultLocale = 'en')
    {
        $this->defaultLocale = $defaultLocale;
        // Set a list of available translations
        $this->localeList = array('fr', 'en', 'de');
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        $request = $event->getRequest();
     
        // No _locale found is session
        if (!$request->hasPreviousSession() || is_null($request->getSession()->get('_locale'))) {
            $preferredLocale = $request->getPreferredLanguage();
            // Match if user preference is the available translations
            foreach($this->localeList as $locale) {
                if (preg_match('/' . $locale . '/', $preferredLocale)) {
                    $request->getSession()->set('_locale', $locale);
                    $request->setLocale($locale);
                } 
            }
            // Not found in available translations, set parameters default
            if (!$request->hasPreviousSession()) {
                $request->getSession()->set('_locale', $this->defaultLocale);
            }
            return;
        }
        
        // _locale found in controller
        if ($locale = $request->attributes->get('_locale')) {
            $request->getSession()->set('_locale', $locale);
            $request->setLocale($locale);
        }
        // _locale found in session
        elseif($locale = $request->getSession()->get('_locale')) {
            $request->setLocale($locale);
        }
    }
    
    public static function getSubscribedEvents()
    {
        return array(
            // must be registered before the default Locale listener
            KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
        );
    }
}

in /app/config/services.yml, add

# /app/config/services.yml
services:
    app.locale_listener:
        class: AppBundle\EventListener\LocaleListener
        arguments: ["%kernel.default_locale%"]
        tags:
            - { name: kernel.event_subscriber }

in the app/config/routing.yml add:

# src/Acme/SiteBundle/Ressources/config/routing.yml

_locale:
    path:     /{_locale}
    defaults: { _controller: AcmeSiteBundle:Locale:index }
    requirements: { _locale: en|fr|de }

Finaly in the src/Acme/SiteBundle/Controller/LocaleController.php add:

# src/Acme/SiteBundle/Controller/LocaleController.php
namespace Acme\SiteBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

use Symfony\Component\HttpFoundation\Request;

class LocaleController extends Controller
{
    public function indexAction(Request $request)
    {
        return $this->render('AcmeSiteBundle:Homepage:index.html.twig');
    }
}

Now you can change translation using:
http://yoursite.ext/en
http://yoursite.ext/fr
http://yoursite.ext/de

See the official documentation to translate your twig files and Controllers :
http://symfony.com/fr/doc/current/book/translation.html

And use the command line to generate dictionaries:

# php app/console translation:update --output-format="yml" --dump-messages --force fr AcmeSiteBundle

Dictionaries should appear in src/Acme/SiteBundle/Resources/translations directory

Persistant translation defined by a FOS User

in the src/AppBundle/EventListener directory, create UserLocaleListener.php file, and add

# src/AppBundle/EventListener/UserLocaleListener.php
namespace AppBundle\EventListener;

use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

/**
 * Stores the locale of the user in the session after the
 * login. This can be used by the LocaleListener afterwards.
 */
class UserLocaleListener
{
    /**
     * @var Session
     */
    private $session;

    public function __construct(Session $session)
    {
        $this->session = $session;
    }

    /**
     * @param InteractiveLoginEvent $event
     */
    public function onInteractiveLogin(InteractiveLoginEvent $event)
    {
        $user = $event->getAuthenticationToken()->getUser();

        if (null !== $user->getLocale()) {
            $this->session->set('_locale', $user->getLocale());
        }
    }
}

Finaly in /app/config/services.yml, add

# /app/config/services.yml
services:
    app.user_locale_listener:
        class: AppBundle\EventListener\UserLocaleListener
        arguments: [@session]
        tags:
            - { name: kernel.event_listener, event: security.interactive_login, method: onInteractiveLogin }

When the user log in, the translate will be is locale setting.

Common errors

While generating a dictionnary with JMSI18nRoutingBundle, if you got the error:

There is no extension able to load the configuration for "jms_i18n_routing"

You have to had libraries in your AppKernel.php
Translation not working
Don’t forget to clear cache

Raphaël has written 45 articles

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>