Internaționalizare cu @angular/localize

Au fost făcute progrese mari pe frontul i18n!Un nou pachet numit @angular/localize a fost introdus în Angular 9.0.

Este folosit sub capotă pentru a ne oferi aceleași caracteristici pe care le aveam anterior:traduceri în șabloane în momentul compilării.

Dar ne lasă să sperăm la mai mult în viitor,cu caracteristici nedocumentate deja disponibile,cum ar fi traduceri în cod,sau traduceri în timp de execuție în loc de traduceri doar la compilare 😎.

Să începem prin a vedea ce putem face cu ajutorul @angular/localizeși al CLI în v9.

i18n în șabloane

Noul pachet @angular/localize oferă o funcție numită $localize.

Suportul i18n existent în Angular folosește acum $localize,ceea ce înseamnă că șabloane precum:

<h1 i18n>Hello</h1>

va fi compilat în apeluri $localize.

Dacă rulați ng serve cu un astfel de șablon,veți da peste o eroare de execuție:

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

Eroarea este de la sine explicată:deoarece atributele i18n sunt acum convertite în apeluri $localizeîn codul generat,trebuie să încărcăm funcția $localize.Acest lucru ar putea fi făcut în mod implicit,dar cum este necesar doar dacă folosiți internaționalizarea,nu ar avea sens.

De aceea, dacă aplicația dumneavoastră, sau una dintre dependențele sale, utilizează atribute i18nîn șabloanele sale, atunci va trebui să adăugați import '@angular/localize/init' la polyfill-urile dumneavoastră!

CLI oferă o schemă pentru a face acest lucru pentru dumneavoastră.Pur și simplu rulați:

ng add @angular/localize

și CLI adaugă pachetul la dependențele dvs. și importul necesar la polyfills.

Apoi, când rulați aplicația dvs. cu un simplu ng serve,$localize afișează pur și simplu mesajul original.

Acum cum traduceți aceste mesaje?

Procesul este foarte asemănător cu cel pe care l-am avut anterior.Mai întâi rulați ng xi18n pentru a extrage mesajele într-un fișier messages.xlf. apoi traduceți fișierul pentru localitățile dumneavoastră, de exemplu messages.fr.xlfși messages.es.xlf.

Apoi trebuie să configurați CLI, în 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" } } } // ...}

Acum, configurațiile es sau fr permit rularea:

ng serve --configuration=fr

Și aplicația servită este acum în franceză!

De asemenea, puteți construi aplicația cu o anumită localizare:

ng build --configuration=production,es

sau cu toate localizările deodată:

ng build --prod --localize

Este un mare progres în comparație cu versiunile Angular anterioare. înainte trebuia să construim aceeași aplicație pentru fiecare localizare,deoarece traducerea făcea parte din compilare.Acum, când Angular compilează aplicația, generează apeluri $localize.Apoi, când acest lucru este făcut, un instrument ia aplicația compilatăși înlocuiește toate apelurile $localize cu traducerile corespunzătoare.Acest lucru este foarte rapid.Aveți apoi un pachet care nu mai conține apeluri la $localizeși toate șirurile i18n au fost traduse.

Până acum, trebuia să vă construiți aplicația o dată pentru fiecare localitate,iar aceasta era o construcție completă. Deci, să spunem că este o compilare de 30 de secunde,și ați vrut 4 locale, atunci aveați nevoie de 2 minute.

Cu noua abordare, compilarea se face o singură dată, iar apoi diferitele versiuni i18n sunt generate în câteva secunde (este chiar generată în paralel, dacă este posibil).Deci, treceți de la 2 minute la ~40 de secunde! 🌈

Apoi aveți mai multe pachete, câte unul pentru fiecare localitate,și puteți să le serviți utilizatorilor dvs. pe cel potrivit în funcție de preferințele lor,așa cum făceați până acum.

Această strategie se numește inlining la compilare,deoarece se inlinează traducerile direct,și apoi nu mai este nimic de făcut la execuție.

Acum haideți să vorbim despre ceva încă nedocumentat,care se poate schimba în viitor,dar totuși interesant de știut:acum putem traduce mesaje și în codul nostru TypeScript!

Funcția $localize despre care am vorbit poate fi folosită direct. Este o funcție particulară pe care o puteți folosi pentru a eticheta un șir de șablon pentru localizare.

Dar poate ar trebui să începem prin a explica ce este un șir de șablon etichetat?

Șiruri de șabloane și funcții de etichetare

Când folosiți șiruri de șabloane, puteți defini o funcție de etichetare,și să o aplicați unui șir de șabloane.Aici askQuestion adaugă un punct de interogare la sfârșitul șirului:

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

Și care este diferența față de o funcție simplă?Funcția de etichetare primește, de fapt, mai multe argumente:

  • un array al părților statice ale șirului
  • valorile rezultate din evaluarea expresiilor

De exemplu, dacă avem un șir șablon care conține expresii:

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

atunci funcția de etichetare va primi diferitele părți statice și dinamice.Aici avem o funcție tag pentru a scrie cu majuscule numele protagoniștilor:

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 cu $localize în codul TypeScript

$localize folosește această mecanică pentru a ne permite să scriem:

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

