Internationalization with @angular/localize

Er is grote vooruitgang geboekt op het i18n front! Een nieuw pakket genaamd @angular/localize is geïntroduceerd in Angular 9.0.

Het wordt onder de motorkap gebruikt om ons dezelfde mogelijkheden te geven die we eerder hadden:vertalingen in templates tijdens het compileren.

Maar het laat ons hopen op meer in de toekomst, met ongedocumenteerde functies die al beschikbaar zijn, zoals vertalingen in code, of runtime vertalingen in plaats van alleen compilatie vertalingen 😎.

Laten we beginnen met te zien wat we kunnen doen met de hulp van @angular/localize en de CLI in v9.

i18n in templates

Het nieuwe @angular/localize pakket biedt een functie genaamd $localize.

De bestaande i18n-ondersteuning in Angular maakt nu gebruik van $localize, wat betekent dat sjablonen zoals:

<h1 i18n>Hello</h1>

worden gecompileerd naar $localize-aanroepen.

Als u ng serve met zo’n sjabloon uitvoert, zult u een runtime error tegenkomen:

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

De fout spreekt voor zich:omdat de i18n attributen nu zijn geconverteerd naar $localize aanroepen in de gegenereerde code, moeten we de $localize functie laden.Dit zou standaard kunnen worden gedaan, maar omdat het alleen nodig is als je internationalisatie gebruikt, zou het niet echt zinvol zijn.

Daarom, als je applicatie, of een van zijn afhankelijkheden, i18n attributen gebruikt in zijn templates, dan zul je import '@angular/localize/init' moeten toevoegen aan je polyfills!

De CLI biedt een schema om dit voor je te doen.Je hoeft alleen maar te draaien:

ng add @angular/localize

en de CLI voegt het pakket toe aan je afhankelijkheden en de benodigde import aan je polyfills.

Als je dan je applicatie draait met een simpele ng serve, toont $localize gewoon het originele bericht.

Nu hoe vertaal je deze berichten?

Het proces lijkt erg op wat we eerder hadden.Eerst laat je ng xi18n de berichten uitpakken in een messages.xlf bestand. Dan vertaal je het bestand voor je locales, bijvoorbeeld messages.fr.xlf en messages.es.xlf.

Dan moet je de CLI configureren, in 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, de es of fr configuraties maken het mogelijk om te draaien:

ng serve --configuration=fr

En de geserveerde app is nu in het Frans!

U kunt de app ook bouwen met een specifieke locale:

ng build --configuration=production,es

of met alle locales tegelijk:

ng build --prod --localize

Dit is een grote vooruitgang ten opzichte van eerdere Angular versies.Vroeger moesten we voor elke locale dezelfde applicatie bouwen, omdat de vertaling onderdeel uitmaakte van de compilatie.Nu, wanneer Angular de applicatie compileert, genereert het $localize aanroepen.Dan, wanneer dit is gedaan, een tool neemt de gecompileerde applicatie en vervangt alle $localize aanroepen met de juiste vertalingen.Dit is supersnel.Je hebt dan een bundel die geen aanroepen naar $localize bevat en alle i18n strings zijn vertaald.

Tot nu toe, moest je je applicatie een keer per locale bouwen, en dit was een volledige build. Dus laten we zeggen dat het een 30s bouwen, en je wilde 4 locales, dan was je in voor 2 minuten.

Met de nieuwe aanpak, de compilatie wordt een keer gedaan, en dan de verschillende i18n versies worden gegenereerd in een paar seconden (het is zelfs parallel gegenereerd indien mogelijk).Dus je gaat van 2 minuten naar ~40 seconden!

Je hebt dan verschillende bundels, een per locale, en je kunt de juiste aan je gebruikers serveren afhankelijk van hun voorkeur zoals je gewend bent.

Deze strategie heet compile-time inlining omdat je de vertalingen direct inline maakt, en dan is er niets meer te doen tijdens runtime.

Nog iets dat nog niet gedocumenteerd is, dat in de toekomst kan veranderen, maar toch interessant is om te weten: we kunnen nu ook berichten vertalen in onze TypeScript-code!

De $localize-functie waar ik het over had, kan direct worden gebruikt. Het is een eigenaardige functie die je kunt gebruiken om een template string te taggen voor lokalisatie.

Maar misschien moeten we beginnen met uit te leggen wat een tagged template string is?

Template strings en tag functies

Wanneer u template strings gebruikt, kunt u een tag functie definiëren, en deze toepassen op een template string.Hier voegt askQuestion een ondervragingspunt toe aan het einde van de string:

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

Wat is nu het verschil met een eenvoudige functie?De tag functie ontvangt in feite verschillende argumenten:

  • een array van de statische delen van de string
  • de waarden die het resultaat zijn van de evaluatie van de expressies

Bijv. als we een template string hebben die expressies bevat:

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

dan zal de tag functie de verschillende statische en dynamische delen ontvangen.Hier hebben we een tag functie om de namen van de hoofdrolspelers een hoofdletter te geven:

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 met $localize in TypeScript code

$localize gebruikt dit mechanisme om ons te laten schrijven:

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

Merk op dat je de functie niet hoeft te importeren.Zolang je import '@angular/localize/init' een keer toevoegt in je applicatie, zal $localize worden toegevoegd aan het globale object.

