Internationalisation avec @angular/localize

De grands progrès ont été réalisés sur le front de l’i18n !Un nouveau paquet appelé @angular/localize a été introduit dans Angular 9.0.

Il est utilisé sous le capot pour nous donner les mêmes fonctionnalités que nous avions précédemment :les traductions dans les modèles au moment de la compilation.

Mais il nous laisse espérer plus à l’avenir,avec des fonctionnalités non documentées déjà disponibles,comme les traductions dans le code,ou les traductions au moment de l’exécution au lieu des traductions de compilation seulement 😎.

Débutons en voyant ce que nous pouvons faire avec l’aide de @angular/localizeet du CLI dans la v9.

i18n dans les templates

Le nouveau paquet @angular/localizeoffre une fonction appelée $localize.

Le support i18n existant dans Angular utilise maintenant $localize,ce qui signifie que les templates comme:

<h1 i18n>Hello</h1>

seront compilés en appels $localize.

Si vous exécutez ng serve avec un tel template, vous rencontrerez une erreur d’exécution:

Error: It looks like your application or one of its dependencies is using i18n.Angular 9 introduced a global `$localize()` function that needs to be loaded.Please run `ng add @angular/localize` from the Angular CLI.(For non-CLI projects, add `import '@angular/localize/init';` to your `polyfills.ts` file.For server-side rendering applications add the import to your `main.server.ts` file.)

L’erreur est auto-explicative:comme les attributs i18n sont maintenant convertis en appels $localizedans le code généré, nous devons charger la fonction $localize.Cela pourrait être fait par défaut, mais comme elle n’est requise que si vous utilisez l’internationalisation, cela n’aurait pas vraiment de sens.

C’est pourquoi si votre application, ou l’une de ses dépendances, utilise des attributs i18n dans ses modèles, alors vous devrez ajouter import '@angular/localize/init' à vos polyfills !

Le CLI offre un schéma pour le faire pour vous.Il suffit d’exécuter :

ng add @angular/localize

et le CLI ajoute le paquet à vos dépendances et l’importation nécessaire à vos polyfills.

Puis, lorsque vous exécutez votre application avec un simple ng serve,$localize affiche simplement le message original.

Maintenant, comment traduire ces messages?

Le processus est très similaire à ce que nous avions précédemment.D’abord vous exécutez ng xi18n pour extraire les messages dans un fichier messages.xlf.Ensuite vous traduisez le fichier pour vos locales, par exemple messages.fr.xlfet messages.es.xlf.

Puis vous devez configurer le CLI, en angular.json

