Internationalisierung mit @angular/localize

Ein großer Fortschritt wurde an der i18n-Front gemacht!

Ein neues Paket namens @angular/localize wurde in Angular 9.0 eingeführt.

Es wird unter der Haube verwendet, um uns die gleichen Funktionen zu geben, die wir vorher hatten:Übersetzungen in Templates zur Kompilierungszeit.

Aber es lässt uns auf mehr in der Zukunft hoffen, mit undokumentierten Features, die bereits verfügbar sind, wie Übersetzungen im Code, oder Übersetzungen zur Laufzeit anstatt nur Übersetzungen bei der Kompilierung 😎.

Lassen Sie uns beginnen, indem wir sehen, was wir mit Hilfe von @angular/localizeund dem CLI in v9 tun können.

i18n in Templates

Das neue @angular/localize Paket bietet eine Funktion namens $localize.

Die bestehende i18n-Unterstützung in Angular nutzt nun $localize, was bedeutet, dass Templates wie:

<h1 i18n>Hello</h1>

zu $localize-Aufrufen kompiliert werden.

Wenn man ng serve mit einer solchen Vorlage ausführt, wird man auf einen Laufzeitfehler stoßen:

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

Der Fehler ist selbsterklärend: da die i18n-Attribute nun in $localize-Aufrufe im generierten Code umgewandelt werden, müssen wir die $localize-Funktion laden.Es könnte standardmäßig gemacht werden, aber da es nur erforderlich ist, wenn Sie Internationalisierung verwenden, würde es nicht wirklich Sinn machen.

Wenn Ihre Anwendung oder eine ihrer Abhängigkeiten i18n-Attribute in ihren Vorlagen verwendet, müssen Sie import '@angular/localize/init' zu Ihren Polyfills hinzufügen!

Das CLI bietet ein Schema, um dies für Sie zu tun.Führen Sie einfach aus:

ng add @angular/localize

und die CLI fügt das Paket zu Ihren Abhängigkeiten und den notwendigen Import zu Ihren Polyfills hinzu.

Wenn Sie dann Ihre Anwendung mit einem einfachen ng serve ausführen, zeigt $localize einfach die ursprüngliche Meldung an.

Wie übersetzen Sie nun diese Meldungen?

Der Prozess ist dem, den wir zuvor hatten, sehr ähnlich.Zuerst führen Sie ng xi18n aus, um die Nachrichten in eine messages.xlf-Datei zu extrahieren, dann übersetzen Sie die Datei für Ihre Sprachumgebungen, zum Beispiel messages.fr.xlfund messages.es.xlf.

Dann müssen Sie die CLI konfigurieren, 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" } } } // ...}

Jetzt können die es oder fr Konfigurationen ausgeführt werden:

ng serve --configuration=fr

Und die App wird jetzt auf Französisch bedient!

Sie können die App auch mit einem bestimmten Gebietsschema erstellen:

ng build --configuration=production,es

oder mit allen Gebietsschemata auf einmal:

ng build --prod --localize

Das ist ein großer Fortschritt im Vergleich zu früheren Angular-Versionen.Früher mussten wir die gleiche Anwendung für jedes Gebietsschema erstellen, da die Übersetzung Teil der Kompilierung war.Wenn Angular die Anwendung kompiliert, werden $localize-Aufrufe generiert. Wenn das erledigt ist, nimmt ein Tool die kompilierte Anwendung und ersetzt alle $localize-Aufrufe durch die richtigen Übersetzungen. Das geht superschnell. Dann hat man ein Bundle, das keine $localize-Aufrufe enthält, und alle i18n-Strings wurden übersetzt.

Bis jetzt musste man seine Anwendung einmal pro Gebietsschema kompilieren, und das war ein vollständiger Build. Sagen wir, es ist ein 30-Sekunden-Build, und Sie wollten 4 Sprachumgebungen, dann brauchten Sie 2 Minuten.

Mit dem neuen Ansatz wird die Kompilierung einmal durchgeführt, und dann werden die verschiedenen i18n-Versionen in ein paar Sekunden generiert (es wird sogar parallel generiert, wenn möglich).Also gehen Sie von 2 Minuten auf ~40 Sekunden! 🌈

