Internationalisering med @angular/localize

Der er sket store fremskridt på i18n-fronten!En ny pakke kaldet @angular/localize er blevet introduceret i Angular 9.0.

Den bruges under motorhjelmen til at give os de samme funktioner, som vi havde tidligere: oversættelser i skabeloner på kompileringstidspunktet.

Men den lader os håbe på mere i fremtiden,med udokumenterede funktioner, der allerede er tilgængelige,som oversættelser i kode,eller oversættelser på køretid i stedet for kun kompileringsoversættelser 😎.

Lad os starte med at se, hvad vi kan gøre ved hjælp af @angular/localizeog CLI’en i v9.

i18n i skabeloner

Den nye @angular/localize-pakke tilbyder en funktion kaldet $localize.

Den eksisterende i18n-understøttelse i Angular bruger nu $localize,hvilket betyder, at skabeloner som:

<h1 i18n>Hello</h1>

bliver kompileret til $localize-opkald.

Hvis du kører ng serve med en sådan skabelon, vil du støde på en køretidsfejl:

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.)

Fejlen er selvforklarende:Da i18n-attributterne nu konverteres til $localize-opkaldi den genererede kode, skal vi indlæse $localize-funktionen.Det kunne gøres som standard,men da det kun er påkrævet, hvis du bruger internationalisering,ville det ikke rigtig give mening.

Det er derfor, at hvis din applikation, eller en af dens afhængigheder, bruger i18n-attributteri sine skabeloner, så skal du tilføje import '@angular/localize/init' til dine polyfills!

CLI tilbyder et skema til at gøre dette for dig.Du skal blot køre:

ng add @angular/localize

og CLI’en tilføjer pakken til dine afhængigheder og den nødvendige import til dine polyfills.

Når du så kører dit program med en simpel ng serve, viser $localize blot den oprindelige meddelelse.

Hvordan oversætter du nu disse meddelelser?

Processen er meget lig den, vi havde tidligere.Først kører du ng xi18n for at udtrække beskederne i en messages.xlf fil. derefter oversætter du filen til dine sprogindstillinger, f.eks. messages.fr.xlfog messages.es.xlf.

Så skal du konfigurere CLI’en, i 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" } } } // ...}

Nu giver es eller fr konfigurationer mulighed for at køre:

ng serve --configuration=fr

Og den app, der serveres, er nu på fransk!

Du kan også bygge appen med et bestemt sprogområde:

ng build --configuration=production,es

eller med alle sprogområder på én gang:

ng build --prod --localize

Dette er et stort fremskridt i forhold til tidligere Angular-versioner.Vi var tidligere nødt til at bygge den samme applikation for hvert sprogområde,da oversættelsen var en del af kompileringen.Nu, når Angular kompilerer applikationen, genererer den $localize-opkald. når det er gjort, tager et værktøj den kompilerede applikation og erstatter alle $localize-opkald med de rigtige oversættelser. det er super hurtigt. du har så et bundle, der ikke indeholder nogen opkald til $localize, og alle i18n-strenge er blevet oversat.

Hertil skulle du bygge din applikation én gang pr. locale, og det var et fuldt build. Så lad os sige, at det er en 30s build, og du ville have 4 locales, så var du i gang med 2 minutter.

Med den nye tilgang, kompileres der én gang, og derefter genereres de forskellige i18n-versioner på få sekunder(det genereres endda parallelt, hvis det er muligt).Så du går fra 2 minutter til ~40 sekunder! 🌈

Du har så flere bundles, en pr. locale, og du kan servere den passende til dine brugere afhængigt af deres præferencer, som du plejede at gøre.

Denne strategi kaldes compile-time inlining,da du inliner oversættelserne direkte,og så er der intet tilbage at gøre ved runtime.

Nu skal vi tale om noget, der stadig ikke er dokumenteret, som måske ændres i fremtiden, men som stadig er interessant at vide: Vi kan nu også oversætte meddelelser i vores TypeScript-kode!

Den $localize-funktion, jeg har talt om, kan bruges direkte. Det er en ejendommelig funktion, som du kan bruge til at tagge en skabelonstreng til lokalisering.

Men måske skal vi starte med at forklare, hvad en tagget skabelonstreng er?

Skabelonstrenge og tag-funktioner

Når du bruger skabelonstrenge, kan du definere en tag-funktion,og anvende den på en skabelonstreng.Her tilføjer askQuestion et forespørgselspunkt i slutningen af strengen:

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

Så hvad er forskellen med en simpel funktion?Tag-funktionen modtager faktisk flere argumenter:

  • en array af de statiske dele af strengen
  • værdierne som følge af evalueringen af udtrykkene

For eksempel hvis vi har en skabelonstreng, der indeholder udtryk:

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

så modtager tag-funktionen de forskellige statiske og dynamiske dele.Her har vi en tag-funktion til at skrive hovedpersonernes navne med store bogstaver:

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 med $localize i TypeScript-kode

$localize bruger denne mekanik til at lade os skrive:

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

