Internationalisierung. Eine typische Aufgaben bei der Implementierung von Web-Anwendungen. Diese Anforderung macht auch vor Angular nicht halt. Hier bieten sich dem Entwickler gleich zwei Möglichkeiten: zum einen die Verwendung der Core-Bibliothek oder die Verwendung der Bibliothek: „ngx-translate“.
Angular i18n
Die Core Bibliothek bietet bereits die Funktion Texte innerhalb der Anwendung zu internationalisieren dazu ist in folgenden Schritten vorzugehen:
- Texte auszeichnen
- Übersetzungsdatei erzeugen
- Übersetzungen durchführen
- Anwendung bauen
Texte auszeichnen
Angular bedient sich für die Internationalisierung der Templates der Pipe „i18n“. Diese kann innerhalb des Templates verwendet werden um die Inhalte von beliebigen Komponenten zu markieren:
<h1>placeholder-text</h1>
Neben der reinen Markierung existieren außerdem noch Möglichkeiten zum setzen
einer Beschreibung oder die Vergabe einer eindeutigen ID die später hilfreich ist wenn Texte der Anwendung hinzugefügt werden sollen: beschreibung@@id
<h1>placeholder-text</h1>
Übersetzungsdatei erzeugen
Dank Angular-CLI ist die Erzeugung einer entsprechenden Übersetzungs-Datei dann ein Kinderspiel
ng xi18n
Führt zur Generierung einer „xlf-Datei“ welche alle markierten Textstellen als so genannte „trans-unit“ beinhaltet:
placeholder app/app.component.html 12
Übersetzungen durchführen
Die Default-Sprache ist Englisch. Für weitere Übersetzungen wird die so erzeugte Datei nun kopiert und in aller Regel um einen Sprach-Post Fix erweitert (z.B. messages.de.xlf) Die eigentliche Übersetzung muss dann natürlich noch in den Dateien durchgeführt werden: dazu wird neben den bereits vorhandenen „source“ Attributen ein „target“ Attribut ergänzt, welches den übersetzten Wert erhält:
(welcome) Willkommen ...
(Alltagstauglich mit ngx-i18nsupport)
Bis hierher erscheint das Vorgehen noch nachvollziehbar. Allerdings zeigt sich die Core Bibliothek bei einem genaueren Blick etwas sperrig:
ein Update bestehender Texte ist direkt nicht möglich.
So würde bei einem erneuten Aufruf der Generierung die bestehende Datei einfach überschrieben werden und alle bisher gepflegten Übersetzungen wären dahin. Ein npm-Tool was hier Abhilfe schafft ist: xliffmerge welches über npm installiert werden kann (npm install –save-dev ngx-i18nsupport). Dieses Tool erlaubt uns ein intelligentes mergen von bestehenden Sprachdateien. Ein Typischer Ablauf lässt sich vermutlich am einfachsten anhand des projekt-tauglichen npm-Script erkären:
---- messages.config.json ----- { "xliffmergeOptions": { "srcDir": "src/i18n", "genDir": "src/i18n" } } ---- npm script ----- "i18n": "ng xi18n --output-path i18n --i18n-locale en && xliffmerge --profile src/messages.config.json en de"
Zuerst lassen wir Angular-CLI ein Standard Message File generieren, welches wir in einem speziellen Ordner ablegen (ng xi18n –output-path i18n). Dann kommt xliffmerge ins Spiel: mittels einer Konfigurationsdatei (in der wir im einfachsten Fall wie zu sehen nur den Ordner angeben in dem unsere messages.xlf abgelegt wurde) und den Sprachen die wir unterstützen wollen (xliffmerge –profile src/messages.config.json en de) werden nun entsprechende Dateien erzeugt (messages.de.xlf, message.en.xlf). Der entscheidende Punkt:
sollten diese Dateien bereits vorhanden und mit Übersetzungen versorgt sein werden nur die neuen Elemente in diese Dateien übernommen.
Anwendung bauen
Ein zweiter Punkt dürfte den ein oder anderen überraschen. Wenn wir den AOT Compiler verwenden (was wir in aller Regel für unsere Produktions-Builds tun wollen und in der aktuellen Version auch der Standard Fall bei einem Prod-Build über
Angular CLI ist) ist ein dynamisches lesen / ermitteln der übersetzten Werte technisch nicht möglich. Der Compiler läuft ja im Fall von AOT nicht im Browser sondern bereits beim Build-Prozess.
Das heißt: ein separater Build pro unterstützte Sprache
und eine eigenständige Version unserer Anwendung die deployt werden muss. Das klingt nun etwas aufwendiger / dramatischer als es ist. Also schauen wir uns das ganze mal praktisch an:
mit AOT
Ein separater Build für jede unterstützte Sprache. Das lässt sich relativ einfach über ein npm Skript lösen das z.B. für Deutsch und Englisch auf Basis der oben erstellen Message-Dateien so aussehen könnte:
"build": "npm run i18n && ng build --prod --i18n-file src/i18n/messages.en.xlf --i18n-format xlf --i18n-locale en --output-path dist/en --base-href . && ng build --prod --i18n-file src/i18n/messages.de.xlf --i18n-format xlf --i18n-locale de --output-path dist/de --base-href .",
für jede Sprache haben wir nun im „dist“ Ordner eine lokalisierte Version unserer
Anwendung. Ein entsprechend konfigurierter Proxy oder eine simple Einstiegsseite mit ein wenig JavaScript könnte nun
automatisch auf Basis der Browser-Locale auf die korrekte Version umleiten.
mit JIT
Während der Entwicklung werden die meisten ( aus Gründen der Geschwindigkeit) auf den JIT Compiler setzen ( Standard bei ng serve). Zwar muss auch hier die Sprache vor Start der Anwendung feststehen ( und damit muss die Anwendung bei immer neu geladen werden wenn die Sprache geändert werden sollte) aber anders als bei AOT kann die Anwendung je nach Bedarf dynamisch initialisiert werden. Dazu konfigurieren wir die entsprechende Sprache und Message-Datei beim bootstrap unserer Anwendung in der main.ts, hier zum Beispiel über die Browser-Locale:
declare const require; export function translationsFactory() { let locale = (window.clientInformation && window.clientInformation.language) || window.navigator.language; let language = locale.split('-')[0]; return require(`raw-loader!./i18n/messages.${language}.xlf`); } platformBrowserDynamic() .bootstrapModule(AppModule, { providers: [ { provide: TRANSLATIONS, useFactory: translationsFactory, deps: [] }, { provide: TRANSLATIONS_FORMAT, useValue: 'xlf' } ] }) .catch(err => console.error(err));
(Alltagstauglich mit i18n-polyfill)
Leider muss auch hier wieder ein „aber“ da gelassen werden. Eine typische Anforderung ist sicherlich auch Benachrichtigungen zu internationalisieren, also auch programmatisch auf dies Texte zugreifen zu können. Bisher hat es dieses Feature leider noch nicht direkt in den Standard geschafft, bis dahin existiert aber ein Polyfil der hoffentlich in ähnlicher Form auch in den Standard übergehen wird:
i18n-polyfill, https://github.com/ngx-translate/i18n-polyfill
Viele Ecken müssen bis hierher umschifft werden um die vermeidlich einfache Anforderung der Internationalisierung um zu setzen. Offiziell heißt es das dieser Prozess / das Vorgehen noch verbessert werden soll um die Verwendung von i18n zu vereinfachen,seien wir also gespannt.
Im zweiten Teil werfen wir trotzdem einen kurzen Blick auf eine Alternative Lösung die in Sachen Performance zwar der Core-Bibliothek nicht das Wasser reichen kann, aber ein etwas pragmatischeres Vorgehen wählt.