Réaliser des traductions naturelles grâce à Fluent

Un problème polyglotte

Les projets à portée internationale sont fréquemment confrontés au casse-tête qu’est la traduction du texte dans la langue de l’utilisateur.

Les systèmes de traductions et localisations classiques montrent rapidement leur limites avec des langues à grammaire éloignée de l’Anglais. Même en Français, une langue relativement proche, on rencontre le cas épineux des noms genrés qui viennent modifier nos accords de participes passés.
Par exemple un logiciel conçu pour un public Anglophone réalisera une seule clé de traduction pour un statut Received où tous les usages correspondent.
La traduction Française devra prendre en compte les accords et aura donc tout ces statuts possibles : Reçu, Reçue, Reçus et Reçues.

Une solution et un outil adaptés

Le problème précédent peut être contourné à l’aide de clés de traductions spécifiques à des phrases mais la spécificité bondit quand on introduit l’usage des nombres.
Prenons l’exemple classique des problèmes de traductions de quantité. En Anglais et Français, nous ne différencions que le singulier du pluriel. Alors qu’en Tchèque, on différencie un élément unique d’une faible quantité puis d’une quantité considérable selon une règle complexe.
On imagine bien la difficulté de devoir créer des clés adaptées au Tchèque et aux autres langues d’origine Slave pour tous les textes de notre application utilisant des quantités.

Fluent aide à résoudre ces problématiques en changeant le paradigme de traduction. Là où la plupart des librairies et frameworks demandent au développeur d’intégrer dans le code source la logique séparant les différentes formes possibles des langages, Fluent renvoie la responsabilité au traducteur et fichier de traduction.

Prenons notre problème de quantité. Nos traductions à l’aide d’une librairie habituelle ressembleraient à ceci :


if (this.apples === 1) {

    this.label = this.translationService.translate(‘LABEL_APPLE_SINGLE’);

} else if (this.apples >= 2 && this.apples <= 4) {

    this.label = this.translationService.translate(‘LABEL_APPLE_FEW’, {count: this.apples});

} else if (this.apples >= 5) {

    this.label = this.translationService.translate(‘LABEL_APPLE_MANY’, {count: this.apples});
}

En associant un fichier de traduction suivant un format similaire :

#FR:
label.apple.single=Il y a une pomme.
label.apple.few=Il y a ${count} pommes.
label.apple.many=Il y a ${count} pommes.

#CZ:
label.apple.single=Existuje 1 jablko.
label.apple.few=Existuje ${count} jablka.
label.apple.many=Existuje ${count} jablek.

Fluent nous permet de simplifier le code source :

this.label = this.translationService.translate(‘LABEL_APPLE’, {count: this.apples});

Avec le fichier de traduction suivant:

#FR:
label.apple= { $count -> 
		[one] Il y a 1 pomme.
		*[other] Il y a { $count } pommes.

#CZ:
label.apple = { $count ->
		[one] Existuje 1 jablko.
		[few] Existuje { $count } jablka.
		*[other] Existuje { $count } jablek.

Ces exemples illustrent bien le concept primaire du projet Fluent, la mise en place d’une asymétrie entre la traduction et le code source. Ainsi il est possible de prendre en compte les spécificités d’un langage uniquement dans le fichier de traduction concerné sans impacter les autres ni nécessiter une nouvelle logique dans le code source.

Fluent plus en détail

Il consiste en une spécification de traduction, un ensemble de bonnes pratiques et des implémentations de référence.

Sa syntaxe modulaire permet de réaliser des traductions très complexes tout en restant le plus lisible possible. L’exemple précédent tire parti d’une fonctionnalité nommée les Selectors, qui elle-même s’appuie sur le projet CLDR pour faire correspondre nos sélecteurs aux bonnes quantités du langage.

Un autre outil majeur de Fluent sont les Fonctions. Elles permettent d’altérer des traductions selon des Paramètres. Les exemples précédents tiraient également parti des Paramètres en les liant aux Sélecteurs ; les Fonctions vont plus loin en permettant l’écriture de règles de format complexes.

Fluent possède deux Fonctions incluses de base, il est également possible d’en créer via le code source de votre application. Ainsi, indépendamment de la librairie utilisée, il est possible d’introduire la logique spécifique au besoin applicatif dans vos traductions.

La Fonction incluse la plus utile est certainement le format des nombres. Celle ci prend de nombreux paramètres en compte (currencyDisplay, useGrouping, minimumIntegerDigits, minimumFractionDigits, maximumFractionDigits, minimumSignificantDigits, maximumSignificantDigits).

Voici un exemple de traduction utilisant un format de nombre spécifique :

label.balance = You have { NUMBER($amount, currencyDisplay: "code", useGrouping: "false", minimumFractionDigits: 2) }

Ce qui donnerait le résultat suivant avec une entrée de 3.9 USD :

Locale FR -> 3,90$
Locale US -> 3.90$
On remarque le format visuel changeant selon la langue de l’utilisateur tout en respectant le format du fluentfile.

La seconde Fonction est responsable du format des dates, un autre point crucial des traductions pour les utilisateurs. Elle permet de sélectionner le bon format selon la langue de l’utilisateur. Cette fonction change également l’affichage des composants de la date tels que les jours de la semaine et mois, entre ‘long’ (Janvier, Février, Mars...) et ‘numeric’ (01, 02, 03…).

Conclusion

Fluent augmente fortement la vitesse de traduction des applications et permet d’alléger le travail des développeurs. Comme on peut le constater dans les exemples sa syntaxe est très claire, les créateurs du projet ayant priorisé sa facilité de lecture et de modification.

C’est dans la création d’applications internationales que le projet est le plus propice et que l’on peut tirer parti au mieux de ses fonctionnalités.

À l’heure actuelle il existe des implémentations officielles de Fluent en JavaScript, Python, Rust. Ainsi que des projets communautaires autour du .NET, ELM, LUA et autres. Pour le développement d’application Web des bindings React on été créés. Il n’y a, pour l’instant, pas de librairie spécifique à Angular ou Vue.js mais rien n’entrave l’utilisation de l’API JavaScript disposant de toutes les fonctionnalités de base de Fluent.