Live Coding : Créer un site multilingue avec Symfony 4

31 janvier 2020 - : Tutoriel Symfony Live-Coding - : 22 commentaires - Tutoriel Controllers MVC Views Symfony Live-Coding

Visualisez les fichiers de cette série sur GitHub

Dernière modification le 3 février 2020

Il arrive parfois d'avoir à mettre en place un site dans plusieurs langues, au niveau de son interface utilisateur.

Symfony nous propose des outils pour mettre en place cette "localisation", également appelée "Internationalization" ou "i18n" (en référence au nombre de caractères entre i et n).

Nous allons voir comment utiliser ces outils.

ATTENTION : nous n'allons pas traiter de la traduction du contenu de la base de données, traîtée par l'extension Doctrine Translatable.

Symfony utilisera le service de traduction (Translation service) pour rechercher une traduction pour les chaînes identifiées. Si il trouve une traduction, il remplacera la chaîne par sa traduction.

Information : il sera toujours préférable d'écrire le site en langue anglaise et de le traduire vers les autres langues.

Installer le service

Le service de traduction s'installera au moyen de Composer. Il est déjà installé par défaut dans les configurations basées sur "website-skeleton".

La commande ci-dessous permettra de l'installer

composer require symfony/translation

Une fois installé, le service sera configuré par le fichier "config/packages/translation.yaml" qui contient le code ci-dessous par défaut

# config/packages/translation.yaml
framework:
    default_locale: 'en'
    translator:
        default_path: '%kernel.project_dir%/translations'

L'option "default_locale" permet de définir la langue par défaut du site. Nous allons configurer le Français en indiquant "fr".

L'option "default_path" indique le chemin vers le dossier contenant les traductions.

Utiliser le service

Une fois le service de traduction installé, nous allons pouvoir l'utiliser. Il est possible de traduire des chaînes directement dans les contrôleurs, mais également dans les vues.

Dans les contrôleurs

Si vous devez envoyer une chaîne à traduire depuis un contrôleur, comme dans un message flash par exemple, il est possible de le faire en utilisant les instructions suivantes dans le contrôleur

use Symfony\Contracts\Translation\TranslatorInterface;

public function index(TranslatorInterface $translator)
{
    $message = $translator->trans('Your comment is pending approval');

    // ...
}

Dans les vues

Nous devrons parfois traduire des chaînes directement dans les vues (fichiers Twig) afin de personnaliser l'interface en fonction de la langue de l'utilisateur.

Par exemple, si nous souhaitons traduire la section suivante de nos vues

<h2>Commentaires ({{ article.commentaires|length }})</h2>
{% for commentaire in article.commentaires %}
    <p>Publié par {{commentaire.pseudo }}</p>
    <div>{{ commentaire.contenu }}</div>
{% else %}
    <p>Il n'y a pas encore de commentaire</p>
{% endfor %}

Nous aurons à traduire les expressions "Commentaires", "Publié par" et "Il n'y a pas encore de commentaire"

Nous allons déclarer des chaînes traductibles dans les vues, comme ci-dessous

{% trans %}Chaîne à traduire{% endtrans %}

