L’internationalisation
Introduction
Les traductions représentent un élément essentiel de votre application si vous souhaitez décliner vos pages dans différentes langues sans avoir à les recopier intégralement.
Le principe de la traduction
Il y a trois éléments essentiels pour mettre en place une traduction sous Symfony :
-
les éléments à traduire,
-
la langue locale dans laquelle doivent s’opérer les traductions,
-
les dictionnaires de traduction.
Il n’y a pas de service de traduction à proprement parler sous Symfony.
Symfony se contente de transférer une chaîne de caractères d’une langue à l’autre en fonction de la variable locale et du dictionnaire approprié.
Attention, la casse (lettres en majuscules ou en minuscules) est importante dans les catalogues de traduction.
L’inconvénient de ce procédé est que c’est à vous d’écrire tous les catalogues de traduction. Cet exercice peut s’avérer très fastidieux si vous avez de nombreuses pages à traduire. Si Symfony ne trouve pas de correspondance dans le catalogue de la langue choisie avec le texte à traduire, il conservera le texte d’origine.
La première chose à faire est de s’assurer de la langue choisie pour la traduction. Cette langue est définie par la variable locale.
La variable locale
La variable d’environnement locale par défaut est définie dans le fichier de configuration config/packages/translation.yaml :
framework:
default_locale: en
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- en
Ici, par défaut, la locale est « en ». C’est l’anglais qui sera utilisé. Les différents codes des langues sont définis par la norme ISO 639. Vous trouverez leurs valeurs sur le site de Wikipedia : https://fr.wikipedia.org/wiki/Liste_des_codes_ISO_639-1
Cette variable locale peut être dynamiquement modifiée dans les routes.
Imaginons que vous souhaitiez utiliser des routes différentes pour chaque langue, vous pouvez définir la locale utilisée pour la page en la mettant en paramètre dans la route.
Syntaxe :
#[Route("/langue/{_locale}/"...
Le paramètre _locale détermine la variable locale utilisée dans la route. La valeur sera automatiquement affectée à la variable locale de votre application.
Prenons un exemple. Dans le contrôleur TestController que nous avons créé...
Les catalogues de traduction
Les catalogues de traduction sont définis dans le dossier /translations. Si vous installez un package particulier (sous forme de bundle), vous pourrez définir les catalogues de traduction dans le dossier Ressources/translations à l’intérieur du bundle que vous avez créé (tout ça dans le dossier vendor, bien sûr).
Chaque catalogue de traduction a un nom particulier. Tous les noms de catalogues suivent la syntaxe suivante : domain.locale.loader.
La signification de cette syntaxe est la suivante.
-
domain : un moyen facultatif d’organiser les messages en groupes. Sauf si des parties de l’application sont explicitement séparées les unes des autres, il est recommandé de n’utiliser que le nom de domaine par défaut des messages : messages.
-
Locale : la langue locale utilisée par le catalogue.
-
Loader : le type d’extension utilisé pour le catalogue. Bien qu’il soit possible de définir des catalogues au format php ou xml, il est recommandé d’utiliser le format YAML.
Nous allons par exemple, créer un catalogue français pour la locale fr. Créez, dans /translations, le fichier messages.fr.yaml (nous utilisons le domaine standard : messages).
À l’intérieur, nous allons par exemple définir une traduction pour le texte : « Welcome...
Les éléments à traduire
Comment dire à Symfony : « traduis moi ceci » ?
Il est possible, par exemple, d’effectuer des traductions dans un contrôleur : il suffit d’injecter le service $translator de l’interface TranslatorInterface dans la méthode désirée.
Syntaxe :
use Symfony\Contracts\Translation\TranslatorInterface;
public function methode(TranslatorInterface $translator)
{
...$translator->trans('texte à traduire');
}
Prenons un exemple. Dans notre méthode langue() dans le contrôleur TestController, insérez ce code :
use Symfony\Contracts\Translation\TranslatorInterface;
...
#[Route("/langue/{_locale}", name:"langue", requirements :
["_locale"=> "en|fr|de"])]
public function langue(Request $request,
TranslatorInterface $translator)
{
$texteTraduit = $translator->trans('Welcome to Symfony');
return new Response( $texteTraduit);
}
Si vous essayez d’exécuter cette requête : localhost:8000/langue/fr, vous tomberez bien sur le texte traduit :...
Les variables dans les traductions
Il est également possible de prendre en compte des variables dans les traductions.
Modifions notre catalogue messages.fr.yaml, ainsi :
Welcome to Symfony: Bienvenue sur Symfony
say_hello: Bonjour %name%:
%name% fait référence à un paramètre dont la valeur sera transmise dynamiquement par un contrôleur ou une vue.
Par exemple, dans notre template templates:test/index.html, utilisons le texte say_hello en lui passant un paramètre :
<h1>{% trans %}Welcome to Symfony{% endtrans %}</h1>
<h2>{{ 'say_hello'|trans({'%name%': 'Yves'}) }}</h2>
Cette fois, la requête donne le résultat suivant :
L’aide à la mise à jour des catalogues
La tâche la plus longue lors de la traduction d’une application consiste à extraire tout le contenu du modèle à traduire et à synchroniser tous les fichiers de traduction. Symfony inclut une commande appelée translation:update qui vous aide dans ces tâches laborieuses.
Par exemple, vous pouvez récupérer tous les messages de votre site non encore traduits dans les catalogues avec la commande :
php bin/console translation:extract --dump-messages fr
Vous pouvez mettre à jour les fichiers de traduction français avec toutes les traductions à faire dans l’application. Les traductions manquantes seront remplacées par l’intitulé de la chaîne précédé de __ (exemple : Welcome to Symfony : __ Welcome to Symfony si cette chaîne n’est pas traduite).
php bin/console translation:extract --force fr
L’organisation des catalogues
Afin de mieux nous y retrouver dans nos catalogues, qui risquent d’être très gros, il est possible de les structurer.
Une des pratiques est d’utiliser des mots-clés plutôt que du texte pour les chaînes source à traduire.
Par exemple, au lieu de traduire :
Welcome to Symfony: Bienvenue sur Symfony
nous allons utiliser une abréviation du type :
test.titre: Bienvenue sur Symfony
test est le nom de la page sur laquelle doit se faire la traduction et titre l’intitulé de la traduction.
Par exemple, dans le template test/index.html.twig, nous utiliserons ce texte :
<h1>{% trans %}test.titre{% endtrans %}</h1>
Ceci peut simplifier grandement vos templates et vos catalogues, surtout si le texte à traduire est conséquent, comme tout un paragraphe par exemple.
Une extension de cette possibilité est de construire des sous-niveaux dans le catalogue.
Il sera plus clair et plus facile de gérer des catalogues organisés en fonction des pages par exemple.
Vous pouvez construire votre catalogue messages.fr.yaml de cette manière :
test :
titre:
bienvenue: Bienvenue sur Symfony
auteur:
bonjour: Bonjour...
La gestion du pluriel
Il se peut qu’on ait besoin de plus de souplesse dans les traductions, notamment pour la gestion des pluriels.
Imaginons, par exemple, que nous souhaitions renseigner le nombre de produits disponibles lors de la commande passée.
Le message différera selon le nombre de produits disponibles :
-
Aucun produit disponible
-
Il y a un produit disponible
-
Il y a x produits disponibles
Bien entendu, il est possible dans le template, avec l’instruction {% if ...%} de tester le nombre de produit disponibles et d’afficher le message correspondant.
Mais si ce message doit être affiché à plusieurs endroits, cela nous oblige à dupliquer du code uniquement pour la traduction.
Symfony propose une gestion du pluriel automatiquement dans le catalogue.
Mais pour profiter de ce type de traduction, Symfony utilise un format particulier : le format ICU.
Ce format est disponible en PHP. Vous trouverez plus d’informations sur ce type de format sur la page http://userguide.icu-project.org/formatparse/messages.
Afin de bénéficier de ce format, il vous faudra renommer le fichier catalogue en ajoutant +intl+icu.
Voici des exemples de conversions de noms de fichiers catalogues de base en nom de fichier au format ICU :
Nom de fichier standard |
Nom de fichier au format ICU |
messages.en.yaml |
messages+intl-icu.en.yaml |
messages.fr_FR.xlf |
messages+intl-icu.fr_FR.xlf |
admin.en.yaml |
admin+intl-icu.en.yaml |
Par exemple, nous allons renommer notre fichier translations/messages.fr.yaml en messages+intl-icu.fr.yaml
Du coup, nous pouvons...
La traduction des messages des contraintes de validation
Traduire les messages dans les contrôleurs et dans les vues c’est bien, mais vous avez d’autres messages qui peuvent apparaître sur votre application et qu’il serait bon de traduire aussi. C’est le cas des messages d’erreurs dans les validations de formulaires par exemple (voir chapitre Les formulaires, section Validation des formulaires).
Pour traduire ces messages, vous allez utiliser le domaine validation au lieu du domaine messages dans votre catalogue.
Créez un fichier translations/validators.fr.yml et insérez à l’intérieur les traductions de vos messages d’erreurs de validation.
Nous allons prendre l’exemple du message d’erreur de validation sur la taille minimum ou maximum du nom des produits.
Modifions le message sur les annotations de $nom dans l’entité Produit :
#[ORM\Column(length: 200)]
#[Assert\Length(
min: 2,
max: 50,
minMessage: 'produit.nom.min',
maxMessage: 'produit.nom.max',
groups: ["all"]
)]
En fonction de la locale nous créerons...
L’utilisation du nom de domaine
Nous venons de voir qu’en fonction du nom de domaines, validators ou messages, ce ne sont pas les mêmes types de messages qui sont traduits.
Il peut être utile de préciser le nom de domaine sur lequel on travaille.
Supposons que toutes vos pages aient des traductions importantes. Votre fichier messages.fr.yaml risque de grossir au point que vous risquez de ne plus vous y retrouver au niveau des traductions.
Il sera alors plus judicieux de partitionner vos catalogues en fonction, par exemple, du nom de la page qui sera censée utiliser ces traductions.
Il est possible de définir le nom de domaine souhaité pour nos traductions.
Par exemple, créons un fichier de traduction test+intl-icu.fr.yaml.
say_thanks: Merci {name}
Dans notre template test/index.html.twig, ajoutons cette ligne :
<h2>{{ 'say_thanks'|trans({'name': 'Yves'}, 'test') }}</h2>
Vous pouvez vérifier que les traductions sont bien toujours effectuées.