{ "projects": { "ponyracer": { "projectType": "application", // ... "i18n": { "locales": { "fr": "src/locale/messages.fr.xlf", "es": "src/locale/messages.es.xlf", } }, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", // ... "configurations": { "production": { // ... }, "fr": { "localize": }, "es": { "localize": } } }, "serve": { // ... "configurations": { "production": { // ... }, "fr": { "browserTarget": "ponyracer:build:fr" }, "es": { "browserTarget": "ponyracer:build:es" } } } // ...}

Maintenant, les configurations es ou fr permettent d’exécuter :

ng serve --configuration=fr

Et l’application servie est maintenant en français !

Vous pouvez également construire l’application avec une locale spécifique:

ng build --configuration=production,es

ou avec toutes les locales à la fois:

ng build --prod --localize

C’est un grand progrès par rapport aux versions précédentes d’Angular.Nous devions auparavant construire la même application pour chaque locale,car la traduction faisait partie de la compilation.Maintenant, lorsque Angular compile l’application, il génère des appels $localize.Puis, lorsque cela est fait, un outil prend l’application compiléeet remplace tous les appels $localize par les traductions appropriées.C’est super rapide.Vous avez alors un bundle ne contenant aucun appel à $localizeet toutes les chaînes i18n ont été traduites.

Jusqu’à présent, vous deviez construire votre application une fois par locale,et c’était un build complet. Donc disons que c’est un build de 30s, et que vous vouliez 4 locales, alors vous étiez dedans pour 2 minutes.

Avec la nouvelle approche, la compilation est faite une fois, et ensuite les différentes versions i18n sont générées en quelques secondes(c’est même généré en parallèle si possible).Donc vous passez de 2 minutes à ~40 secondes ! 🌈

Vous avez alors plusieurs bundles, un par locale,et vous pouvez servir celui qui convient à vos utilisateurs en fonction de leur préférence comme vous le faisiez auparavant.

Cette stratégie est appelée compile-time inliningcar vous inlinez les traductions directement,et il ne reste plus rien à faire à l’exécution.

Maintenant, parlons de quelque chose d’encore non documenté, qui pourrait changer dans le futur, mais qui reste intéressant à savoir : nous pouvons maintenant aussi traduire des messages dans notre code TypeScript !

La fonction $localize dont j’ai parlé peut être utilisée directement. C’est une fonction particulière que vous pouvez utiliser pour baliser une chaîne de modèle pour la localisation.

Mais peut-être devrions-nous commencer par expliquer ce qu’est une chaîne de modèle balisée ?

Chaînes modèles et fonctions de balises

Lorsque vous utilisez des chaînes modèles, vous pouvez définir une fonction de balises,et l’appliquer à une chaîne modèle.Ici askQuestion ajoute un point d’interrogation à la fin de la chaîne :

const askQuestion = strings => strings + '?';const template = askQuestion`Is anyone here`;

Alors quelle est la différence avec une simple fonction ?La fonction tag reçoit en fait plusieurs arguments:

  • un tableau des parties statiques de la chaîne
  • les valeurs résultant de l’évaluation des expressions

Par exemple si nous avons une chaîne modèlecontenant des expressions:

const person1 = 'Cedric';const person2 = 'Agnes';const template = `Hello ${person1}! Where is ${person2}?`;

alors la fonction tag recevra les différentes parties statiques et dynamiques.Ici, nous avons une fonction tag pour mettre en majuscule les noms des protagonistes:

const uppercaseNames = (strings, ...values) => { // `strings` is an array with the static parts // `values` is an array with the evaluated expressions const names = values.map(name => name.toUpperCase()); // `names` now has // let's merge the `strings` and `names` arrays return strings.map((string, i) => `${string}${names ? names : ''}`).join('');};const result = uppercaseNames`Hello ${person1}! Where is ${person2}?`;// returns 'Hello CEDRIC! Where is AGNES?'

i18n avec $localize dans le code TypeScript

$localize utilise cette mécanique pour nous permettre d’écrire:

@Component({ template: '{{ title }}'})export class HomeComponent { title = $localize`You have 10 users`;}

Notez que vous n’avez pas besoin d’importer la fonction.Tant que vous ajoutez import '@angular/localize/init' une fois dans votre application,$localize sera ajouté à l’objet global.

Vous pouvez ensuite traduire le message de la même manière que pour un modèle.Mais, à l’heure actuelle (v9.0.0), le CLI n’extrait pas ces messages avec la commande xi18n comme il le fait pour les modèles.

Si vous servez l’application et qu’aucune traduction n’est trouvée,$localize affiche simplement la chaîne originale,et enregistre un avertissement dans la console :

No translation found for "6480943972743237078" ("You have 10 users").

Vous devez donc l’ajouter manuellement à votre messages.fr.xlfavec l’ID donnési vous voulez essayer:

<trans-unit> <source>You have 10 users</source> <target>Vous avez 10 utilisateurs</target></trans-unit>

Le template de mon HomeComponent affiche alors Vous avez 10 utilisateurs !

Que se passe-t-il si vous avez une expression dynamique dans votre chaîne de template ?

title = $localize`Hi ${this.name}! You have ${this.users.length} users.`;

Les expressions seront automatiquement nommées PH et PH_1 (PH est pour le placeholder).Ensuite, vous pouvez utiliser ces placeholders où vous voulez dans les traductions:

<trans-unit> <source>Hi <x/>! You have <x/> users.</source> <target>Bonjour <x/>&nbsp;! Vous avez <x/> utilisateurs.</target></trans-unit>

Mais la meilleure pratique est de donner vous-même un placeholdname significatif à l’expression,et vous pouvez le faire en utilisant la syntaxe ${expression}:placeholder:.

title = $localize`Hi ${this.name}:name:! You have ${this.users.length}:userCount: users.`;

Puis vous pouvez utiliser ce placeholder où vous voulez dans les traductions:

<trans-unit> <source>Hi <x/>! You have <x/> users.</source> <target>Bonjour <x/>&nbsp;! Vous avez <x/> utilisateurs.</target></trans-unit>

Identifiants personnalisés

Notez que si vous avez des traductions avec des identifiants personnalisés,ils sont utilisés par $localize (comme c’était le cas précédemment) :

<h1 i18n="@@home.greetings">Hello</h1>

Alors votre traduction ressemble à :

<trans-unit> <source>Hello</source> <target>Bonjour</target></trans-unit>

ce qui est évidemment plus agréable à utiliser.

Qu’en est-il pour les traductions dans le code ?$localize comprend également une syntaxe permettant de spécifier un ID:

title = $localize`:@@home.users:You have 10 users`;

La syntaxe pour l’ID personnalisé est la même que dans les modèles,et l’ID est entouré de deux points pour le séparer du contenu de la traduction.

Comme pour la syntaxe des modèles, vous pouvez également spécifier une description et une signification,pour aider les traducteurs avec un peu de contexte : :meaning|[email protected]@id:message.

Par exemple:

title = $localize`:greeting message with the number of users currently logged [email protected]@home.users:You have 10 users`;

N’oubliez pas qu’il s’agit d’une API de bas niveau, non documentée.L’équipe ou la communauté Angular proposera probablement des fonctions de plus haut niveau avec une meilleure expérience pour les développeurs (enfin, je l’espère !).Locl d’Olivier Combe,l’auteur de ngx-translatevaut probablement la peine d’être surveillé 🧐.

Traductions à l’exécution

Comme je le mentionnais, si vous utilisez les commandes CLI ci-dessus (ng serve --configuration=fr ou ng build --localize), alors l’application est compilée puis traduite avant de frapper le navigateur, il n’y a donc pas d’appels $localize à l’exécution.

Mais $localize a été conçu pour offrir une autre possibilité : les traductions à l’exécution.Qu’est-ce que cela signifie ? Eh bien, nous pourrions expédier une seule application, contenant des appels $localize, et avant que l’application ne démarre, nous pourrions charger les traductions que nous voulons.Plus de N builds et N bundles pour N locales \o/

Sans trop plonger dans les détails, cela est déjà possible avec la v9, en utilisant la fonction loadTranslationsofferte par @angular/localize.Mais cela doit être fait avant le démarrage de l’application.

Vous pouvez charger vos traductions dans polyfills.ts avec:

import '@angular/localize/init';import { loadTranslations } from '@angular/localize';loadTranslations({ '1815172606781074132': 'Bonjour {$name}\xa0! Vous avez {$userCount} utilisateurs.'});

Comme vous pouvez le voir il n’y a pas de considération de locale:vous chargez juste votre traduction comme un objet,dont les clés sont les chaînes à traduire,et les valeurs, leurs traductions.

Maintenant si vous exécutez un simple ng serve,le titre est affiché en français!Et plus besoin de ng xi18n, ou messages.fr.xlfou de configuration spécifique pour chaque locale dans angular.json.A long terme, quand cela sera correctement supporté et documenté,nous devrions pouvoir charger des fichiers JSON à l’exécution,comme le font la plupart des bibliothèques i18n.Vous pourriez même le réaliser dans la v9, c’est juste un peu de travail manuel, mais c’est faisable.

Qu’en est-il du changement de locale à la volée alors ? Pouvons-nous charger un autre ensemble de traductions lorsque l’application est lancée ? Eh bien, non. La façon actuelle dont les appels $localize sont générés les rend impossibles à modifier après : vous devez redémarrer l’application.Mais si cela ne vous dérange pas de rafraîchir votre navigateur, c’est possible.J’ai testé une stratégie simple qui fonctionne :

  • un utilisateur sélectionne une nouvelle langue (par exemple l’espagnol).
  • nous stockons la langue dans le navigateur (par exemple dans le localStorage)
  • nous rechargeons la page, ce qui redémarre l’application
  • dans polyfills.ts, nous commençons par lire la langue stockée
  • nous chargeons le jeu approprié de traductions pour l’espagnol avec loadTranslations.

Bien sûr, cela sera plus fluide à l’avenir, soit dans une future version d’Angular, soit via une bibliothèque de l’éco-système.Quoi qu’il en soit, nous nous rapprochons de ne livrer qu’une seule version de notre application, et de simplement charger les traductions au moment de l’exécution \o/

Tous nos matériaux (ebook, formation en ligne et formation) sont à jour avec ces changements si vous voulez en savoir plus!

Leave a Reply