Je kunt dan het bericht vertalen op dezelfde manier als je zou doen voor een sjabloon.Maar, op dit moment (v9.0.0), haalt de CLI deze berichten niet met het xi18n commando zoals het doet voor sjablonen.

Als je de applicatie serveert en er is geen vertaling gevonden, $localize geeft gewoon de originele string weer, en logt een waarschuwing in de console:

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

Dus moet je het handmatig toevoegen aan je messages.fr.xlf met de gegeven ID als je het wilt proberen:

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

Het sjabloon van mijn HomeComponent geeft dan Vous avez 10 utilisateurs weer!

Wat gebeurt er als je een of andere dynamische expressie in je sjabloonstring hebt?

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

De expressies krijgen dan automatisch de namen PH en PH_1 (PH is voor placeholder).Vervolgens kunt u deze plaatshouders gebruiken waar u maar wilt in de vertalingen:

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

Maar het beste is om zelf een betekenisvolle plaatsnaam aan de expressie te geven, en dat kunt u doen door de ${expression}:placeholder: syntaxis te gebruiken.

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

Dan kunt u deze plaatsaanduiding gebruiken waar u maar wilt in de vertalingen:

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

Aangepaste ID’s

Merk op dat als u vertalingen hebt met aangepaste ID’s, deze worden gebruikt door $localize (zoals voorheen het geval was):

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

Dan ziet uw vertaling er uit als:

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

Wat natuurlijk prettiger is om te gebruiken.

Hoe zit het met vertalingen in code? $localize begrijpt ook een syntaxis die het mogelijk maakt om een ID op te geven:

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

De syntaxis voor de aangepaste ID is hetzelfde als in de sjablonen, en de ID wordt omgeven door dubbele punten om het te scheiden van de inhoud van de vertaling.

Zoals in de sjabloonsyntaxis kunt u ook een beschrijving en een betekenis opgeven, om vertalers te helpen met een beetje context: :meaning|description@@id:message.

Bijvoorbeeld:

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

Bedenk dat dit een low level, ongedocumenteerde API is.Het Angular team of de gemeenschap zal waarschijnlijk functies van een hoger niveau bieden met een betere ontwikkelaarservaring (nou ja, dat hoop ik!).Locl van Olivier Combe, de auteur van ngx-translateis waarschijnlijk de moeite waard om in de gaten te houden 🧐.

Runtime vertalingen

Zoals ik al zei, als je de CLI commando’s hierboven gebruikt (ng serve --configuration=fr of ng build --localize) dan wordt de applicatie gecompileerd en dan vertaald voordat het de browser raakt, dus er zijn geen $localize aanroepen in runtime.

Maar $localize is ontworpen om een andere mogelijkheid te bieden: runtime vertalingen. Wat betekent dat? Nou, we zouden in staat zijn om slechts een applicatie te verschepen, die $localize aanroepen bevat, en voordat de applicatie start, kunnen we de vertalingen laden die we willen. Niet meer N builds en N bundels voor N locales. Zonder al te veel in de details te duiken, dit is al mogelijk met v9, door gebruik te maken van de loadTranslations functie die @angular/localize biedt.Maar dit moet gebeuren voordat de applicatie start.

U kunt uw vertalingen laden in polyfills.ts met:

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

Zoals u ziet wordt er geen rekening gehouden met de locale: u laadt gewoon uw vertaling als een object, waarvan de sleutels de te vertalen strings zijn, en de waarden, hun vertalingen.

Als je nu een eenvoudige ng serve uitvoert, wordt de titel in het Frans weergegeven! En geen noodzaak meer voor ng xi18n, of messages.fr.xlf of specifieke configuratie voor elke locale in angular.json. Op de lange termijn, wanneer dit goed zal worden ondersteund en gedocumenteerd, zouden we in staat moeten zijn om JSON bestanden te laden tijdens runtime, zoals de meeste i18n bibliotheken doen.Je zou het zelfs in v9 kunnen bereiken, het is alleen een beetje handwerk, maar het is te doen.

Hoe zit het dan met het veranderen van de locale on the fly? Kunnen we een andere set vertalingen laden als de applicatie wordt gestart? Nou, nee. De huidige manier waarop $localize aanroepen worden gegenereerd maakt het onmogelijk om ze achteraf te veranderen: je moet de applicatie opnieuw starten. Maar als je het niet erg vindt om je browser te verversen, is het mogelijk. Ik heb een eenvoudige strategie getest die werkt:

  • een gebruiker selecteert een nieuwe taal (bijvoorbeeld Spaans).
  • we slaan de taal op in de browser (bijvoorbeeld in de localStorage)
  • we herladen de pagina, waardoor de applicatie opnieuw wordt gestart
  • in polyfills.ts, beginnen we met het lezen van de opgeslagen taal
  • we laden de juiste set vertalingen voor Spaans met loadTranslations.

Natuurlijk zal dit in de toekomst soepeler gaan, hetzij in een toekomstige versie van Angular, of via een bibliotheek uit het eco-systeem.Hoe dan ook, we komen steeds dichter bij het verschepen van slechts een versie van onze applicatie, en alleen de vertalingen te laden tijdens runtime

Al onze materialen (ebook, online training en opleiding) zijn up-to-date met deze veranderingen als je meer wilt leren!

Leave a Reply