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/localize
og 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.xlf
og 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.xlf
med 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/> ! 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/> ! 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.xlf
eller 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