Le langage d’un site

Symfony est constitué de composants. Ceux-ci savent comment effectuer des appels de traductions si celle-ci est active. On sait donc facilement traiter une langue.

Par contre, comment déterminer laquelle utiliser ? Les composants ne fournissent pas d’outil pour le faire automatiquement. En effet, considérez ces exemples :

  • http://tech.sensiolabs.com/
  • http://tech.sensiolabs.com/fr/
  • http://tech.sensiolabs.fr/
  • http://fr.tech.sensiolabs.com/
  • http://tech.sensiolabs.com/welcome
  • http://tech.sensiolabs.com/bienvenue

Il existe beaucoup de possibilités pour définir la langue en fonction d’une URL, mais il existe d’autres indicateurs. Le navigateur stocke un choix de langue et envoie un en-tête Accept-Language qui est une liste de langues. Souvent les sites proposent une liste de langues, sous forme de drapeau ou de menu déroulant, qui change la langue le temps d’une session. L’utilisateur peut avoir un compte et avoir sélectionné une langue préférée qui sera utilisée à sa connexion.

Bref, beaucoup de combinaisons possibles en fonction des éléments de votre site. Donc impossible de faire une sorte de résolveur automatique qui fonctionnerait selon vos désirs.

Une solution

Un écouteur

Pour votre site multilingue, vous avez besoin d’un écouteur qui se chargera de résoudre la langue en fonction des possibilités de fonctionnalité de votre site.

Dans notre exemple, nous allons juste proposer aux utilisateurs trois langues et déterminer celle à utiliser en fonction du navigateur.

Préparation

Tout d’abord il faut faire un petit tour dans le fichier config.yml afin de :

  • définir une langue par défaut, en
  • définir la liste des langues qui sont disponible, en, fr et es
  • décommenter la ligne translator afin d’activer les capacités de traduction du framework
parameters:
    locale: en
    accepted_locales: [en, fr, es]

framework:
    translator:      { fallbacks: ["%locale%"] }
    default_locale:  "%locale%"

On ajoute, dans un template, une clé qui sera traduite :

    <p>{{ 'article.welcome' | trans }}</p>

Il faut ensuite créer des fichiers de traduction. Ceux-ci peuvent être généré en ligne de commande, par exemple :

bin/console translation:update --output-format yml --force en AppBundle

Et voilà, il ne reste plus qu’à définir la langue dans la requête.

Mise en place

Voici l’écouteur :

<?php

namespace AppBundle\Locale;

// [...]

class RequestLocaleListener implements EventSubscriberInterface
{
    const SESSION_LOCALE = 'locale';

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

    /** @var string[] */
    protected $acceptedLocales;

    /**
     * @param string   $defaultLocale
     * @param string[] $acceptedLocales
     */
    public function __construct($defaultLocale, $acceptedLocales)
    {
        $this->defaultLocale = $defaultLocale;
        $this->acceptedLocales = $acceptedLocales;
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::REQUEST => ['onKernelRequest', 15],
        ];
    }

    /**
     * Determines and sets the locale for the request.
     *
     * @param GetResponseEvent $event
     */
    public function onKernelRequest(GetResponseEvent $event)
    {
        $request = $event->getRequest();

        // Avoid sub-request locale changes
        if (!$event->isMasterRequest()) {
            return;
        }

        $locale = $this->defaultLocale;

        $session = $request->getSession();

        if (null !== $session && $session->has(self::SESSION_LOCALE)) {
            $locale = $session->get(self::SESSION_LOCALE);
        } else {

            // Select the best language based on browser
            $localeBrowser = $request->getPreferredLanguage($this->acceptedLocales);

            if (!empty($localeBrowser)) {
                $locale = $localeBrowser;
            }

            $session->set(self::SESSION_LOCALE, $locale);
        }

        $request->setLocale($locale);
        $request->attributes->set('_locale', $locale);
    }
}

Comme indiqué ci-dessus, c’est le navigateur qui nous informe des les langues de l’utilisateur dans l’en-tête Accept-Language. Voici un exemple de valeur possible : fr-FR,fr;q=0.7,en;q=0.3. Ici c’est le français qui est prioritaire, puis l’anglais. La valeur q est un poids représentant la priorité de la langue.

L’objet Request possède une méthode getPreferredLanguage() bien pratique qui vous permet, à partir d’une liste de langues disponibles, la langue correspondant le mieux aux choix mentionnés dans l’en-tête Accept-Language. Donc inutile d’analyser vous même cet en-tête.

On peut constater que l’on utilise la session comme cache pour éviter de fouiller l’en-tête en permanence.

Et voilà, le tour est joué. Bien sûr, vous pouvez aller plus loin en complexifiant l’écouteur pour qu’il détermine la langue en fonction d’autres paramètres tel qu’un segment de l’URL ou un attribut de la classe de votre utilisateur.

Pour aller plus loin

Vous pouvez retrouver un autre exemple dans la documentation officielle de Symfony.

Il existe plusieurs bundles ou des extensions qui peuvent vous accompagner dans la mise en place de l’i18n de votre site :