La classe
I18n permet d'afficher les textes d'une application dans la langue de l'utilisateur.
Cette classe est inspirée du projet
php-i18n.
Les textes sont écrits dans un fichier au format
YAML puis ce fichier est transformé automatiquement en classe où chaque entrée est une constante.
Lors de l'utilisation, dans le meilleur des cas, l'affichage d'un texte ne nécessitera que le temps d'accès à une constante de classe.
Concepts
Ce qu'on appelle "locale" dans la suite est en réalité un "language-tag" tel que décrit ici :
https://www.w3.org/International/articles/language-tags/.
C'est une chaîne de caractères identifiant la langue de l'utilisateur. Elle est en général de la forme :
langue[-region].
La constante
salt\I18N_DEFAULT_LOCALE contient la locale par défaut et vaut
en.

Le contenu de cette constante est fixe et ne peut pas être surchargé pour une application.
La locale par défaut détermine quelle classe sera la classe mère de toutes les classes générées par
I18n, on ne peut donc pas permettre à une application utilisant
le framework de la surcharger car on pourrait avoir deux applications avec des valeurs différentes, ce qui pourrait engendrer une classe A ayant pour classe parente B, et une classe B ayant pour classe parente A, ce qui est impossible.
Si on souhaite tout de même modifier la locale par défaut, il faut la changer dans le fichier
conf/config.php du framework SALT, et elle s'appliquera à toutes les applications.
Les fichiers de langues correspondants (de SALT et de votre application) doivent contenir toutes les entrées référencées dans SALT ou l'application.
Il y a une instance de classe
I18n par "application" ou par "module" si votre application est modulaire.
Par exemple, une application utilisant SALT a en général une instance
I18n pour le framework SALT et une instance pour elle-même.
Chaque instance permet de définir :
- Un dossier de stockage des fichiers de langues (fichiers .yml)
- Un dossier de stockage des classes générées (fichiers .php)
Pour chaque locale, on va générer une classe de même nom que la locale. Pour une application, on aura l'héritage des classes générées suivants :
| Locale initialisée |
Héritage |
| A |
A extends I18N_DEFAULT_LOCALE |
| A-B |
B extends A
A extends I18N_DEFAULT_LOCALE |
| A-B-C |
C extends B
B extends A
A extends I18N_DEFAULT_LOCALE |
En clair cela veux dire qu'on peut définir toutes les entrées de
en.yml et ne redéfinir dans
en-gb.yml que
les entrées spécifiques à la locale
en-gb, les autres entrées étant héritées de la classe mère.
On peut également traduire partiellement une application, les textes manquants seront récupérés depuis la classe mère qui est la locale par défaut.
Configuration
- Locale par défaut
La constante salt\I18N_DEFAULT_LOCALE correspond à la locale par défaut.
Vous pouvez la modifier uniquement en éditant le fichier conf/config.php du framework SALT et il faut s'assurer que toutes les entrées du fichier de langue de SALT existent dans cette locale.
Les fichiers de langues sont dans le dossier lang de SALT.
- Définir la locale de SALT
La constante salt\I18N_LOCALE correspond à la locale utilisée par SALT, pour les messages d'erreurs par exemple.
Vous pouvez la redéfinir avant d'appeler Salt::config(), en vous assurant que le fichier de langue correspondant existe bien dans le dossier lang de SALT.
- Initialiser l'instance de l'application
<?php
$i18n = I18n::getInstance('NOM APPLICATION', '/chemin/racine/application');
// ou :
$i18n = I18n::getInstance('NOM APPLICATION')
->setLangPath('/chemin/dossier/lang')
->setCachePath('/chemin/dossier/cache');
NOM APPLICATION doit être un nom logique représentant l'application.
/chemin/racine/application doit être un chemin relatif ou absolu contenant au moins un dossier lang et un dossier cache, accessible en lecture et en écriture.
/chemin/dossier/lang doit être un chemin relatif ou absolu accessible en lecture.
/chemin/dossier/cache doit être un chemin relatif ou absolu accessible en écriture.
- Initialiser la locale de l'utilisateur
<?php
$i18n->init('LOCALE');
// ou :
$i18n->init(array('LOCALE1', 'LOCALE2', ...));
// puis :
$localeClass = $i18n->get();
// ou :
$i18n->alias('T');
La méthode
init() permet d'initialiser
UNE SEULE locale. Dans le cas où l'on passe plusieurs locales avec un tableau, on initialisera la 1ère locale disponible uniquement.
A noter que si aucune locale indiquée dans init() n'est disponible, on essayera également d'initialiser la locale par défaut avant d'échouer avec une exception.
Une fois la locale initialisée, on peut :
- Soit appeler
get() pour récupérer le nom interne de la classe et ainsi accéder aux constantes de la classe
- Soit appeler
alias(...) pour définir un nom de classe a travers laquelle la classe de locale sera accessible
Utilisation basique
Avec les fichiers suivants :
| lang/fr.yml |
lang/en.yml |
text: Test de texte jours: lundi: Lundi mardi: Mardi a: b: c: D escape: "Texte avec 2 points :" francais: FR
|
text: Text test jours: lundi: Monday mardi: Tuesday a: b: c: D escape: "Text with colon :" english: EN
|
Pour l'entrée "escape", on doit entourer le texte avec des guillemets ou des apostrophes s'il contient un caractère spécial, comme ":"
Dans les fichiers il y a une erreur (pour l'exemple) : la clé "francais" n'existe pas dans le fichier
en.yml qui est le langage par défaut.
Le fait qu'une clé du langage par défaut ("english") n'existe pas dans
fr.yml est toléré, mais le contraire non.
<?php
// initialisation
define('salt\I18N_LOCALE', 'en');
$i18n = I18n::getInstance('APP', RELATIVE);
$i18n->init('fr')->alias('T'); // la classe générée sera donc "fr", elle étendra de la classe "en"
// et sera accessible via la classe "T"
// utilisation
echo T::text; // affiche "Test de texte"
echo T::jours_lundi; // affiche "Lundi"
echo T::a_b_c; // affiche "D"
echo T::escape; // affiche "Texte avec 2 points :"
$en = $i18n->init('en'); // récupération ponctuelle d'une autre locale
echo $en::jours_lundi; // affiche "Monday"
echo T::francais; // affiche "FR"
echo T::english; // affiche "EN" car T correspond à la classe générée à partir de fr.yml,
// mais elle hérite aussi de la classe générée à partir de en.yml qui définit bien cette clé.
echo $en::english; // affiche "EN"
echo $en::francais; // affiche une erreur indiquant que la constante de classe "francais" n'est pas définie
Ici on voit que les clés générées dans la classe utilisent le caractère underscore comme séparateur : La clé "jours_lundi" n'existe pas directement dans le fichier source
Textes avec remplacement
Lorsqu'un texte contient une partie variable, on peut le définir avec un format :
text_avec_remplacement: variable1 : %s, variable2 : %05d
<?php
echo T::text_avec_remplacement('var1', 3); // affiche : "variable1 : var1, variable2 : 00003"
Les formats sont ceux utilisés par
sprintf.
Listes
Lorsque le fichier contient des listes, comme la clé "jours" dans l'exemple au dessus, on peut récupérer directement la clé pour avoir l'ensemble des sous valeurs :
<?php
var_dump(T::jours); // affiche un tableau associatif : ['lundi' => 'Lundi', 'mardi' => 'Mardi']
En cas d'erreur
Si on demande une clé qui n'existe pas, PHP levera une
FATAL ERROR de type "Undefined class constant"
Il n'est pas possible avec les versions actuelles de PHP d'intercepter proprement cette erreur dans le framework.
Il est donc de la responsabilité de l'utilisateur du framework de faire attention à ce que toutes les clés utilisées dans le code existent bien, au moins dans la locale par défaut.
Par défaut, à chaque initialisation de locale, le framework vérifie la date de dernière modification du fichier YAML pour déterminer s'il doit ou non regénérer la classe.
Cela se traduit par des appels aux méthodes PHP
file_exists et
filemtime à chaque page.
Il est possible de s'en passer en modifiant la manière dont l'instance
I18n est initialisée :
<?php
$i18n = I18n::getInstance('APP', RELATIVE, I18n::MODE_USE_GENERATED);
En faisant cela, on n'appelle plus
file_exists et
filemtime, on utilise directement les classes générées précédemment sans aucune vérification.
Cependant, à chaque modification d'un fichier YAML, il faudra regénérer manuellement les classes.
Génération
Lorsqu'on utilise le mode
I18n::MODE_USE_GENERATED, on doit générer manuellement les classes.
Pour cela on doit exécuter le code suivant :
<?php
$i18n = I18n::getInstance('APP', RELATIVE, I18n::MODE_USE_GENERATED);
$i18n->generate(); // va regénérer toutes les classes

Il ne faut pas utiliser la méthode generate() sur une page accessible aux utilisateurs normaux.
Idéalement seul l'administrateur du site doit appeler cette méthode, lors d'une mise à jour par exemple.
Dans le cas contraire, la méthode n'étant pas thread-safe, on s'expose à des erreurs en cas d'appels concurrents.
Cette opération étant à faire pour chaque instance I18n, il est également possible de le faire pour l'instance de SALT :
<?php
define('salt\I18N_MODE', I18n::MODE_USE_GENERATED); // a définir tout le temps
define('salt\I18N_GENERATE', TRUE); // a définir une seule fois, ou à chaque mise à jour de SALT, AVANT Salt::config();
Salt::config(); // va lancer la regeneration et arreter l'application.
// Affiche : "SALT I18n classes generated - exit application. Please remove salt\I18N_GENERATE constant"
La méthode
generate() prend un paramètre optionnel
$display qui peut être passé à
TRUE pour que SALT affiche un compte rendu des opérations effectuées.
Sinon il est possible de récupérer son retour qui sera la liste des locales générées.
Vérification
Il est également possible de demander à SALT de vérifier la cohérence des fichiers de langues avec le code suivant :
<?php
$i18n = I18n::getInstance('APP', RELATIVE);
$i18n->check(); // va vérifier tout les fichiers de langues
// ou pour les fichiers de SALT :
define('salt\I18N_CHECK', TRUE);
Salt::config(); // lance la vérification et arrête l'exécution
La méthode
check() prend un paramètre optionnel
$display qui peut être passé à
TRUE pour que SALT affiche un compte rendu des opérations effectuées.
Sinon il est possible de récupérer son retour qui sera un tableau associatif indexé par les locales et dont la valeur est un tableau contenant 3 clés :
- count: Le nombre d'entrées dans le fichier de locale
- missing: La liste des entrées absentes dans le fichier de locale mais présentes dans la locale par défaut (sera toujours vide pour les variations de locale comme en_gb)
- orphan: La liste des entrées présentes dans le fichier de locale mais abstentes dans la locale par défaut
Sécurité
Si le dossier contenant vos fichiers
.yml (
lang) est dans votre arborescence web, n'oubliez pas de le protéger avec un
.htaccess.
Par exemple :
<IfModule mod_version.c>
<IfVersion >= 2.4>
Require all denied
</IfVersion>
<IfVersion < 2.4>
Deny from all
</IfVersion>
</IfModule>
<IfModule !mod_version.c>
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Deny from all
</IfModule>
</IfModule>
Ce fichier est ajouté automatiquement dans le dossier des classes générées (
cache), mais pas dans le dossier des locales.