Rețineți că nu trebuie să importați funcția.Atâta timp cât adăugați import '@angular/localize/init' o dată în aplicația dvs.,$localize va fi adăugat la obiectul global.

Puteți apoi traduce mesajul în același mod în care ați face-o pentru un șablon.Dar, în acest moment (v9.0.0), CLI nu extrage aceste mesajecu comanda xi18n așa cum face pentru șabloane.

Dacă serviți aplicația și nu se găsește nicio traducere,$localize afișează pur și simplu șirul original,și înregistrează un avertisment în consolă:

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

Așa că trebuie să-l adăugați manual la messages.fr.xlfcu ID-ul datdacă doriți să încercați:

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

Șablonul meu HomeComponent afișează apoi Vous avez 10 utilisateurs!

Ce se întâmplă dacă aveți o expresie dinamică în șirul șablonului dumneavoastră?

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

Expresiile se vor numi automat PH și PH_1 (PH este pentru spațiu rezervat).Apoi, puteți folosi aceste placeholder-uri oriunde doriți în traduceri:

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

Dar cea mai bună practică este să dați voi înșivă un nume de placeholder semnificativ expresiei,și puteți face acest lucru folosind sintaxa ${expression}:placeholder:.

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

Apoi puteți folosi acest placeholder oriunde doriți în traduceri:

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

Custom IDs

Rețineți că dacă aveți traduceri cu ID-uri personalizate,acestea sunt folosite de $localize (așa cum era cazul anterior):

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

Atunci traducerea dvs. arată ca:

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

care este evident mai frumos de utilizat.

Cum rămâne cu traducerile în cod?$localize înțelege și o sintaxă care permite specificarea unui ID:

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

Sintaxa pentru ID-ul personalizat este aceeași ca și în șabloane,iar ID-ul este înconjurat de două puncte de suspensie pentru a-l separa de conținutul traducerii.

Ca și în cazul sintaxei șabloanelor, puteți specifica, de asemenea, o descriere și o semnificație,pentru a ajuta traducătorii cu un pic de context: :meaning|description@@id:message.

De exemplu:

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

Rețineți că acesta este un API de nivel scăzut, nedocumentat.Echipa sau comunitatea Angular va oferi probabil funcții de nivel mai înaltcu o experiență mai bună pentru dezvoltatori (ei bine, așa sper!).Locl de la Olivier Combe, autorul ngx-translateeste probabil demn de a fi urmărit 🧐.

Traduceri în timp de execuție

După cum menționam,dacă folosiți comenzile CLI de mai sus (ng serve --configuration=fr sau ng build --localize), atunci aplicația este compilată și apoi tradusăînainte de a ajunge în browser, deci nu există apeluri $localize în timp de execuție.

Dar $localize a fost conceput pentru a oferi o altă posibilitate:traduceri în timp de execuție.Ce înseamnă asta? Ei bine, am putea livra o singură aplicație, care să conțină apeluri $localize, iar înainte ca aplicația să pornească, am putea încărca traducerile pe care le dorim.Gata cu N compilări și N pachete pentru N localități \o/

Fără a intra prea mult în detalii, acest lucru este deja posibil cu v9, prin utilizarea funcției loadTranslationsoferită de @angular/localize.Dar acest lucru trebuie făcut înainte ca aplicația să pornească.

Vă puteți încărca traducerile în polyfills.ts cu:

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

După cum vedeți nu se ține cont de locale:doar încărcați traducerea ca un obiect,ale cărui chei sunt șirurile de tradus,iar valorile, traducerile acestora.

Acum, dacă rulați un simplu ng serve,titlul este afișat în limba franceză!Și nu mai este nevoie de ng xi18n, sau messages.fr.xlfsau de o configurație specifică pentru fiecare locale în angular.json.Pe termen lung, când acest lucru va fi susținut și documentat corespunzător,ar trebui să putem încărca fișiere JSON în timpul execuției,așa cum fac majoritatea bibliotecilor i18n.Ați putea chiar să realizați acest lucru în v9, este doar un pic de muncă manuală, dar este realizabil.

Cum rămâne cu schimbarea locală din mers atunci?Putem încărca un alt set de traduceri atunci când aplicația este pornită?Ei bine, nu. Modul actual în care sunt generate apelurile $localize face imposibilă schimbarea lor după: trebuie să reporniți aplicația.Dar dacă nu vă deranjează să reîmprospătați browserul, este posibil.Am testat o strategie simplă care funcționează:

  • un utilizator selectează o nouă limbă (de exemplu, spaniolă).
  • memorizăm limba în browser (de exemplu în localStorage)
  • reîncărcăm pagina, ceea ce repornește aplicația
  • în polyfills.ts, începem prin a citi limba memorată
  • încărcăm setul corespunzător de traduceri pentru spaniolă cu loadTranslations.

Desigur, acest lucru va fi mai ușor în viitor, fie într-o versiune viitoare de Angular,fie prin intermediul unei biblioteci din ecosistem.Oricum, suntem tot mai aproape de a livra o singură versiune a aplicației noastre, și de a încărca doar traducerile în timpul execuției \o/

Toate materialele noastre (ebook, instruire online și training) sunt actualizate cu aceste modificări dacă doriți să aflați mai multe!

Leave a Reply