Bemærk, at du ikke behøver at importere funktionen.Så længe du tilføjer import '@angular/localize/init' én gang i dit program, vil $localize blive tilføjet til det globale objekt.

Du kan derefter oversætte meddelelsen på samme måde som for en skabelon.Men lige nu (v9.0.0.0) udtrækker CLI’en ikke disse meddelelser med kommandoen xi18n, som den gør for skabeloner.

Hvis du tjener programmet, og der ikke findes nogen oversættelse, viser $localize blot den originale streng og logger en advarsel i konsollen:

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

Så du skal manuelt tilføje den til din messages.fr.xlfmed det givne IDhvis du vil prøve:

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

Skabelonen i min HomeComponent viser derefter Vous avez 10 utilisateurs!

Hvad sker der, hvis du har et eller andet dynamisk udtryk i din skabelonstreng?

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

Udtrykkene får automatisk navnene PH og PH_1 (PH er for placeholder).Derefter kan du bruge disse placeholdere hvor som helst i oversættelserne:

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

Men den bedste praksis er at give udtrykket selv et meningsfuldt placeholdername,og det kan du gøre ved at bruge ${expression}:placeholder:-syntaksen.

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

Så kan du bruge denne placeholder hvor som helst i oversættelserne:

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

Brugerdefinerede ID’er

Bemærk, at hvis du har oversættelser med brugerdefinerede ID’er, bliver de brugt af $localize (som det var tilfældet tidligere):

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

Så ser din oversættelse ud som:

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

hvilket naturligvis er pænere at bruge.

Hvad med oversættelser i kode?$localize forstår også en syntaks, der gør det muligt at angive et ID:

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

Syntaksen for det brugerdefinerede ID er den samme som i skabelonerne,og ID’et er omgivet af koloner for at adskille det fra indholdet af oversættelsen.

Som i syntaksen for skabelonerne kan du også angive en beskrivelse og en betydning,for at hjælpe oversætterne med en smule kontekst: :meaning|description@@id:message.

For eksempel:

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

Hold i tankerne, at dette er en API på lavt niveau og udokumenteret API.Angular-holdet eller -fællesskabet vil sandsynligvis tilbyde funktioner på højere niveau med en bedre udvikleroplevelse (det håber jeg da!).Locl fra Olivier Combe,forfatteren af ngx-translateer nok værd at holde øje med 🧐.

Runtime-oversættelser

Som jeg nævnte, hvis du bruger CLI-kommandoerne ovenfor (ng serve --configuration=fr eller ng build --localize), så bliver programmet kompileret og derefter oversat, før det rammer browseren, så der er ingen $localize-opkald på runtime.

Men $localize er designet til at tilbyde en anden mulighed: runtime-oversættelser.Hvad betyder det? Vi vil kunne sende kun én applikation, der indeholder $localize-opkald, og inden applikationen starter, kan vi indlæse de oversættelser, vi ønsker.Ikke flere N builds og N bundles for N locales \o/

Suden at dykke for meget ned i detaljerne er dette allerede muligt med v9 ved at bruge loadTranslations-funktionen, der tilbydes af @angular/localize.Men det skal gøres, før programmet starter.

Du kan indlæse dine oversættelser i polyfills.ts med:

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

Som du kan se, er der ingen hensyn til lokaliteterne: du indlæser bare din oversættelse som et objekt, hvis nøgler er de strenge, der skal oversættes, og værdierne, deres oversættelser.

Nu, hvis du kører en simpel ng serve, vises titlen på fransk!Og der er ikke længere behov for ng xi18n, eller messages.fr.xlfeller specifik konfiguration for hvert lokalområde i angular.json.På lang sigt, når dette vil blive ordentligt understøttet og dokumenteret, bør vi kunne indlæse JSON-filer ved kørselstid, som de fleste i18n-biblioteker gør.Du kan endda opnå det i v9, det er bare lidt manuelt arbejde, men det kan lade sig gøre.

Hvad med at ændre locale on the fly så?Kan vi indlæse et andet sæt oversættelser, når programmet startes?Tja, nej. Den nuværende måde, hvorpå $localize-opkald genereres, gør det umuligt at ændre dem bagefter: du skal genstarte programmet.Men hvis du ikke har noget imod at opdatere din browser, er det muligt.Jeg testede en simpel strategi, der virker:

  • en bruger vælger et nyt sprog (f.eks. spansk).
  • vi gemmer sproget i browseren (f.eks. i localStorage)
  • vi genindlæser siden, hvilket genstarter programmet
  • i polyfills.ts starter vi med at læse det lagrede sprog
  • vi indlæser det korrekte sæt oversættelser for spansk med loadTranslations.

Dette vil selvfølgelig være mere smidigt i fremtiden, enten i en fremtidig version af Angular eller via et bibliotek fra økosystemet.Under alle omstændigheder nærmer vi os at kun sende én version af vores applikation og blot indlæse oversættelserne ved kørselstid \o/

Alle vores materialer (e-bog, onlinetræning og træning) er opdateret med disse ændringer, hvis du vil lære mere!

Leave a Reply