Angular : internationaliser vos applications avec ngx-translate
Cet article a pour vocation de décrire la façon la plus simple d'internationaliser vos applications Angular à l'aide du package ngx-translate. Sa mise en place est très simple et il faudra cependant ne pas oublier de configurer les pipes Angular.
Installer la librairie
L'installation de la librairie se fait de manière très classique avec npm. Attention, la dernière version d'ngx-translate (10) n'est pas compatible avec Angular 5. Il y a aussi de fortes chances d'avoir besoin du package ngx-translate/http-loader qui vous permettra de charger les traductions depuis un fichier json.
Voici la commande à utiliser en spécifiant le numéro de version du package principal :
npm install @ngx-translate/core@^9.1.1 --save npm install @ngx-translate/http-loader --save
Créer un fichier de traduction
Les traductions se concrétisent sous la forme de fichiers JSON. Chaque propriété correspond à une traduction et il est possible d'imbriquer des objets afin de structurer les traductions. Il faut créer un fichier que par convention je nomme du nom de la culture ciblée et que je place dans le dossier "assets/i18n". Nous pouvons donc avoir un fichier fr.json de ce type :
{ "Home": { "WelcomeText": "Bonjour cher inconnu." } }
Il est aussi possible de renseigner des valeurs dynamiques dans vos traductions. Ces valeurs dynamiques ne sont rien d'autres que des bindings appliqués à partir d'un objet source que vous fournirez plus tard au mécanisme de traduction. Rajoutons donc le nom de notre visiteur dans la traduction précédente :
{ "Home": { "WelcomeText": "Bonjour cher inconnu", "WelcomeTextValue": "Bonjour cher {{nomDeLaPersonne}}." } }
Charger les traductions
La prochaine étape consiste à fournir les fichiers de traductions à ngx-translate. Pour cela on va déclarer le module TranslateModule dans notre module applicatif à l'aide de l'helper TranslateModule.forRoot auquel on fournira une configuration nécessaire pour charger le bon fichier. Cela se fait en renseignant la propriété loader de la configuration de cette manière :
@NgModule({ declarations: [...], imports: [ ... TranslateModule.forRoot({ loader: { provide: TranslateLoader, useFactory: (createTranslateLoader), deps: [HttpClient] } }) ], bootstrap: [...], entryComponents: [...], providers: [...] }) export class AppModule { }
La fonction createTranslateLoader doit être déclarée exportée pour être compatible avec l'AOT d'Angular ou Ionic :
export function createTranslateLoader(http: HttpClient) { return new TranslateHttpLoader(http, './assets/i18n/', '.json'); }
Sachez que dans notre cas on utilise un fichier de traduction placé dans les assets de l'application mais il serait tout à fait possible de charger celui-ci depuis une API. J'en donner un exemple dans ce StackBlitz où les valeurs sont chargées depuis un objet mémoire directement : https://stackblitz.com/edit/ngx-translate.
Utiliser les traductions depuis un template
Au sein de vos templates, l'utilisation se fait au travers de la pipe "translate" que vous utilisez directement sur le chemin de la ressource souhaitée.
<span>{{'Home.WelcomeTextAnonymous'|translate}}
Si votre traduction contient de valeurs à remplacer, il suffit de les donner en paramètre de la pipe. Soit vous indiquez directement la valeur (peu courant mais sait-on jamais), soit vous fournissez le nom d'un champ de votre composant contenant les valeurs. Vous remarquerez un espace après le '}' du paramètre dans le premier exemple : il est obligatoire pour être reconnu correctement par Angular.
<span> {{'Home.WelcomeTextValue'|translate:{'nomDeLaPersonne':'lecteur'} }} </span> <span> {{'Home.WelcomeTextValue'|translate:parametres }} </span>
Il existe une autre syntaxe à base d'attributs qui utilise le contenu de l'élément sur lequel il est placé comme clef. Cela est particulièrement pratique car cela permet de générer la clef de traduction dans un binding au sein d'un ngFor par exemple (la transclusion préférée d'Aurélie). Dans l'exemple ci dessous, mes clefs de traductions seront dans le format "TrackerType_XXX" avec XXX étant une valeur dynamique.
<span *ngFor="let type of availableTypes" [value]="type" translate> TrackerType_{{type}} </span>
Utiliser les traductions depuis le code
Pour cela, on récupère le service TranslateService par injection de dépendance et on utilise la méthode 'get' pour obtenir un observable contenant la traduction. Il s'agit d'un Observable car les valeurs seront produites à chaque changement de langue. Cette méthode prend potentiellement en paramètre un objet contenant la liste des valeurs à remplacer dans les traductions.
this.translate.get('Home.WelcomeText') .subscribe(val => this.fromCode = val); this.translate.get( 'Home.WelcomeTextValue', { nomDeLaPersonne: 'visiteur' }) .subscribe(val => this.fromCodeValued = val);
Il est aussi possible de récupérer plusieurs valeurs à la fois. Les résultats seront dans un objet ayant pour propriétés les clefs demandées. Il est possible de passer des valeurs d'interpolations qui seront les mêmes pour toutes les demandes de traduction :
this.translate.get( ['Home.WelcomeText', 'Home.WelcomeTextValue'], { nomDeLaPersonne: 'visiteur' } ) .subscribe(val => { this.fromCode = val['Home.WelcomeText']; this.fromCodeValued = val['Home.WelcomeTextValue']; });
Changer de langue à la volée
Le changement de langue à la volée est de base dans ngx-translate via la fonction "use". Il est aussi possible de choisir une langue par défaut via la fonction "setDefaultLang".
this.translateService.use("fr"); this.translateService.setDefaultLang("en");
Bon à savoir, translateService expose aussi un EventEmitter "onLangChange" déclenché lorsque la langue est modifiée.
Configurer les pipes natives angular
Tout est maintenant en place pour avoir une application internationalisée mais il reste cependant quelques endroits intéressants à modifier : les pipes natives angular prenant en compte une culture telles que DatePipe.
La solution la plus simple et rapide que j'ai trouvé consiste à créer une nouvelle Pipe dérivant de DatePipe et de passer la culture d'ngx-translate à son constructeur de base.
@Pipe({ name: 'i18nDate', }) export class I18nDatePipe extends DatePipe { constructor(translateService: TranslateService) { super(translateService.currentLang); } }
Happy coding :)
Commentaires