{# ou #}

{{ 'Chaîne à traduire'|trans }}

Nous allons utiliser les deux dans notre exemple. Ce qui donnera

<h2>{% trans %}Comments{% endtrans %} ({{ article.commentaires|length }})</h2>
{% for commentaire in article.commentaires %}
    <p>{% trans %}Published by{% endtrans %} {{commentaire.pseudo }}</p>
    <div>{{ commentaire.contenu }}</div>
{% else %}
    <p>{% trans %}There's no comment yet{% endtrans %}</p>
{% endfor %}

Créer le fichiers de traduction

Nous allons enfin créer le fichier de traduction qui contiendra les chaînes en anglais et leur traduction dans la langue de notre choix.

Création manuelle

Le fichier sera à créer en utilisant le code de la langue sur 2 caractères (à l'emplacement de xx), comme fr pour le français.

On créera donc autant de fichiers que de langues, sous le format "translation/messages.xx.xlf"

Pour traduire notre message flash vu précédemment, nous inscrirons donc ceci dans le fichier

<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
  <file source-language="en" target-language="fr" datatype="plaintext" original="file.ext">
    <header>
      <tool tool-id="symfony" tool-name="Symfony"/>
    </header>
    <body>
      <trans-unit id="2lTOrMA" resname="Article published successfully">
        <source>Article published successfully</source>
        <target>Article publié avec succès</target>
      </trans-unit>
    </body>
  </file>
</xliff>

Ce fichier n'est pas simple, la partie ci-dessous explique comment l'automatiser.

Création automatique

Il existe une commande permettant de créer automatiquement le fichier de traduction pour la langue de notre choix.

Cette commande va parcourir tous les fichiers et intégrer toutes les chaines dans le fichier correspondant à la langue choisie.

ATTENTION : cette commande ne traduit pas le contenu

php bin/console translation:update --force fr

Le fichier xlf sera créé automatiquement.

Mettre en place le choix de la langue

Pour permettre aux utilisateurs de choisir la langue dans laquelle le site doit s'afficher, nous allons devoir stocker la langue choisie dans la session.

Pour commencer, nous allons créer une variable d'environnement qui contiendra la liste des langues souhaitée.

Cette variable sera déclarée dans "config/services.yaml" de cette façon

parameters:
    # ici vos autres variables
    app.locales: [en, fr]

Nous allons également déclarer cette variable globalement dans les fichiers Twig afin d'y accéder depuis tous les fichiers. Nous l'appellerons "locales" et l'intégrons dans "config/packages/twig.yaml"

twig:
    default_path: '%kernel.project_dir%/templates'
    debug: '%kernel.debug%'
    strict_variables: '%kernel.debug%'
    # On déclare ci-dessous le nom de la variable globale
    globals:
        locales: '%app.locales%'

On crée maintenant une méthode dans un contrôleur qui permettra de changer la langue.

Cette méthode contiendra le code suivant

/**
 * @Route("/change_locale/{locale}", name="change_locale")
 */
public function changeLocale($locale, Request $request)
{
    // On stocke la langue dans la session
    $request->getSession()->set('_locale', $locale);

    // On revient sur la page précédente
    return $this->redirect($request->headers->get('referer'));
}

Nous appellerons cette méthode depuis notre menu ou tout fichier Twig dans lequel nous donnerons le choix de la langue aux utilisateurs.

{% for locale in locales %}
    {% if locale != app.request.locale %}
        <a href="{{ path('change_locale', {'locale': locale}) }}">{{ locale }}</a>
    {% endif %}
{% endfor %}

Et voilà, la traduction est en place.

Intercepter les requêtes

Afin de gérer les changements de langues dans le coeur de Symfony (Kernel), nous allons nous attacher à chacune des requêtes en créant un souscripteur d'évènements (EventSubscriber).

Pour ce faire, nous allons entrer la commande suivante

php bin/console make:subscriber LocaleSubscriber

A la question de l'évènement à utiliser, entrer

Symfony\Component\HttpKernel\Event\RequestEvent

Le fichier "LocaleSubscriber.php" est créé dans le dossier "src/EventSubscriber"

Nous allons remplacer son code par celui-ci

<?php

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class LocaleSubscriber implements EventSubscriberInterface
{
    // Langue par défaut
    private $defaultLocale;

    public function __construct($defaultLocale = 'en')
    {
        $this->defaultLocale = $defaultLocale;
    }

    public function onKernelRequest(RequestEvent $event)
    {
        $request = $event->getRequest();
        if (!$request->hasPreviousSession()) {
            return;
        }

        // On vérifie si la langue est passée en paramètre de l'URL
        if ($locale = $request->query->get('_locale')) {
            $request->setLocale($locale);
        } else {
            // Sinon on utilise celle de la session
            $request->setLocale($request->getSession()->get('_locale', $this->defaultLocale));
        }
    }

    public static function getSubscribedEvents()
    {
        return [
            // On doit définir une priorité élevée
            KernelEvents::REQUEST => [['onKernelRequest', 20]],
        ];
    }
}

Voilà qui termine ce tutoriel.

Obtenir de l'aide

Pour obtenir de l'aide, vous pouvez accéder au serveur Discord pour une entraide par chat

Visualisez les fichiers de cette série sur GitHub

Partager

Partager sur Facebook Partager sur Twitter Partager sur LinkedIn

Commentaires

Ferran a écrit le 22 juillet 2020 à 19:21

Est il possible de translate un label ou un placehoder d'un fichier twig ?

Répondre

Plesk a écrit le 7 juillet 2020 à 20:08

Bonsoir Nouvelle-Techno.fr,

Je suis débutant avec symfony, je voudrais savoir si j'ai effectué la bonne méthode en tous cas mon code fonctionne.

 

            <div class="dropdown d-inline-block">
                {% set locale = app.request.locale %}
                {% if locale == locale %}
                    <button type="button" class="btn header-item waves-effect" id="page-header-flag-dropdown"
                        data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                        <img class="" src="{{ asset('build/images/flags/'~locale~'.jpg') }}"alt="{{ locale|capitalize }}" height="14">
                    </button>                   
                    <div class="dropdown-menu dropdown-menu-right">
                        {% for locale in locales %}
                            {% if locale != app.request.locale %}
                                <a href="{{ path('lang', {'locale': locale}) }}" class="dropdown-item notify-item">
                                    <img src="{{ asset('build/images/flags/'~locale~'.jpg') }}" alt="{{ locale|capitalize }}" class="mr-2" height="12"><span class="align-middle">{{ locale|capitalize }}</span>
                                </a>
                            {% endif %}
                        {% endfor %}
                    </div>
                {% endif %}
            </div>

 

Merci

Répondre

Nouvelle-Techno.fr a répondu le 8 juillet 2020 à 09:14

Bonjour,

Le 1er if du code ne sert à rien étant donné qu'il compare une variable à elle-même.

Il serait plus simple de discuter sur Discord.

Répondre

firedev a écrit le 3 juillet 2020 à 18:51

Merci beaucoup !!

Répondre

Zekinht a écrit le 3 juin 2020 à 14:42

Bonjour,

J'ai eu le même soucis que toi TheDraill , pour y remédier j'ai simplement détaché le if du for.

Répondre

Nouvelle-Techno.fr a répondu le 3 juin 2020 à 14:46

Effectivement une mise à jour de twig interdit maintenant le if dans le for, je corrige l'article.

Répondre

TheDraill a écrit le 2 juin 2020 à 08:59

Bonjour, J'ai bien suivi votre tuto, cependant, Symfony m'affiche cette erreur alors que la ligne "{{ dump(locales) }}" m'affiche correctement la variable locale. Faudrait-il que je rajour un "endif" ? Merci pour votre tuto !

Répondre

Ferran a répondu le 22 juillet 2020 à 19:19

Comme toi, avec symfony 5, cela fonctionne en séparant le for du if :

                    {% for locale in locales %}

                        {% if locale != app.request.locale %}

                            <li lass="nav-item active">

                                <a class="nav-link" href="{{ path('translate_local', {'locale': locale }) }}"><img src="{{ asset ('esa/img/flag/'~locale~'.png') }}" alt="{{locale}}" width="32" height="32"></a>

                            </li>

                        {% endif %}

                    {% endfor %}

Répondre

Algiu a écrit le 29 mai 2020 à 20:30

Bonjour,

J'ai suivi le tuto et tout fonctionne à l'execption d'un détail que je n'explique pas:

Lorsque j'arrive sur le site, j'ai les deux drapeaux qui s'affichent alors que la locale dans le profiler est celle par défaut "en".

On dirait que le subscriber n'affecte pas automatiquement la locale par défaut à la session. Je suis obliger de cliquer sur un des drapeau pour initialiser la locale de la session.

 

Répondre

Algiu a écrit le 28 mai 2020 à 23:09

Hello, Merci beaucoup pour ce super tuto. Est ce qu'il serait possible de créer une partie 2 avec la traduction de la base de donnée via Translatable ? Et faire une exemple d'une entité article pour laquelle ont maintien une description et un contenu en FR et en EN ?

Merci beaucoup.

Répondre

Nouvelle-Techno.fr a répondu le 28 mai 2020 à 23:17

Bonsoir,

Je vais regarder mais pour le moment je n'ai jamais vraiment chercher à utiliser translatable, ça va me demander un peu de temps

Répondre

Algiu a répondu le 29 mai 2020 à 10:47

Bonjour,

Merci beaucoup pour votre retour. Alors pour info, j'ai regardé le fonctionnement de Doctrine Translatable hier soir. Il s'avere que cela marche pratiquement tout seul. Par ailleur Translatable s'intégre parfaitement avec le switch de langue du site que vous avez expliqué dans cette video sans code à ajouter. Je suis assez bluffé par cette extension.

Merci encore pour vos tutos, c'est génial ce que vous faites.

Répondre

Will0fried a écrit le 1 mai 2020 à 17:46

Merci beaucoup, j'ai découvert tes tutos il y a quelque semaine et c'est top.

Répondre

Kokish a écrit le 12 avril 2020 à 14:00

Bonjour,

lors de la création du subscriber, l'invite de commande m'affiche comme message d'erreur "Notice: Undefined index: Symfony\Component\Mailer\Event\MessageEvent", est-ce normal? j'ai créé donc un dossier et fichier Subscriber, mais la traduction n'est tjrs pas activée quand j'appuie sur le drapeau

merci de votre aide

Répondre

gregRamos a écrit le 26 mars 2020 à 10:49

Salut,

J'ai réussi la traduction, mais par la suite j'ai du ajouter du contenu à mon site.  Comment faire pour utiliser la commande update sans perdre la traduction déjà faite et pourvoir compléter la nouvelle partie a traduire?

Tuto très facile a comprendre, merci!

Répondre

Nea a répondu le 26 mars 2020 à 11:46

Bonjour je suis également en train d'installer une translation pour la première fois sur un site et je me suis aussi posée cette question à un moment. Cette commande n'efface rien de ton fichier elle ajoutera simplement de nouvelles balises à la suite des autres.

php bin/console translation:update --force

Répondre

gregRamos a répondu le 26 mars 2020 à 16:27

Merci pour la réponse rapide, je n'ai malheureusement rien qui s'ajoute dans le fichier xlf après

php bin/console translation:update --force

Répondre

Nouvelle-Techno.fr a répondu le 26 mars 2020 à 16:32

Bonjour,

La commande exacte telle que décrite dans l'article est

php bin/console translation:update --force fr

Il faut préciser la langue (ici "fr")

Répondre

gregRamos a répondu le 26 mars 2020 à 16:38

Même avec la commande complète

php bin/console translation:update --force fr

le fichier ne change pas, j'ai pourtant bien le message :  

[OK] Translation files were successfully updated.

Répondre

Nouvelle-Techno.fr a répondu le 26 mars 2020 à 16:40

Si le fichier ne change pas c'est qu'il n'y a aucune chaîne détectée.

Il serait plus simple d'en discuter sur Discord https://discord.gg/azQ9sbD

Répondre

gregRamos a répondu le 26 mars 2020 à 16:47

Désolé pour le temps perdu, j'ai fait une erreur de débutant j'ai oublié tous simplement de rajouter les balises trans dans le template...

Répondre

Nouvelle-Techno.fr a répondu le 26 mars 2020 à 17:01

Pas de soucis, l'essentiel est de trouver ;-)

Répondre

Obtenir de l'aide

Il n'est plus possible d'ajouter de commentaires.

Pour obtenir de l'aide, vous pouvez accéder au serveur Discord pour une entraide par chat