Internationalisering med @angular/localize

Vissa framsteg har gjorts på i18n-fronten!Ett nytt paket som heter @angular/localize har introducerats i Angular 9.0.

Det används under huven för att ge oss samma funktioner som tidigare: översättningar i mallar vid kompilering.

Men det låter oss hoppas på mer i framtiden,med odokumenterade funktioner som redan finns tillgängliga,som översättningar i koden,eller översättningar vid körtid istället för endast kompileringsöversättningar 😎.

Låt oss börja med att se vad vi kan göra med hjälp av @angular/localizeoch CLI:n i v9.

i18n i mallar

Det nya @angular/localize-paketet erbjuder en funktion som heter $localize.

Det befintliga i18n-stödet i Angular använder nu $localize,vilket innebär att mallar som:

<h1 i18n>Hello</h1>

kommer att kompileras till $localize-anrop.

Om du kör ng serve med en sådan mall får du ett körtidsfel:

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

Felet är självförklarande:Eftersom i18n-attributen nu konverteras till $localize-anropi den genererade koden måste vi ladda $localize-funktionen.Det skulle kunna göras som standard, men eftersom det bara krävs om du använder internationalisering skulle det inte riktigt vara meningsfullt.

Det är därför som ditt program, eller ett av dess beroenden, använder i18n-attribut i sina mallar, då måste du lägga till import '@angular/localize/init' i dina polyfills!

CLI erbjuder ett schema för att göra detta åt dig.Kör helt enkelt:

ng add @angular/localize

och CLI lägger till paketet till dina beroenden och den nödvändiga importen till dina polyfills.

När du sedan kör ditt program med ett enkelt ng serve visar $localize helt enkelt originalmeddelandet.

Hur översätter du nu de här meddelandena?

Processen är mycket lik den vi hade tidigare.Först kör du ng xi18n för att extrahera meddelandena i en messages.xlf-fil. sedan översätter du filen för dina lokala språk, till exempel messages.fr.xlfoch messages.es.xlf.

Därefter måste du konfigurera CLI, 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 kan du köra es eller fr med hjälp av es eller fr-konfigurationerna:

ng serve --configuration=fr

Och den app som serveras är nu på franska!

Du kan också bygga appen med en specifik lokal:

ng build --configuration=production,es

eller med alla lokaliteter på en gång:

ng build --prod --localize

Det här är ett stort framsteg jämfört med tidigare Angular-versioner. vi brukade vara tvungna att bygga samma applikation för varje lokalitet,eftersom översättningen var en del av sammanställningen.När Angular nu kompilerar applikationen genererar den $localize-anrop. när detta är gjort tar ett verktyg den kompilerade applikationen och ersätter alla $localize-anrop med rätt översättningar. detta är supersnabbt. du har då ett paket som inte innehåller några $localize-anrop och alla i18n-strängar har översatts.

Förresten var du tvungen att bygga applikationen en gång per lokal, och detta var en fullständig konstruktion. Så låt oss säga att det är en 30-talsbyggnad och att du ville ha 4 lokala språk, då var du ute efter 2 minuter.

Med det nya tillvägagångssättet görs kompileringen en gång och sedan genereras de olika i18n-versionerna på några få sekunder (de genereras till och med parallellt om det är möjligt).Så du går från 2 minuter till ~40 sekunder! 🌈

Du har då flera buntar, en per lokal, och du kan servera den lämpliga till dina användare beroende på deras preferenser som du brukade göra.

Denna strategi kallas compile-time inlining eftersom du inline översättningarna direkt, och sedan finns det inget kvar att göra vid körtid.

Nu ska vi prata om något som fortfarande är odokumenterat, som kan förändras i framtiden, men som ändå är intressant att veta:vi kan nu också översätta meddelanden i vår TypeScript-kod!

Den $localize-funktion som jag har pratat omkan användas direkt. Det är en märklig funktion som du kan använda för att tagga en mallsträng för lokalisering.

Men vi kanske ska börja med att förklara vad en taggad mallsträng är?

Mallsträngar och taggfunktioner

När du använder mallsträngar kan du definiera en taggfunktion,och tillämpa den på en mallsträng.Här lägger askQuestion till en förhörspunkt i slutet av strängen:

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

Så vad är skillnaden med en enkel funktion?Tag-funktionen tar i själva verket emot flera argument:

  • en array av strängens statiska delar
  • värdena som är resultatet av utvärderingen av uttrycken

Till exempel om vi har en mallsträng som innehåller uttryck:

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

då kommer tag-funktionen att ta emot de olika statiska och dynamiska delarna.Här har vi en taggfunktion för att skriva upp huvudpersonernas namn:

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-kod

$localize använder sig av denna mekanik för att låta oss skriva:

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