Sie haben dann mehrere Pakete, eines pro Gebietsschema, und können Ihren Nutzern je nach Vorliebe das passende anbieten, so wie Sie es früher getan haben.

Diese Strategie wird Inlining zur Kompilierzeit genannt, da Sie die Übersetzungen direkt einbinden, und dann gibt es zur Laufzeit nichts mehr zu tun.

Lassen Sie uns nun über etwas sprechen, das noch undokumentiert ist, das sich vielleicht in Zukunft ändern wird, aber dennoch interessant zu wissen ist: Wir können jetzt auch Nachrichten in unserem TypeScript-Code übersetzen!

Die $localize-Funktion, über die ich gesprochen habe, kann direkt verwendet werden. Es ist eine besondere Funktion, mit der man einen Template-String für die Lokalisierung markieren kann.

Aber vielleicht sollten wir damit beginnen, zu erklären, was ein markierter Template-String ist?

Vorlagenzeichenketten und Tag-Funktionen

Bei der Verwendung von Vorlagenzeichenketten können Sie eine Tag-Funktion definieren und sie auf eine Vorlagenzeichenkette anwenden.askQuestion Fügt hier einen Abfragepunkt am Ende der Zeichenkette hinzu:

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

Was ist also der Unterschied zu einer einfachen Funktion?Die Tag-Funktion erhält in der Tat mehrere Argumente:

  • ein Array der statischen Teile der Zeichenkette
  • die Werte, die sich aus der Auswertung der Ausdrücke ergeben

Wenn wir zum Beispiel eine Vorlagenzeichenkette haben, die Ausdrücke enthält:

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

dann erhält die Tag-Funktion die verschiedenen statischen und dynamischen Teile.Hier haben wir eine Tag-Funktion, um die Namen der Protagonisten in Großbuchstaben zu schreiben:

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 mit $localize in TypeScript-Code

$localize nutzt diese Mechanik, um uns schreiben zu lassen:

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

Beachte, dass du die Funktion nicht importieren musst.Solange Sie import '@angular/localize/init' einmal in Ihre Anwendung einfügen, wird $localize zum globalen Objekt hinzugefügt.

Sie können die Nachricht dann auf die gleiche Weise übersetzen, wie Sie es für eine Vorlage tun würden. Aber im Moment (v9.0.0) extrahiert die CLI diese Nachrichten nicht mit dem Befehl xi18n, wie es für Vorlagen der Fall ist.

Wenn Sie die Anwendung ausliefern und keine Übersetzung gefunden wird, zeigt $localize einfach die ursprüngliche Zeichenfolge an und protokolliert eine Warnung in der Konsole:

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

So musst du es manuell zu deinem messages.fr.xlfmit der angegebenen ID hinzufügen, wenn du es versuchen willst:

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

Das Template meines HomeComponent zeigt dann Vous avez 10 utilisateurs!

Was passiert, wenn du einen dynamischen Ausdruck in deinem Template-String hast?

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

Die Ausdrücke werden automatisch mit PH und PH_1 benannt (PH steht für Platzhalter).Sie können diese Platzhalter dann an beliebiger Stelle in den Übersetzungen verwenden:

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

Aber am besten ist es, dem Ausdruck selbst einen aussagekräftigen Platzhalternamen zu geben, und das können Sie mit der ${expression}:placeholder:-Syntax tun.

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

Dann können Sie diesen Platzhalter überall in den Übersetzungen verwenden:

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

Benutzerdefinierte IDs

Beachten Sie, dass, wenn Sie Übersetzungen mit benutzerdefinierten IDs haben, diese von $localize verwendet werden (wie es vorher der Fall war):

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

Dann sieht Ihre Übersetzung wie folgt aus:

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

, was natürlich schöner zu verwenden ist.

Wie sieht es mit Übersetzungen im Code aus?$localize kennt auch eine Syntax, die es erlaubt, eine ID anzugeben:

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

Die Syntax für die benutzerdefinierte ID ist die gleiche wie in den Vorlagen, und die ID wird von Doppelpunkten umgeben, um sie vom Inhalt der Übersetzung zu trennen.

