Internationalisation¶
Ce chapitre aborde la thématique de l'internationalisation au niveau du moteur Ewt et au niveau des applications.
Généralités¶
D'une manière générale, tous les messages (informations, avertissements, erreurs) et libellés (noms de modèles, noms de groupes, noms de champs, etc.) présentés à l'utilisateur final peuvent être internationalisés, c'est-à-dire adaptés en fonction de la locale de la session utilisateur. En réalité l'internationalisation va plus loin que la simple correspondance de langue. Les formats de valeurs, les formats de dates, etc. doivent également tenir compte de la locale pour laquelle ils sont destinés.
Les messages internes au moteur et à l'application, c'est-à-dire les messages de debug ou d'erreur qui ne sont pas directement montrés à l'utilisateur peuvent être rédigés "en dur". Ils sont généralement rédigés en anglais.
i18n
Dans la suite du chapitre nous utiliserons le terme "i18n" pour désigner l'internationalisation. Ce terme est une version abrégée du mot "internationalisation", où 18 représente le nombre de caractères entre le i initial et le n final du mot.
L'internationalisation s'articule autour de bundles de langues. Un bundle
de langues est un ensemble de fichiers ayant un préfixe commun et un suffixe
désignant la locale gérée. Ces fichiers doivent se retrouver dans un même
dossier. En l'occurrence, Ewt recherche les bundles d'une application dans
le sous-dossier i18n
de l'application.
Par exemple, Ewt utilise un bundle par défaut qui est nommé descript
pour
internationaliser les libellés relatifs à la descript (le nom du bundle peut
être changé au moyen de la propriété bundles.descriptBundle
dans le
fichier de config de l'application). On peut ainsi définir un fichier
descript_fr.properties
pour y spécifier les libellés pour le français,
descript_de.properties
pour l'allemand, descript_en.properties
pour
l'anglais, etc. Le moteur s'appuie sur la valeur de la propriété
bundles.defaultLocale
pour déterminer la
locale de secours à utiliser lorsqu'il n'existe pas de fichier correspondant
à la locale actuelle.
Bundle embarqué dans Ewt
Ewt embarque également son propre bundle pour les messages d'erreur
qu'il doit éventuellement afficher lui-même. Le bundle de langue du
moteur est enregistré dans le dossier src/main/i18n
des sources.
La suite de ce document revient sur ces notions.
Application¶
Comme mentionné en introduction, les bundles de langues d'une application
sont à créer au niveau d'un sous-dossier i18n
. Un bundle est constitué de
un ou plusieurs fichiers ayant un préfixe commun (ce préfixe est le nom du
bundle). Il est possible d'adjoindre un suffixe désignant la langue pour
laquelle le fichier est valable. Les fichiers portent l'extension
.properties
.
Ainsi, les fichiers mywindow_fr.properties
, mywindow_de.properties
et
mywindow_en.properties
constituent le bundle mywindow
pour lequel on
définit 3 jeux de libellés: un fichier de libellés pour le français, un
fichier pour l'anglais et un fichier pour l'allemand.
Bundle descript
¶
Par défaut, le moteur charge le bundle descript
et y recherche les
libellés des modèles, groupes, champs, etc. Il est possible de définir des
bundles supplémentaires en fonction des besoins de l'application. Par
exemple, si on souhaite construire un style qui contient des libellés
spécifiques, on pourra créer un bundle propre à ce style. Pour ce faire, il
suffit de créer un bundle avec un nom personnalisé (autre que descript
),
puis de lier ce bundle au style via une entrée bundles.style
du fichier de
configuration. Dans le cas de l'exemple du bundle mywindow
donné plus
haut:
- on crée un style
mystyle
- on crée un bundle
mywindow.properties
-
on lie le bundle au style via l'entrée suivante:
1 2 3 4
<bundles> <style name="mystyle">mywindow</style> ... </bundles>
Ainsi, le moteur reprendra automatiquement les libellés de
mywindow.properties
dans l'arbre de sortie lorsqu'une page est affichée
avec le style mystyle
.
Structure de fichier¶
Chaque fichier de properties est un ensemble d'entrées key = value
séparées par un retour ligne, où key
est le nom d'une propriété et
value
est le libellé associé.
Arguments¶
Un libellé peut contenir des arguments, c'est-à-dire des marqueurs qui seront substitués par des valeurs au moment de la récupération du libellé.
Les marqueurs en question sont des nombres allant de 1 à 9 et encadrés par
des accolades, par exemple {0}
, {1}
, etc. Un exemple d'utilisation de
ces marqueurs est donné dans la documentation de la méthode
$i18n.getLabel
.
Attention, le caractère apostrophe ('
) joue un rôle important et peut
désactiver le traitement des marqueurs. Veuillez lire le chapitre ci-dessous
pour comprendre ce qu'il en est.
Échappements¶
Le moteur s'appuie sur la classe java MessageFormat pour la gestion des arguments.
Cela entraîne deux conséquences:
-
Cette classe s'attend à ce que les messages soient correctement échappés. Le caractère d'échappement est l'apostrophe (
'
). Cela signifie que les apostrophes que l'on souhaite faire apparaître dans le libellé doivent être doublés. Par exemple:message.exemple = Veuillez corriger l''erreur.
-
Les références de variables du genre
${mavariable}
,${data:monchamp}
ou${date:dd/MM/yyyy}
doivent être échappées sous peine d'être mal interprétées.Il faut donc les encadrer par des apostrophes, ce qui donne
'${mavariable}'
,'${data:monchamp}'
et'${date:dd/MM/yyyy}'
.En réalité le caractère
'
joue un rôle de bascule : le premier'
désactive le traitement des marqueurs, le second le réactive, le troisième le désactive à nouveau, et ainsi de suite.Dans l'exemple suivant, nous regardons comment le moteur interprète les libellés dans les 4 cas suivants:
message.test1 = "Test {0}: L'exemple du ${date:dd/MM/yyyy} avec {1} et {2}." message.test2 = "Test {0}: L'exemple du '${date:dd/MM/yyyy}' avec {1} et {2}." message.test3 = "Test {0}: L''exemple du ${date:dd/MM/yyyy} avec {1} et {2}." message.test4 = "Test {0}: L''exemple du '${date:dd/MM/yyyy}' avec {1} et {2}."
1 2 3 4
$logger.info($i18n.getLabel("message.test1", {arguments: [ 1, "a", "b" ] })); $logger.info($i18n.getLabel("message.test2", {arguments: [ 2, "a", "b" ] })); $logger.info($i18n.getLabel("message.test3", {arguments: [ 3, "a", "b" ] })); $logger.info($i18n.getLabel("message.test4", {arguments: [ 4, "a", "b" ] }));
L'exemple ci-dessus génère les traces suivantes:
Test 1: Lexemple du 16/01/2025 avec {1} et {2}. Test {0}: L'exemple du '16/01/2025' avec {1} et {2}. Test {0}: L''exemple du 16/01/2025 avec {1} et {2}. Test 4: L'exemple du 16/01/2025 avec a et b.
Pour les tests 1 et 2, l'apostrophe de "L'exemple" n'est pas échappée. Par conséquent, elle désactive le traitement des arguments pour la suite du libellé, ce qui explique que les marqueurs
{1}
et{2}
ne sont pas substitués.Le marqueur
{0}
du test 1 est substitué car il est placé avant l'apostrophe de "L'exemple".Le marqueur
{0}
du test 2 n'est par contre pas substitué car en réalité tout le processus de substitution des marqueurs échoue. En effet, l'apostrophe placé avant${date:dd/MM/yyyy}
réactive le traitement des marqueurs, mais ce traitement échoue cardate:dd/MM/yyyy
n'est pas une référence d'argument valide. Lorsque ce cas de figure se produit, le moteur essaie de fournir un libellé sans remplacement d'arguments.Le test 3 rencontre le même problème : la référence
${date:dd/MM/yyyy}
n'est pas échappée, ce qui fait planter le mécanisme de traitement des arguments.Le test 4 est correct car l'apostrophe de "L'exemple" est doublé, et la référence
${date:dd/MM/yyyy}
est échappée.Comment ne pas substituer les références de variables
Si on souhaite que la valeur en sortie contienne le texte "${date:dd/MM/yyyy}" en clair plutôt que la date qu'elle représente, on a deux manières de le faire:
- On peut utiliser l'option
raw
de la méthode$i18n.getLabel
-
On peut échapper la référence elle-même avec le caractère
$
, comme indiqué au chapitre Échappement de variables. Cela donne:message.test4 = "Test {0}: L''exemple du '$${date:dd/MM/yyyy}' avec {1} et {2}."
- On peut utiliser l'option
Libellés multilignes¶
Il est possible de découper le libellé sur plusieurs lignes. Pour ce faire,
il faut utiliser le caractère \
à la fin de la ligne pour indiquer la
coupure. Par exemple:
message.exemple = Ceci est un long message \
qui est découpé en \
plusieurs lignes
L'indentation ajoutée sur les lignes supplémentaires est ignorée.
Fichiers de langue¶
Le bundle descript
permet de spécifier les libellés de champs.
Un libellé de champ sera désigné par son identifiant logique. Par exemple
l'entrée field.vendeur.info.datenaissance.label
correspond au libellé du
champ datenaissance
présent dans le groupe info
du modèle vendeur
. On
utilisera donc la propriété suivante pour spécifier le libellé:
field.vendeur.info.datenaissance.label = Birthdate
Le préfixe field
indique qu'il s'agit d'un libellé portant sur un champ.
Le moteur gère les préfixes suivants:
model
: désigne un modèlegroup
: désigne un groupefield
: désigne un champ
Le suffixe .label
désigne ici l'étiquette du champ. Le moteur prévoit
différents suffixes prédéfinis:
.label
: étiquette du champ.description
: description détaillée du champ (utilisable dans untitle
ou une aide en ligne).placeholder
: valeur de placeholder (info affichée dans le champ lorsque celui-ci est vide)
Le format attendu pour les libellés relatifs aux états et aux transitions sont détaillés dans la documentation qui traite des modèles d'états.
Génération de fichier de langue
Ewt met à disposition une fonction pour faciliter la création d'un
fichier de langue. La fonction admin
propose une action
gen-language-canvas
qui construit le canevas de base d'un fichier
de langue.
Namespace ch.epilogic.ewt.i18n
¶
Le moteur retrouve automatiquement les libellés correspondant aux modèles,
groupes et champs, selon le principe décrit ci-dessus (par exemple pour un
champ il recherche un libellé correspondant à une entrée ayant la forme
field.modelname.groupname.fieldname.label
dans le bundle descript
).
Il n'y a donc rien besoin de spécifier au niveau du descript.xml
pour
faire le lien entre un modèle, un groupe ou un champ et le libellé i18n
correspondant.
Toutefois, pour les autres types d'éléments, il devient nécessaire
d'indiquer le nom de la propriété contenant le libellé i18n. Pour ce faire,
on utilisera le
namespace ch.epilogic.ewt.i18n
.
Par convention, on mappera ce namespace sur le préfixe i18n
.
Ainsi, il est possible de référencer une propriété et d'indiquer que l'on souhaite l'utiliser comme label. Prenons l'exemple suivant:
1 2 3 4 5 6 7 8 9 10 |
|
Dans cet exemple, on a défini des attributs i18n:label
dont le préfixe
i18n
référence le namespace ch.epilogic.ewt.i18n
. Cela indique au moteur
qu'il doit récupérer la valeur de la propriété indiquée et la placer dans
l'attribut label
. Ainsi à l'exécution pour une locale en français,
la propriété "categories" apparaîtrait ainsi dans l'arbre de sortie:
1 2 3 4 5 |
|