Skip to content

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:

  1. 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.
    
  2. 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 car date: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:

    1. On peut utiliser l'option raw de la méthode $i18n.getLabel
    2. 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}."
      

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èle
  • group : désigne un groupe
  • field : 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 un title 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
<?xml version="1.0" encoding="UTF-8"?>
<descript xmlns:i18n="ch.epilogic.ewt.i18n">
  <properties>
    <property name="categories">
      <categorie name="vendeurs" i18n:label="descript.attribute.categorie.vendeurs.label"/>
      <categorie name="vente" i18n:label="descript.attribute.categorie.vente.label"/>
      <categorie name="administration" i18n:label="descript.attribute.categorie.administration.label"/>
    </property>
  </properties>
  ...

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
  <property name="categories">
    <categorie name="vendeurs" label="Vendeurs"/>
    <categorie name="vente" label="Vente"/>
    <categorie name="administration" label="Administration"/>
  </property>