md
Traduction d'applications de type console dans Free Pascal
2017-03-14

Il s'avère que DefaultTranslator ne peut pas être utilisé dans les applications de type console. La fonction SetDefaultLang, de l'unité LCLTranslator, qui est utilisée pour basculer entre les fichiers de traduction tire le paquet LCL entier. Or SetDefaultLang veut traduire toutes les fiches du projet. Bien sûr il n'y a pas de fiches dans une application console, mais la fonction ne le sait pas.

J'ai donc décidé de retirer la traduction des fiches du code dans LCLTranslator et d'enregistrer cette version modifiée dans une unité appelée UnitTranslator. Cette nouvelle unité nécessite le paquet LCLBase au lieu du paquet LCL complet.

Comportement douteux de SetDefaultLang dans LCLTranslator

Si l'on veut sélectionner manuellement une traduction française adaptée à la Belgique d'une application (appelée app) alors, il devrait suffire d'appeler SetDefaultLang('fr_BE',''). La fonction recherchera un fichier .po ou .mo correspondant dans tous les "lieux habituels". Cela signifie que les fichiers suivants sont recherchés :

Si un tel fichier n'est pas trouvé, un fichier .mo compilé est recherché dans les mêmes endroits. Si aucun fichier .po ou .mo n'a été trouvé, la localisation sera réduite à la langue ('fr' dans ce cas) et la recherche sera répétée:

C'est logique: si une traduction vers une variation territoriale d'une langue n'est pas disponible, alors aussi bien utiliser une traduction générique dans cette langue. Jusqu'ici tout va bien. Mais les choses peuvent aller mal.

Habituellement, l'unité DefaultTranslator est ajoutée aux applications qui sont fournies avec des fichiers de traduction. Elle se résume à l'appel SetDefaultLang('','') dans son initialisation. Lorsqu'aucune langue n'est spécifiée, SetDefaultLang essaie de trouver la langue du système, puis effectue la recherche décrite ci-dessus. Dans Linux, cette fonction examine les variables d'environnement 'LANG', 'LC_ALL' et 'LC_MESSAGES' dans cet ordre. Sur mon système, 'LANG' contient «fr_CA.UTF-8», qui est une langue (fr pour le français), une région (CA pour le Canada) et un codage Unicode (UTF-8).

Donc ce qui se passe, c'est qu'une recherche pour les fichiers suivants est menée:

Qui ne produit rien et alors la localisation est réduite à 'fr' et la recherche se répète : La fonction ne recherche jamais 'fr_CA'. Je pense que ce n'est pas le comportement prévu.

Dans UnitTranslator, qui est ma version du code, j'ai décidé de créer une fonction GetLang(const Lang: string): string qui interroge les variables d'environnement comme dans le code d'origine, mais qui, à la fin, ajoute un test pour gérer les paramètres régionaux tels que trouvés sur mon système.

if (length(Result) > 5) and (Result[3] = '_') then setlength(Result, 5);

La raison du test pour le '_' est que le répertoire /usr/share/locale de mon system Ubuntu contient des sous répertoires tels que 'be@latin' dont les noms ne doivent pas être amputés à 5 caractères.

Puisque je réécrivais le code pour la fonction FindLocaleFileName, j'ai également décidé de tester l'existence des sous-répertoires 'locale' et 'languages' avant d'exécuter une recherche de fichiers .po et .mo. Il est inutile de rechercher des fichiers dans des répertoires non existants.

Enfin, j'ai incorporé DefaultTranslator dans UnitTranslator en incluant un appel à SetDefaultLang('','') dans le code d'initialisation de cette dernière unité.

L'unité peut être trouvée ici. Je suis incertain quant aux droits d'auteur impliqués ici. Si j'avais écrit cette unité sans regarder le code de V.I. Volchencko et al, j'aurais utilisé une licence de type BDS à deux ou trois clauses. En ce qui me concerne, n'importe qui peut utiliser le code de façon éthique.

La bonne variable d'environnement

En examinant l'environnement de mon système Ubuntu, il me semble que la variable 'LANGUAGE' aurait peut-être été un meilleur choix.

michel@hp:~$ printenv ... LANG=fr_CA.UTF-8 GDM_LANG=fr_CA ... LANGUAGE=fr_CA:fr ... michel@hp:~$ locale LANG=fr_CA.UTF-8 LANGUAGE=fr_CA:fr LC_CTYPE="fr_CA.UTF-8" LC_NUMERIC="fr_CA.UTF-8" LC_TIME="fr_CA.UTF-8" LC_COLLATE="fr_CA.UTF-8" LC_MONETARY="fr_CA.UTF-8" LC_MESSAGES="fr_CA.UTF-8" LC_PAPER="fr_CA.UTF-8" LC_NAME="fr_CA.UTF-8" LC_ADDRESS="fr_CA.UTF-8" LC_TELEPHONE="fr_CA.UTF-8" LC_MEASUREMENT="fr_CA.UTF-8" LC_IDENTIFICATION="fr_CA.UTF-8" LC_ALL=

Ceci est plus ou moins confirmé par la réponse de Rémi (modifiée par Édouard Lopez) à une question posée sur SuperUser. Il semble que l'utilisateur pourrait avoir quelque chose comme «LANGUAGE=fr:de:en» ce qui signifie que les messages doivent être en «français s'ils existent, sinon en allemand , et qu'ils passeront à l'anglais si un message français ou allemand ne se trouve pas».

Le hic est que je ne sais pas si c'est universel. La question portait sur les systèmes Debian. Qu'en est-il des autres distributions Linux ? La page man de Locale (7) ne mentionne pas 'LANGUAGE'. La page man sur gettext (3) indique qu'il s'agit d'une 'extension Gnu':

If the LANGUAGE environment variable is set to a nonempty value, and the locale is not the "C" locale, the value of LANGUAGE is assumed to contain a colon separated list of locale names. The functions will attempt to look up a translation of msgid in each of the locales in turn. This is a GNU extension.

Peut-être l'approche correcte est de supposer qu'il devrait y avoir une liste de langues à rechercher. La variable LANGUAGE doit être la première à être examinée pour établir cette liste. Si elle n'est pas présente, les autres variables d'environnement doivent être examinées.

Si seulement les choses étaient aussi simples. La variable 'LC_ALL', si elle est définie, a primauté sur les autres alors que 'LANG' est la valeur par défaut quand une autre n'est pas définie. Si je comprends bien toutes ces informations, les valeurs suivantes

LANG="en_GB"
LC_MESSAGES="fr_CA.UTF-8"
LC_MEASUREMENT=
LC_ALL=
veulent dire que 'en_GB' est utilisée pour LC_MEASUREMENT, alors que les messages utilisent 'fr_CA.UTF-8'. Par contre si 'LC_ALL' avait été
LC_ALL="de"
alors 'de' doit être utilisé pour 'LC_MEASUREMENT' et 'LC_MESSAGES'. Mais est-ce que cette primauté s'applique à 'LANGUAGES' ?

Puisque le sujet de la préséance de 'LC_ALL' est abordé, j'enchaîne en notant que la première variable d'environnement examinée dans GetLanguageIDs de gettext et puis 'LC_MESSAGES' est considéré et enfin 'LANG'. Tout cela est correct à ma connaissance. Mais dans LCLTranslator, la variable d'environnement 'LANG' est vérifiée avant d'appeler LazGetLanguageIDs qui à son tour appelle GetLanguageIDs. Cela signifierait que 'LC_ALL' serait ignoré! Il y a un commentaire dans le code que cela a quelque chose à faire avec Windows.

J'ai mal à la tête.