Wie bei der Vorlagensyntax kann man auch hier eine Beschreibung und eine Bedeutung angeben, um den Übersetzern ein wenig Kontext zu geben: :meaning|description@@id:message.

Zum Beispiel:

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

Denken Sie daran, dass dies eine undokumentierte API auf niedrigem Niveau ist.Das Angular-Team oder die Community wird wahrscheinlich Funktionen auf höherem Niveau mit einer besseren Entwicklererfahrung anbieten (nun, ich hoffe es!).Locl von Olivier Combe, dem Autor von ngx-translateist es wahrscheinlich wert, ein Auge darauf zu werfen 🧐.

Laufzeitübersetzungen

Wie ich bereits erwähnte, wird die Anwendung bei Verwendung der obigen CLI-Befehle (ng serve --configuration=fr oder ng build --localize) kompiliert und dann übersetzt, bevor sie im Browser erscheint, so dass es keine $localize-Aufrufe zur Laufzeit gibt.

Aber $localize wurde entwickelt, um eine weitere Möglichkeit zu bieten: Laufzeitübersetzungen.Was bedeutet das? Nun, wir wären in der Lage, nur eine Anwendung auszuliefern, die $localize-Aufrufe enthält, und bevor die Anwendung startet, könnten wir die gewünschten Übersetzungen laden. N Builds und N Bundles für N Locales \o/

Ohne zu sehr ins Detail zu gehen, ist dies bereits mit v9 möglich, indem wir die loadTranslations-Funktion von @angular/localize verwenden.Aber das muss gemacht werden, bevor die Anwendung startet.

Sie können Ihre Übersetzungen in polyfills.ts laden mit:

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

Wie Sie sehen können, gibt es keine Rücksicht auf das Gebietsschema: Sie laden Ihre Übersetzung einfach als Objekt, dessen Schlüssel die zu übersetzenden Zeichenketten sind, und die Werte, ihre Übersetzungen.

Wenn man nun ein einfaches ng serve ausführt, wird der Titel in Französisch angezeigt und man braucht kein ng xi18n oder messages.fr.xlf oder eine spezifische Konfiguration für jedes Gebietsschema in angular.json mehr.Langfristig, wenn dies richtig unterstützt und dokumentiert wird, sollten wir in der Lage sein, JSON-Dateien zur Laufzeit zu laden, wie es die meisten i18n-Bibliotheken tun.Man könnte es sogar in v9 erreichen, es ist nur ein bisschen manuelle Arbeit, aber es ist machbar.

Was ist mit dem Ändern des Gebietsschemas während der Laufzeit, können wir einen anderen Satz von Übersetzungen laden, wenn die Anwendung gestartet wird? Die derzeitige Art und Weise, wie $localize-Aufrufe generiert werden, macht es unmöglich, sie im Nachhinein zu ändern: Man muss die Anwendung neu starten.

  • Aber wenn es Ihnen nichts ausmacht, Ihren Browser zu aktualisieren, ist es möglich.
    • Ich habe eine einfache Strategie getestet, die funktioniert:
      • Ein Benutzer wählt eine neue Sprache aus (zum Beispiel Spanisch).
      • Wir speichern die Sprache im Browser (z.B. im localStorage)
      • Wir laden die Seite neu, was die Anwendung neu startet
      • In polyfills.ts beginnen wir mit dem Lesen der gespeicherten Sprache
      • Wir laden den richtigen Satz von Übersetzungen für Spanisch mit loadTranslations.

      Natürlich wird dies in Zukunft einfacher sein, entweder in einer zukünftigen Version von Angular, oder über eine Bibliothek aus dem Ökosystem.Wie auch immer, wir kommen dem Ziel näher, nur noch eine Version unserer Anwendung auszuliefern und nur noch die Übersetzungen zur Laufzeit zu laden \o/

      Alle unsere Materialien (Ebook, Online-Training und Schulungen) sind auf dem neuesten Stand mit diesen Änderungen, wenn Sie mehr erfahren möchten!

Leave a Reply