Bemärk att du inte behöver importera funktionen.Så länge du lägger till import '@angular/localize/init' en gång i programmet kommer $localize att läggas till i det globala objektet.

Du kan sedan översätta meddelandet på samma sätt som du skulle göra för en mall.Men just nu (v9.0.0) extraherar CLI inte dessa meddelanden med kommandot xi18n på samma sätt som det gör för mallar.

Om du serverar programmet och ingen översättning hittas visar $localize helt enkelt den ursprungliga strängen och loggar en varning i konsolen:

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

Så du måste manuellt lägga till den i din messages.fr.xlfmed det givna ID:

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

Mallen för min HomeComponent visar då Vous avez 10 utilisateurs!

Vad händer om du har något dynamiskt uttryck i din mallsträng?

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

Uttrycken får automatiskt namnen PH och PH_1 (PH är en platshållare).Sedan kan du använda dessa platshållare var du vill i översättningarna:

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

Men den bästa metoden är att själv ge uttrycket ett meningsfullt platsnamn, vilket du kan göra genom att använda ${expression}:placeholder:-syntaxen.

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

Därefter kan du använda denna platshållare var du vill i översättningarna:

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

Anpassade ID:n

Bemärk att om du har översättningar med anpassade ID:n så används de av $localize (som det var fallet tidigare):

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

Då ser din översättning ut som:

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

vilket naturligtvis är trevligare att använda.

Hur är det med översättningar i kod?$localize förstår också en syntax som gör det möjligt att ange ett ID:

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

Syntaxen för det anpassade ID:t är densamma som i mallar,och ID:t omges av kolon för att separera det från innehållet i översättningen.

Som i syntaxen för mallen kan du också ange en beskrivning och en betydelse,för att hjälpa översättarna med lite sammanhang: :meaning|description@@id:message.

Till exempel:

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

Håll i åtanke att det här är en API på låg nivå och odokumenterad.Angular-teamet eller -gemenskapen kommer förmodligen att erbjuda funktioner på högre nivå med en bättre utvecklarupplevelse (jag hoppas det!).Locl från Olivier Combe,författaren till ngx-translateär förmodligen värt att hålla ett öga på 🧐.

Körtidsöversättningar

Som jag nämnde, om du använder CLI-kommandona ovan (ng serve --configuration=fr eller ng build --localize) kompileras applikationen och översätts sedan innan den når webbläsaren, så det finns inga $localize-anrop vid körning.

Men $localize har utformats för att erbjuda en annan möjlighet: körtidsöversättningar.Vad innebär det? Jo, vi skulle kunna leverera endast en applikation, som innehåller $localize-anrop, och innan applikationen startar skulle vi kunna ladda in de översättningar vi vill ha.Inga fler N builds och N bundles för N locales \o/

Och utan att dyka ner för mycket i detaljerna är detta redan möjligt med v9, genom att använda loadTranslations-funktionen som erbjuds av @angular/localize.Men detta måste göras innan programmet startar.

Du kan ladda dina översättningar 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 finns det inga lokala överväganden:du laddar bara din översättning som ett objekt,vars nycklar är strängarna som ska översättas och värdena, deras översättningar.

Nu om du kör en enkel ng serve visas titeln på franska!Och inget behov av ng xi18n, eller messages.fr.xlfeller specifik konfiguration för varje lokal i angular.json.På lång sikt, när detta kommer att stödjas och dokumenteras på rätt sätt, bör vi kunna läsa in JSON-filer vid körning, som de flesta i18n-bibliotek gör.Du kan till och med åstadkomma det i v9, det är bara lite manuellt arbete, men det går att göra.

Hur är det med att ändra locale i farten då?Kan vi ladda in en annan uppsättning översättningar när programmet startas?Tja, nej. Det nuvarande sättet som $localize-anrop genereras på gör det omöjligt att ändra dem efteråt: du måste starta om programmet.Men om du inte har något emot att uppdatera webbläsaren är det möjligt.Jag testade en enkel strategi som fungerar:

  • en användare väljer ett nytt språk (till exempel spanska).
  • vi lagrar språket i webbläsaren (till exempel i localStorage)
  • vi laddar om sidan, vilket startar om programmet
  • i polyfills.ts börjar vi med att läsa det lagrade språket
  • vi laddar in den rätta uppsättningen översättningar för spanska med loadTranslations.

Detta kommer naturligtvis att bli smidigare i framtiden, antingen i en framtida version av Angular, eller via ett bibliotek från ekosystemet.Hur som helst, vi närmar oss att bara leverera en version av vår applikation, och bara läsa in översättningarna vid körning \o/

Alla våra material (e-bok, online-utbildning och utbildning) är uppdaterade med dessa ändringar om du vill lära dig mer!

Leave a Reply