Internationalization with @angular/localize
Na froncie i18n dokonał się wielki postęp! Nowy pakiet o nazwie @angular/localize
został wprowadzony w Angular 9.0.
Jest on używany pod maską, aby dać nam te same funkcje, które mieliśmy wcześniej:tłumaczenia w szablonach w czasie kompilacji.
Ale pozwala nam mieć nadzieję na więcej w przyszłości, z nieudokumentowanymi funkcjami już dostępnymi, jak tłumaczenia w kodzie, lub tłumaczenia runtime zamiast tłumaczeń tylko w kompilacji 😎.
Zacznijmy od zobaczenia, co możemy zrobić z pomocą @angular/localize
i CLI w v9.
i18n w szablonach
Nowy pakiet @angular/localize
oferuje funkcję o nazwie $localize
.
Istniejące wsparcie i18n w Angular wykorzystuje teraz $localize
, co oznacza, że szablony takie jak:
<h1 i18n>Hello</h1>
będą kompilowane do wywołań $localize
.
Jeśli uruchomisz ng serve
z takim szablonem, napotkasz błąd runtime:
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.)
Błąd jest oczywisty: ponieważ atrybuty i18n
są teraz konwertowane na wywołania $localize
w wygenerowanym kodzie, musimy załadować funkcję $localize
.Można to zrobić domyślnie, ale ponieważ jest to wymagane tylko wtedy, gdy używasz internacjonalizacji, nie miałoby to sensu.
Dlatego jeśli twoja aplikacja, lub jedna z jej zależności, używa atrybutów i18n
w swoich szablonach, to musisz dodać import '@angular/localize/init'
do swoich polyfills!
CLI oferuje schemat, aby zrobić to za ciebie.Po prostu uruchom:
ng add @angular/localize
i CLI doda pakiet do twoich zależności i niezbędny import do twoich polyfills.
Kiedy uruchomisz swoją aplikację z prostym ng serve
,$localize
po prostu wyświetli oryginalny komunikat.
Jak przetłumaczyć te komunikaty?
Proces jest bardzo podobny do tego, który mieliśmy poprzednio.Najpierw uruchamiasz ng xi18n
, aby wyodrębnić wiadomości w pliku messages.xlf
.Następnie tłumaczysz plik dla swoich języków lokalnych, na przykład messages.fr.xlf
i messages.es.xlf
.
Następnie należy skonfigurować CLI, w 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" } } } // ...}
Teraz konfiguracje es
lub fr
pozwalają uruchomić:
ng serve --configuration=fr
A serwowana aplikacja jest teraz w języku francuskim!
Możesz także zbudować aplikację z określonym locale:
ng build --configuration=production,es
lub ze wszystkimi locale na raz:
ng build --prod --localize
To duży postęp w porównaniu do poprzednich wersji Angulara.Kiedyś musieliśmy budować tę samą aplikację dla każdego locale, ponieważ tłumaczenie było częścią kompilacji.Teraz, gdy Angular kompiluje aplikację, generuje wywołania $localize
.Następnie, gdy to się stanie, narzędzie pobiera skompilowaną aplikację i zastępuje wszystkie wywołania $localize
odpowiednimi tłumaczeniami.Jest to super szybkie.Następnie masz pakiet nie zawierający wywołań do $localize
i wszystkie ciągi i18n zostały przetłumaczone.
Do tej pory musiałeś zbudować swoją aplikację raz na locale, a to był pełny build. Powiedzmy, że jest to 30s kompilacji, a ty chciałeś mieć 4 wersje lokalne, więc miałeś 2 minuty.
Z nowym podejściem, kompilacja jest wykonywana raz, a następnie różne wersje i18n są generowane w ciągu kilku sekund (jest to nawet generowane równolegle, jeśli to możliwe).Więc idziesz z 2 minut do ~40 sekund! 🌈
Masz wtedy kilka pakietów, po jednym na locale, i możesz zaserwować odpowiedni dla swoich użytkowników w zależności od ich preferencji, tak jak to robiłeś.
Ta strategia nazywa się compile-time inliningas, ponieważ inline’ujesz tłumaczenia bezpośrednio, a potem nie ma już nic do zrobienia w runtime.
Teraz porozmawiajmy o czymś jeszcze nieudokumentowanym, co może się zmienić w przyszłości, ale nadal interesującym: możemy teraz również tłumaczyć wiadomości w naszym kodzie TypeScript!
Funkcja $localize
, o której mówiłem, może być używana bezpośrednio. Jest to osobliwa funkcja, której można użyć do oznaczenia łańcucha szablonu w celu lokalizacji.
Ale może powinniśmy zacząć od wyjaśnienia, czym jest oznaczony łańcuch szablonu?
Szablonowe łańcuchy i funkcje znaczników
Przy użyciu łańcuchów szablonów można zdefiniować funkcję znacznika i zastosować ją do szablonowego łańcucha.Tutaj askQuestion
dodaje punkt zapytania na końcu łańcucha:
const askQuestion = strings => strings + '?';const template = askQuestion`Is anyone here`;
Czym więc różni się od zwykłej funkcji?Funkcja tagu w rzeczywistości otrzymuje kilka argumentów:
- tablica statycznych części łańcucha
- wartości wynikające z oceny wyrażeń
Na przykład jeśli mamy szablon łańcucha zawierający wyrażenia:
const person1 = 'Cedric';const person2 = 'Agnes';const template = `Hello ${person1}! Where is ${person2}?`;
to funkcja tagu otrzyma różne statyczne i dynamiczne części.Tutaj mamy funkcję znacznika, która ma wstawiać wielkie litery w imionach bohaterów:
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 z $localize w kodzie TypeScript
$localize
wykorzystuje tę mechanikę, aby pozwolić nam napisać:
@Component({ template: '{{ title }}'})export class HomeComponent { title = $localize`You have 10 users`;}
Zauważ, że nie musisz importować funkcji.Tak długo, jak dodajesz import '@angular/localize/init'
raz w swojej aplikacji, $localize
zostanie dodane do obiektu globalnego.
Możesz wtedy przetłumaczyć wiadomość w taki sam sposób, jak w przypadku szablonu.Ale w tej chwili (v9.0.0), CLI nie wyodrębnia tych wiadomości za pomocą polecenia xi18n
, jak to robi dla szablonów.
Jeśli obsługujesz aplikację i nie znaleziono tłumaczenia, $localize
po prostu wyświetla oryginalny łańcuch i rejestruje ostrzeżenie w konsoli:
No translation found for "6480943972743237078" ("You have 10 users").
Musisz więc ręcznie dodać go do swojego messages.fr.xlf
z podanym IDif chcesz spróbować:
<trans-unit> <source>You have 10 users</source> <target>Vous avez 10 utilisateurs</target></trans-unit>
Szablon mojego HomeComponent
wyświetla wtedy Vous avez 10 utilisateurs
!
Co się stanie, jeśli masz jakieś dynamiczne wyrażenie w swoim łańcuchu szablonu?
title = $localize`Hi ${this.name}! You have ${this.users.length} users.`;
Wyrażenia te zostaną automatycznie nazwane PH
i PH_1
(PH
jest dla placeholder).Następnie możesz użyć tych placeholderów gdziekolwiek chcesz w tłumaczeniach:
<trans-unit> <source>Hi <x/>! You have <x/> users.</source> <target>Bonjour <x/> ! Vous avez <x/> utilisateurs.</target></trans-unit>
Ale najlepszą praktyką jest nadanie znaczącej nazwy placeholderowi samemu wyrażeniu, a możesz to zrobić używając składni ${expression}:placeholder:
.
title = $localize`Hi ${this.name}:name:! You have ${this.users.length}:userCount: users.`;
Wtedy możesz użyć tego placeholdera gdziekolwiek chcesz w tłumaczeniach:
<trans-unit> <source>Hi <x/>! You have <x/> users.</source> <target>Bonjour <x/> ! Vous avez <x/> utilisateurs.</target></trans-unit>
Custom IDs
Zauważ, że jeśli masz tłumaczenia z niestandardowymi identyfikatorami, są one używane przez $localize
(tak jak poprzednio):
<h1 i18n="@@home.greetings">Hello</h1>
Wtedy twoje tłumaczenie wygląda jak:
<trans-unit> <source>Hello</source> <target>Bonjour</target></trans-unit>
co jest oczywiście ładniejsze w użyciu.
A co z tłumaczeniami w kodzie? $localize
również rozumie składnię pozwalającą na określenie ID:
title = $localize`:@@home.users:You have 10 users`;
Składnia dla niestandardowego ID jest taka sama jak w szablonach, a ID jest otoczone dwukropkami, aby oddzielić je od treści tłumaczenia.
Tak jak w przypadku składni szablonów, możesz również określić opis i znaczenie, aby pomóc tłumaczom z odrobiną kontekstu: :meaning|description@@id:message
.
Na przykład:
title = $localize`:greeting message with the number of users currently logged in@@home.users:You have 10 users`;
Miej na uwadze, że jest to niskopoziomowe, nieudokumentowane API.Zespół Angulara lub społeczność prawdopodobnie zaoferuje funkcje wyższego poziomu z lepszym doświadczeniem deweloperskim (cóż, mam taką nadzieję!).Locl od Oliviera Combe’a, autora ngx-translateis prawdopodobnie warto mieć oko 🧐.
Tłumaczenia runtime
Jak już wspominałem, jeśli używasz powyższych poleceń CLI (ng serve --configuration=fr
lub ng build --localize
), to aplikacja jest kompilowana i tłumaczona przed trafieniem do przeglądarki, więc nie ma żadnych wywołań $localize
w runtime.
Ale $localize
zostało zaprojektowane, aby zaoferować inną możliwość: tłumaczenia runtime.Co to oznacza? Cóż, bylibyśmy w stanie wysłać tylko jedną aplikację, zawierającą wywołania $localize
, a przed uruchomieniem aplikacji moglibyśmy załadować tłumaczenia, które chcemy. Koniec z N buildami i N bundlami dla N lokalizacji.
Nie zagłębiając się zbytnio w szczegóły, jest to już możliwe w v9, poprzez użycie funkcji loadTranslations
oferowanej przez @angular/localize
.Ale to musi być zrobione przed uruchomieniem aplikacji.
Twoje tłumaczenia możesz załadować do polyfills.ts
za pomocą:
import '@angular/localize/init';import { loadTranslations } from '@angular/localize';loadTranslations({ '1815172606781074132': 'Bonjour {$name}\xa0! Vous avez {$userCount} utilisateurs.'});
Jak widzisz, nie ma żadnego uwzględniania locale:po prostu ładujesz swoje tłumaczenie jako obiekt, którego kluczami są łańcuchy do przetłumaczenia, a wartościami ich tłumaczenia.
Teraz, jeśli uruchomisz proste ng serve
, tytuł zostanie wyświetlony w języku francuskim! I nie ma już potrzeby używania ng xi18n
, lub messages.fr.xlf
lub specyficznej konfiguracji dla każdego locale w angular.json
.W dłuższej perspektywie, kiedy będzie to odpowiednio wspierane i udokumentowane, powinniśmy być w stanie załadować pliki JSON w czasie wykonywania, tak jak robi to większość bibliotek i18n.Można to osiągnąć nawet w v9, jest to tylko trochę ręcznej pracy, ale jest to do zrobienia.
Co w takim razie ze zmianą locale w locie? Czy możemy załadować inny zestaw tłumaczeń, gdy aplikacja jest uruchamiana? Cóż, nie. Obecny sposób generowania wywołań $localize
uniemożliwia ich zmianę po uruchomieniu: trzeba ponownie uruchomić aplikację.Ale jeśli nie masz nic przeciwko odświeżaniu przeglądarki, jest to możliwe.Przetestowałem prostą strategię, która działa:
- użytkownik wybiera nowy język (na przykład hiszpański).
- zapisujemy język w przeglądarce (na przykład w localStorage)
- przeładowujemy stronę, co powoduje ponowne uruchomienie aplikacji
- w
polyfills.ts
zaczynamy od odczytania zapisanego języka - załadujemy odpowiedni zestaw tłumaczeń dla hiszpańskiego za pomocą
loadTranslations
.
Oczywiście, w przyszłości będzie to płynniejsze, albo w przyszłej wersji Angulara, albo poprzez bibliotekę z ekosystemu.Tak czy inaczej, jesteśmy coraz bliżej tego, aby wysyłać tylko jedną wersję naszej aplikacji, i po prostu ładować tłumaczenia w runtime \o/
Wszystkie nasze materiały (ebook, szkolenia online i treningi) są aktualne z tymi zmianami, jeśli chcesz dowiedzieć się więcej!
Leave a Reply