Automatisation de la récupération des chaines à traduire
Avant de commencer le projet à proprement parler et de créer un billet sur la création du répertoire de travail ainsi que des éventuelles opérations pré-projet, j’ai voulu faire un test avec le système de traduction de Laravel, qui est traité ici dans la doc, le système en lui même est assez simple, à priori il peut y avoir principalement deux formats :
__(‘Welcome’); qui va permettre de traduire une chaine en ‘dur’.
__(‘Welcome [name]’, [‘name’ => Auth::user()->first_name.’ ‘.Auth::user()->last_name]); qui va, de son côté, permettre de passer un ou plusieurs paramètre(s), dans ce cas, la chaine traduite devra inclure le paramètre précédé de :
Pour notre exemple la chaine en Français devra donc être par exemple Bonjour :name, ce qui aura pour résultat à l’écran de l’utilisateur Bonjour toto Titi par exemple.
Attention toutefois, pour que l’exemple en question fonctionne, l’utilisateur devra être connecté et la table User devra contenir un champ first_name et un champ last_name.
Ça c’est pour la partie traitement des traductions pour l’affichage final, et bien entendu il existe d’autres points dans la doc mais qui ne m’intéressent pas, du moins pour le moment.
Mais quid du peuplement des fichiers de langues (à moins que j’ai mal lu la doc), que je n’ai pas trouvé, et sur lequel j’avais travaillé lors d’un système compatible WP / Prestashop.
C’est partie pour la création et l’automatisation du système de traduction.
J’ai donc créé le package de lang de base avec la commande
php artisan lang:publish
Ce qui a eu pour effet d’ajouter le répertoire lang, ainsi qu’un sous répertoire en dans le projet (la langue par défaut étant en Anglais), ensuite le système de traduction de Laravel fonctionne selon deux méthodes :
la première utilise le sous répertoire de l’iso de la langue et à l’intérieur comporte des fichiers php qui représentent les ‘domaines’ d’application dans lequel est retourné un tableau contenant les chaines à traduire, la clé étant la chaine d’origine, la valeur étant la chaine traduite, d’après la documentation, cette version se limite aux clés courtes, on pourrait sans doute utiliser un md5 comme clé comme dans Prestashop, mais pour le moment je ne souhaite pas passer trop de temps sur ce point, donc je ne retiens pas cette version.
La deuxième, que j’utiliserai donc pour mon projet, est un fichier json enregistré à la racine du répertoire lang de la forme [iso].json, donc pour le Français, fr.json.
Une fois cette opération terminée, j’ai ajouté à ma base de données une table langs que j’ai arbitrairement remplie avec 3 langues l’Anglais, l’Allemand et le Français, afin d’avoir des tests plus sûrs :
J’ai ensuite créé le modèle Language avec la commande
php artisan make:model Language
Une fois ce modèle créé, une variable $strings est ajoutée sous forme de tableau qui va contenir un tableau par langue, ensuite j’ai créé 3 fonctions :
Une fonction qui va parcourir tous les répertoires afin de trouver les fichiers contenant des chaines de traduction et qui prend 3 paramètres :
- Les répertoires à exclure afin de ne pas chercher dans vendor ou storage par exemple.
- Les fichiers à exclure.
- Les extensions des fichiers (c’est un héritage car dans Prestashop, les fichiers de templates se terminent par .tpl et non php).
Cette fonction sera à extraire de ce fichier et à mettre dans une classe Tools par exemple, mais pour mes tests la structure convient.
En complément j’ai créé un helper Dummies qui va recenser toutes les chaines qui n’existent nulle part car étant des chaines constituées en partie au moins d’une variable, par exemple {{ __($profile_right) }}
qui est une valeur d’un champ sous forme de JSON, en l’occurrence :
{"display":"1","read":"1","create":"1","update":"1","delete":"1"}
J’aurais pu créer une table avec un champ label, voir cet article, mais d’une part j’avais mis en place ce système de fichier Dummies au préalable, d’autre part ça ajouterait une table supplémentaire et pour d’autres cas l’utilisation d’une table ne serait pas envisageable, exemple {{ __($type.'s') }}
. Bref, c’est encore une fois à chacun de voir, mais ce fonctionnement et simple et éprouvé de mon côté.
Une fonction qui va extraire de la chaine trouvée la chaine à traduire pour transformer __(‘Welcome’); en Welcome et __(‘Welcome %name%’, [‘name’ => Auth::user()->first_name.’ ‘.Auth::user()->last_name]); en Welcome %name%, j’avais précédemment utilisé Welcome [name], mais à la mise en place de l’interface d’administration, cela s’est révélé être un mauvais choix, et les placer ensuite dans le tableau $strings dans chaque langue pour peu que cette clé n’existe pas encore pour la langue en question, ce qui pour mon exemple donne :
Attention ceci est ma convention pour les chaines comportant des paramètres, libre à vous d’utiliser ce que vous voulez.
Et enfin une fonction qui va sauvegarder les données dans les fichiers JSON qui se trouvent dans le répertoire lang en fonction de la clé de chaque tableau de $strings, ce qui aura pour objectif d’obtenir autant de fichiers que de langues dans la base, chacune avec les mêmes clés mais avec des valeurs qui lui sont propres:
Pour le Français par exemple.
Pour le moment, j’ai mis l’appel à la recherche des chaines à traduire dans mon fichier wep.php, mais l’objectif est d’avoir un contrôleur qui permette de gérer ces traductions, quoi qu’il en soit à partir de maintenant et sous réserve de suivre la nomenclature des chaines à traduire, à chaque fois qu’une expression à traduire sera ajoutée à un des fichiers parsés, elle sera ajoutée aux fichiers JSON et pourra être traduite.
Un mot concernant ma convention pour les chaines comportant des paramètres, j’ai mis [name] par exemple, car à priori il est peu probable que j’ai du texte de ce format à traduire, mais quoi qu’il en soit, pour que ce dernier soit pris comme paramètre dans la chaine traduite il faudra le remplacer par :name comme indiqué au dessus, cette convention va me permettre dans la vue d’administration des traductions d’indiquer à l’utilisateur qu’il se peut qu’il y ait un paramètre et qu’il doive donc le mettre dans la chaine traduite en remplaçant [name] par :name.
Dernier point concernant les traductions, et là j’avoue que j’ai eu à chercher pas mal pour afficher les traductions sur le front et d’ailleurs je n’ai pas trouvé de solution en ligne, en effet, j’ai mis la langue dans la table user, et donc lorsque l’utilisateur est connecté, je souhaite traduire dans sa langue, j’avais donc testé dans le fichier wep.php puis dans AppServiceProvider.php si l’utilisateur était connecté avec Auth::check(), mais à chaque fois j’avais un retour false au lieu de true alors que mon utilisateur était bien connecté avec une session, des pros pourront sans doute m’expliquer le pourquoi du comment, bref, je suis donc passé par le même système que Prestashop en Créant un FrontController qui étend Controller, et j’ai modifié mes contrôleurs annexes pour qu’ils étendent ce FrontController plutôt que Controller, j’ai ensuite ajouté un constructeur à ce nouveau contrôleur en définissant la locale en fonction de l’état de connexion de l’utilisateur et de la valeur de sa langue dans la base et là tout est ok.
Pas mal de choses à dire pour ce premier billet mais je préfère détailler les opérations si ça peut aider quelqu’un.
Une extension à ce sujet est publiée dans cet article.