So internationalisieren Sie Ihre AngularJS-App
Veröffentlicht: 2022-03-11Die Internationalisierung Ihrer App kann die Softwareentwicklung zu einer schmerzhaften Erfahrung machen, insbesondere wenn Sie nicht von Anfang an damit beginnen oder einen willkürlichen Ansatz verfolgen.
Moderne Apps, bei denen Frontend und Backend klar voneinander getrennt sind, können bei der Internationalisierung noch schwieriger zu handhaben sein. Plötzlich haben Sie keinen Zugriff mehr auf die Fülle bewährter Tools, die einst bei der Internationalisierung Ihrer traditionellen serverseitigen seitengenerierten Web-Apps geholfen haben.
Dementsprechend erfordert eine AngularJS-App die On-Demand-Bereitstellung von Internationalisierungs- (i18n) und Lokalisierungsdaten (l10n), die an den Client geliefert werden müssen, um sich im entsprechenden Gebietsschema darzustellen. Im Gegensatz zu herkömmlichen serverseitig gerenderten Apps können Sie sich nicht mehr darauf verlassen, dass der Server bereits lokalisierte Seiten liefert. Hier erfahren Sie, wie Sie eine mehrsprachige PHP-Anwendung erstellen
In diesem Artikel erfahren Sie, wie Sie Ihre AngularJS-App internationalisieren können, und lernen Tools kennen, mit denen Sie den Prozess vereinfachen können. Die Mehrsprachigkeit Ihrer AngularJS-App kann einige interessante Herausforderungen mit sich bringen, aber bestimmte Ansätze können es einfacher machen, die meisten dieser Herausforderungen zu umgehen.
Eine einfache i18n-fähige AngularJS-App
Damit der Client die Sprache und das Gebietsschema basierend auf den Benutzereinstellungen spontan ändern kann, müssen Sie eine Reihe wichtiger Designentscheidungen treffen:
- Wie gestalten Sie Ihre App so, dass sie von Anfang an sprach- und gebietsschemaunabhängig ist?
- Wie strukturieren Sie i18n- und l10n-Daten?
- Wie liefern Sie diese Daten effizient an Kunden?
- Wie abstrahieren Sie möglichst viele Implementierungsdetails auf niedriger Ebene, um den Entwickler-Workflow zu vereinfachen?
Eine möglichst frühzeitige Beantwortung dieser Fragen kann dazu beitragen, Hindernisse im späteren Entwicklungsprozess zu vermeiden. Jede dieser Herausforderungen wird in diesem Artikel behandelt; einige durch robuste AngularJS-Bibliotheken, andere durch bestimmte Strategien und Ansätze.
Internationalisierungsbibliotheken für AngularJS
Es gibt eine Reihe von JavaScript-Bibliotheken, die speziell für die Internationalisierung von AngularJS-Apps erstellt wurden.
angular-translate
ist ein AngularJS-Modul, das Filter und Anweisungen sowie die Möglichkeit bietet, i18n-Daten asynchron zu laden. Es unterstützt die Pluralisierung durch MessageFormat
und ist so konzipiert, dass es in hohem Maße erweiterbar und konfigurierbar ist.
Wenn Sie angular-translate
in Ihrem Projekt verwenden, finden Sie möglicherweise einige der folgenden Pakete sehr nützlich:
-
angular-sanitize
: kann zum Schutz vor XSS-Angriffen in Übersetzungen verwendet werden. -
angular-translate-interpolation-messageformat
: Pluralisierung mit Unterstützung für geschlechtsspezifische Textformatierung. -
angular-translate-loader-partial
: Wird verwendet, um übersetzte Zeichenfolgen an Clients zu liefern.
Für ein wirklich dynamisches Erlebnis können Sie dem Bündel ein angular-dynamic-locale
hinzufügen. Mit dieser Bibliothek können Sie das Gebietsschema dynamisch ändern – und dazu gehört auch die Art und Weise, wie Datumsangaben, Zahlen, Währungen usw. formatiert sind.
Erste Schritte: Installieren relevanter Pakete
Vorausgesetzt, Sie haben bereits Ihre AngularJS-Boilerplate bereit, können Sie NPM verwenden, um die Internationalisierungspakete zu installieren:
npm i -S angular-translate angular-translate-interpolation-messageformat angular-translate-loader-partial angular-sanitize messageformat
Vergessen Sie nach der Installation der Pakete nicht, die Module als Abhängigkeiten Ihrer App hinzuzufügen:
// /src/app/core/core.module.js app.module('app.core', ['pascalprecht.translate', ...]);
Beachten Sie, dass sich der Name des Moduls vom Namen des Pakets unterscheidet.
Übersetzen Ihres ersten Strings
Angenommen, Ihre App hat eine Symbolleiste mit Text und ein Feld mit Platzhaltertext:
<nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="#">Hello</a> </div> <div class="collapse navbar-collapse"> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" ng-model="vm.query" placeholder="Search"> </div> ... </div> </div> </nav>
Die obige Ansicht enthält zwei Textteile, die Sie internationalisieren können: „Hallo“ und „Suchen“. In Bezug auf HTML erscheint einer als innerer Text eines Anchor-Tags, während der andere als Wert eines Attributs erscheint.
Um sie zu internationalisieren, müssen Sie beide Zeichenfolgenliterale durch Token ersetzen, die AngularJS dann beim Rendern der Seite je nach Benutzereinstellung durch die tatsächlich übersetzten Zeichenfolgen ersetzen kann.
AngularJS kann dies tun, indem es Ihre Token verwendet, um eine Suche in von Ihnen bereitgestellten Übersetzungstabellen durchzuführen. Das Modul angular-translate
erwartet, dass diese Übersetzungstabellen als einfache JavaScript-Objekte oder als JSON-Objekte (bei Remote-Laden) bereitgestellt werden.
Hier ist ein Beispiel dafür, wie diese Übersetzungstabellen im Allgemeinen aussehen würden:
// /src/app/toolbar/i18n/en.json { "TOOLBAR": { "HELLO": "Hello", "SEARCH": "Search" } } // /src/app/toolbar/i18n/tr.json { "TOOLBAR": { "HELLO": "Merhaba", "SEARCH": "Ara" } }
Um die Symbolleistenansicht von oben zu internationalisieren, müssen Sie die Zeichenfolgenliterale durch Token ersetzen, die AngularJS zum Nachschlagen in der Übersetzungstabelle verwenden kann:
<!-- /src/app/toolbar/toolbar.html --> <a class="navbar-brand" href="#" translate="TOOLBAR.HELLO"></a> <!-- or --> <a class="navbar-brand" href="#">{{'TOOLBAR.HELLO' | translate}}</a>
Beachten Sie, dass Sie für inneren Text entweder die translate
-Direktive oder den translate
-Filter verwenden können. (Mehr über die translate
-Direktive erfahren Sie hier und über translate
-Filter hier.)
Mit diesen Änderungen fügt angle angular-translate
beim Rendern der Ansicht automatisch die entsprechende Übersetzung, die TOOLBAR.HELLO
entspricht, basierend auf der aktuellen Sprache in das DOM ein.
Um Zeichenfolgenliterale zu tokenisieren, die als Attributwerte erscheinen, können Sie den folgenden Ansatz verwenden:
<!-- /src/app/toolbar/toolbar.html --> <input type="text" class="form-control" ng-model="vm.query" translate translate-attr-placeholder="TOOLBAR.SEARCH">
Was wäre nun, wenn Ihre tokenisierten Strings Variablen enthalten würden?
Um Fälle wie „Hallo, {{name}}.“ zu behandeln, können Sie Variablenersetzungen mit derselben Interpolationssyntax durchführen, die AngularJS bereits unterstützt:
Übersetzungstabelle:
// /src/app/toolbar/i18n/en.json { "TOOLBAR": { "HELLO": "Hello, {{name}}." } }
Sie können die Variable dann auf verschiedene Arten definieren. Hier sind ein paar:
<!-- /src/app/toolbar/toolbar.html --> <a ... translate="TOOLBAR.HELLO" translate-values='{ name: vm.user.name }'></a> <!-- or --> <a ... translate="TOOLBAR.HELLO" translate-value-name='{{vm.user.name}}'></a> <!-- or --> <a ...>{{'TOOLBAR.HELLO | translate:'{ name: vm.user.name }'}}</a>
Umgang mit Pluralisierung und Geschlecht
Pluralisierung ist ein ziemlich schwieriges Thema, wenn es um i18n und l10n geht. Unterschiedliche Sprachen und Kulturen haben unterschiedliche Regeln dafür, wie eine Sprache in verschiedenen Situationen mit der Pluralisierung umgeht.
Aufgrund dieser Herausforderungen gehen Softwareentwickler manchmal einfach nicht auf das Problem ein (oder gehen es zumindest nicht angemessen an), was zu Software führt, die dumme Sätze wie diese produziert:
He saw 1 person(s) on floor 1. She saw 1 person(s) on floor 3. Number of people seen on floor 2: 2.
Glücklicherweise gibt es einen Standard, wie damit umzugehen ist, und eine JavaScript-Implementierung des Standards ist als MessageFormat verfügbar.
Mit MessageFormat können Sie die oben genannten schlecht strukturierten Sätze durch Folgendes ersetzen:
He saw 1 person on the 2nd floor. She saw 1 person on the 3rd floor. They saw 2 people on the 5th floor.
MessageFormat
akzeptiert Ausdrücke wie die folgenden:
var message = [ '{GENDER, select, male{He} female{She} other{They}}', 'saw', '{COUNT, plural, =0{no one} one{1 person} other{# people}}', 'on the', '{FLOOR, selectordinal, one{#st} two{#nd} few{#rd} other{#th}}', 'floor.' ].join(' ');
Sie können mit dem obigen Array einen Formatierer erstellen und ihn zum Generieren von Zeichenfolgen verwenden:
var messageFormatter = new MessageFormat('en').compile(message); messageFormatter({ GENDER: 'male', COUNT: 1, FLOOR: 2 }) // 'He saw 1 person on the 2nd floor.' messageFormatter({ GENDER: 'female', COUNT: 1, FLOOR: 3 }) // 'She saw 1 person on the 3rd floor.' messageFormatter({ COUNT: 2, FLOOR: 5 }) // 'They saw 2 people on the 5th floor.'
Wie können Sie MessageFormat
mit Angular angular-translate
verwenden, um die volle Funktionalität in Ihren Apps zu nutzen?
In Ihrer App-Konfiguration teilen Sie angular-translate
einfach mit, dass die Interpolation des Nachrichtenformats wie folgt verfügbar ist:
/src/app/core/core.config.js app.config(function ($translateProvider) { $translateProvider.addInterpolation('$translateMessageFormatInterpolation'); });
So könnte ein Eintrag in der Übersetzungstabelle dann aussehen:
// /src/app/main/social/i18n/en.json { "SHARED": "{GENDER, select, male{He} female{She} other{They}} shared this." }
Und in der Ansicht:
<!-- /src/app/main/social/social.html --> <div translate="SHARED" translate-values="{ GENDER: 'male' }" translate-interpolation="messageformat"></div> <div> {{ 'SHARED' | translate:"{ GENDER: 'male' }":'messageformat' }} </div>
Hier müssen Sie explizit angeben, dass der Nachrichtenformat-Interpolator anstelle des Standard-Interpolators in AngularJS verwendet werden soll. Dies liegt daran, dass sich die beiden Interpolatoren geringfügig in ihrer Syntax unterscheiden. Hier können Sie mehr darüber lesen.
Bereitstellung von Übersetzungstabellen für Ihre App
Nachdem Sie nun wissen, wie AngularJS Übersetzungen für Ihre Token aus Übersetzungstabellen nachschlagen kann, woher weiß Ihre App überhaupt von den Übersetzungstabellen? Wie teilen Sie Ihrer App mit, welches Gebietsschema/welche Sprache verwendet werden soll?
Hier erfahren Sie mehr über $translateProvider
.
Sie können die Übersetzungstabellen für jedes Gebietsschema, das Sie unterstützen möchten, direkt in der Datei „ core.config.js
“ Ihrer App wie folgt bereitstellen:
// /src/app/core/core.config.js app.config(function ($translateProvider) { $translateProvider.addInterpolation('$translateMessageFormatInterpolation'); $translateProvider.translations('en', { TOOLBAR: { HELLO: 'Hello, {{name}}.' } }); $translateProvider.translations('tr', { TOOLBAR: { HELLO: 'Merhaba, {{name}}.' } }); $translateProvider.preferredLanguage('en'); });
Hier stellen Sie Übersetzungstabellen als JavaScript-Objekte für Englisch (en) und Türkisch (tr) bereit, während Sie Englisch (en) als aktuelle Sprache deklarieren. Wenn der Benutzer die Sprache ändern möchte, können Sie dies mit dem Dienst $translate tun:

// /src/app/toolbar/toolbar.controller.js app.controller('ToolbarCtrl', function ($scope, $translate) { $scope.changeLanguage = function (languageKey) { $translate.use(languageKey); // Persist selection in cookie/local-storage/database/etc... }; });
Bleibt noch die Frage, welche Sprache standardmäßig verwendet werden soll. Die Hardcodierung der Ausgangssprache unserer App ist möglicherweise nicht immer akzeptabel. In solchen Fällen kann alternativ versucht werden, die Sprache automatisch mit $translateProvider zu ermitteln:
// /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.determinePreferredLanguage(); });
determinePreferredLanguage
sucht nach Werten in window.navigator
und wählt eine intelligente Vorgabe aus, bis ein klares Signal vom Benutzer kommt.
Lazy-Loading-Übersetzungstabellen
Im vorherigen Abschnitt wurde gezeigt, wie Sie Übersetzungstabellen direkt im Quellcode als JavaScript-Objekte bereitstellen können. Für kleine Anwendungen mag das akzeptabel sein, aber der Ansatz ist nicht skalierbar, weshalb Übersetzungstabellen oft als JSON-Dateien von einem entfernten Server heruntergeladen werden.
Das Verwalten von Übersetzungstabellen auf diese Weise reduziert die anfängliche Nutzlastgröße, die an den Client geliefert wird, führt jedoch zu zusätzlicher Komplexität. Jetzt stehen Sie vor der Design-Herausforderung, i18n-Daten an den Client zu liefern. Wenn dies nicht sorgfältig gehandhabt wird, kann die Leistung Ihrer Anwendung unnötig leiden.
Warum ist es so komplex? AngularJS-Anwendungen sind in Modulen organisiert. In einer komplexen Anwendung kann es viele Module geben, jedes mit seinen eigenen unterschiedlichen i18n-Daten. Ein naiver Ansatz, wie z. B. das gleichzeitige Laden und Bereitstellen von i18n-Daten, sollte daher vermieden werden.
Was Sie brauchen, ist eine Möglichkeit, Ihre i18n-Daten nach Modul zu organisieren. Auf diese Weise können Sie genau das laden, was Sie brauchen, wenn Sie es brauchen, und das, was zuvor geladen wurde, zwischenspeichern, um ein erneutes Laden derselben Daten zu vermeiden (zumindest bis der Cache ungültig ist).
Hier kommt partialLoader
ins Spiel.
Angenommen, die Übersetzungstabellen Ihrer Anwendung sind wie folgt strukturiert:
/src/app/main/i18n/en.json /src/app/main/i18n/tr.json /src/app/toolbar/i18n/en.json /src/app/toolbar/i18n/tr.json
Sie können $translateProvider
so konfigurieren, dass partialLoader
mit einem URL-Muster verwendet wird, das dieser Struktur entspricht:
// /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.useLoader('$translatePartialLoader', { urlTemplate: '/src/app/{part}/i18n/{lang}.json' }); });
Wie zu erwarten, wird „lang“ zur Laufzeit durch den Sprachcode ersetzt (z. B. „en“ oder „tr“). Was ist mit „Teil“? Woher weiß $translateProvider, welcher „Teil“ geladen werden soll?
Sie können diese Informationen innerhalb von Controllern mit $translatePartialLoader
bereitstellen:
// /src/app/main/main.controller.js app.controller('MainCtrl', function ($translatePartialLoader) { $translatePartialLoader.addPart('main'); }); // /src/app/toolbar/toolbar.config.js app.controller('ToolbarCtrl', function ($translatePartialLoader) { $translatePartialLoader.addPart('toolbar'); });
Das Muster ist jetzt vollständig und die i18n-Daten für eine bestimmte Ansicht werden geladen, wenn ihr Controller zum ersten Mal ausgeführt wird, was genau das ist, was Sie wollen.
Caching: Ladezeiten reduzieren
Was ist mit dem Caching?
Sie können den Standard-Cache in der App-Konfiguration mit $translateProvider
aktivieren:
// /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.useLoaderCache(true); // default is false });
Wenn Sie den Cache für eine bestimmte Sprache leeren müssen, können Sie $translate
verwenden:
$translate.refresh(languageKey); // omit languageKey to refresh all
Mit diesen Elementen ist Ihre Anwendung vollständig internationalisiert und unterstützt mehrere Sprachen.
Lokalisieren von Zahlen, Währungen und Daten
In diesem Abschnitt erfahren Sie, wie Sie angular-dynamic-locale
verwenden können, um die Formatierung von UI-Elementen wie Zahlen, Währungen, Datumsangaben und dergleichen in einer AngularJS-Anwendung zu unterstützen.
Dazu müssen Sie zwei weitere Pakete installieren:
npm i -S angular-dynamic-locale angular-i18n
Sobald die Pakete installiert sind, können Sie das Modul zu den Abhängigkeiten Ihrer App hinzufügen:
// /src/app/core/core.module.js app.module('app.core', ['tmh.dynamicLocale', ...]);
Gebietsschemaregeln
Gebietsschemaregeln sind einfache JavaScript-Dateien, die Spezifikationen dafür bereitstellen, wie Datumsangaben, Zahlen, Währungen und dergleichen von Komponenten formatiert werden sollen, die vom $locale-Dienst abhängen.
Die Liste der derzeit unterstützten Gebietsschemas ist hier verfügbar.
Hier ist ein Ausschnitt aus angular-locale_en-us.js
, der die Formatierung von Monat und Datum veranschaulicht:
... "MONTH": [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], "SHORTDAY": [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], ...
Im Gegensatz zu i18n-Daten gelten Gebietsschemaregeln für die Anwendung global, sodass die Regeln für ein bestimmtes Gebietsschema auf einmal geladen werden müssen.
Standardmäßig erwartet angle angular-dynamic-locale
, dass sich Dateien mit Gebietsschemaregeln in angular/i18n/angular-locale_{{locale}}.js
. Wenn sie sich woanders befinden, muss tmhDynamicLocaleProvider
verwendet werden, um den Standard zu überschreiben:
// /src/app/core/core.config.js app.config(function (tmhDynamicLocaleProvider) { tmhDynamicLocaleProvider.localeLocationPattern( '/node_modules/angular-i18n/angular-locale_{{locale}}.js'); });
Das Caching wird automatisch vom tmhDynamicLocaleCache
-Dienst gehandhabt.
Das Invalidieren des Caches ist hier weniger wichtig, da Gebietsschemaregeln weniger wahrscheinlich geändert werden als Zeichenfolgenübersetzungen.
Um zwischen Gebietsschemas zu wechseln, stellt angle angular-dynamic-locale
den Dienst tmhDynamicLocale
:
// /src/app/toolbar/toolbar.controller.js app.controller('ToolbarCtrl', function ($scope, tmhDynamicLocale) { $scope.changeLocale = function (localeKey) { tmhDynamicLocale.set(localeKey); // Persist selection in cookie/local-storage/database/etc... }; });
Generieren von Übersetzungstabellen mit automatischer Übersetzung
Gebietsschemaregeln werden mit dem angular-i18n
Paket geliefert, sodass Sie lediglich den Paketinhalt Ihrer Anwendung nach Bedarf zur Verfügung stellen müssen. Aber wie generieren Sie die JSON-Dateien für Ihre Übersetzungstabellen? Es gibt nicht genau ein Paket, das Sie herunterladen und in unsere Anwendung stecken könnten.
Eine Möglichkeit besteht darin, APIs für die programmgesteuerte Übersetzung zu verwenden, insbesondere wenn die Zeichenfolgen in Ihrer Anwendung einfache Literale ohne Variablen oder pluralisierte Ausdrücke sind.
Mit Gulp und ein paar zusätzlichen Paketen ist das Anfordern programmatischer Übersetzungen für Ihre Anwendung ein Kinderspiel:
import gulp from 'gulp'; import map from 'map-stream'; import rename from 'gulp-rename'; import traverse from 'traverse'; import transform from 'vinyl-transform'; import jsonFormat from 'gulp-json-format'; function translateTable(to) { return transform(() => { return map((data, done) => { const table = JSON.parse(data); const strings = []; traverse(table).forEach(function (value) { if (typeof value !== 'object') { strings.push(value); } }); Promise.all(strings.map((s) => getTranslation(s, to))) .then((translations) => { let index = 0; const translated = traverse(table).forEach(function (value) { if (typeof value !== 'object') { this.update(translations[index++]); } }); done(null, JSON.stringify(translated)); }) .catch(done); }); }); } function translate(to) { return gulp.src('src/app/**/i18n/en.json') .pipe(translateTable(to)) .pipe(jsonFormat(2)) .pipe(rename({ basename: to })) .pipe(gulp.dest('src/app')); } gulp.task('translate:tr', () => translate('tr')); This task assumes the following folder structure: /src/app/main/i18n/en.json /src/app/toolbar/i18n/en.json /src/app/navigation/i18n/en.json ...
Das Skript liest zuerst alle englischen Übersetzungstabellen, fordert asynchron Übersetzungen für ihre Zeichenfolgenressourcen an und ersetzt dann die englischen Zeichenfolgen durch die übersetzten Zeichenfolgen, um eine Übersetzungstabelle in einer neuen Sprache zu erstellen.
Schließlich wird die neue Übersetzungstabelle als Geschwister zur englischen Übersetzungstabelle geschrieben, was Folgendes ergibt:
/src/app/main/i18n/en.json /src/app/main/i18n/tr.json /src/app/toolbar/i18n/en.json /src/app/toolbar/i18n/tr.json /src/app/navigation/i18n/en.json /src/app/navigation/i18n/tr.json ...
Auch die Implementierung von getTranslation
ist unkompliziert:
import bluebird from 'bluebird'; import MicrosoftTranslator from 'mstranslator'; bluebird.promisifyAll(MicrosoftTranslator.prototype); const Translator = new MicrosoftTranslator({ client_id: process.env.MICROSOFT_TRANSLATOR_CLIENT_ID, client_secret: process.env.MICROSOFT_TRANSLATOR_CLIENT_SECRET }, true); function getTranslation(string, to) { const text = string; const from = 'en'; return Translator.translateAsync({ text, from, to }); }
Hier verwenden wir Microsoft Translate, aber man könnte auch einfach einen anderen Anbieter wie Google Translate oder Yandex Translate verwenden.
Obwohl programmatische Übersetzungen bequem sind, gibt es mehrere Nachteile, darunter:
- Roboterübersetzungen eignen sich gut für kurze Zeichenfolgen, aber selbst dann können Fallstricke mit Wörtern auftreten, die in verschiedenen Kontexten unterschiedliche Bedeutungen haben (z. B. kann „Pool“ Schwimmen oder Gruppieren bedeuten).
- APIs sind möglicherweise nicht in der Lage, Zeichenfolgen mit Variablen oder Zeichenfolgen zu verarbeiten, die auf dem Nachrichtenformat basieren.
In diesen und anderen Fällen können menschliche Übersetzungen erforderlich sein; Das ist jedoch ein Thema für einen anderen Blogbeitrag.
Die Internationalisierung von Front-Ends sieht nur entmutigend aus
In diesem Artikel haben Sie gelernt, wie Sie diese Pakete verwenden, um AngularJS-Anwendungen zu internationalisieren und zu lokalisieren.
angular-translate
, angular-dynamic-locale
und gulp
sind leistungsstarke Tools zur Internationalisierung einer AngularJS-Anwendung, die schmerzhafte Implementierungsdetails auf niedriger Ebene kapseln.
Eine Demo-App, die die in diesem Beitrag diskutierten Ideen veranschaulicht, finden Sie in diesem GitHub-Repository.