Inhaltsverzeichnis


Nichts verpassen?

angularjs | Pascal Precht ‚ÄĘ | 14 Minuten

Internationalisierung (I18n) in Apps ist heutzutage ein Muss. Ob auf dem Desktop, im Web oder auf mobilen Endger√§ten, Apps sollten dem Nutzer die M√∂glichkeit geben die Sprache des UIs ausw√§hlbar zu machen. Auch AngularJS bietet uns ein Internationalisierungs- und Lokalisierungs-Feature in s√§mtlichen Filterkomponenten. Da sich der Support aktuell aber nur auf Datums-, Zahlen-, und W√§hrungsformatierungen beschr√§nkt, sind die M√∂glichkeiten zum Internationalisieren vollst√§ndiger App-Inhalte schnell ausgereizt. Also was tun, wenn die Anforderungen √ľber das I18n-Feature der Filterkomponenten hinausgehen? Hier kommt nun angular-translate ins Spiel.

angular-translate ist ein AngularJS-Modul, das von Pascal Precht aktiv entwickelt wird und mit dem mehrsprachigen AngularJS-Anwendungen auch aus der Content-Sicht nichts mehr im Wege steht. angular-translate bietet f√ľr diesen Zweck viele Features, die uns das Leben leichter machen. Dazu geh√∂rt das asynchrone Nachladen von √úbersetzungsdateien oder das automatische Merken der vom Benutzer ausgew√§hlten Sprache. Dieser Artikel zeigt, wie einfach es ist mit angular-translate eine AngularJS-Applikation in mehreren Sprachen anzubieten.

Installation

Am einfachsten lässt sich angular-translate mit dem von Twitter entwickelten Package-Manager Bower installieren. Bower bietet dazu ein simples Command Line Interface, mit dem es möglich ist, Front-End Pakete zu registrieren, zu suchen, zu installieren und zu aktualisieren.

Um also angular-translate mit Bower zu installieren, reicht die Ausf√ľhrung des folgenden Befehls im Hauptverzeichnis des Projekts, in dem das Modul eingepflegt werden soll:

$ bower install angular-translate --save

Anschließend befindet sich das Modul in einem Ordner unterhalb des Projektes. Der Name des Ordners ist davon abhängig wie Bower auf der lokalen Maschine konfiguriert ist. Seit Version 0.9.0 installiert Bower sämtliche Pakete in einen bower_components Ordner. Dies lässt sich ändern, in dem man innerhalb des Projektes eine Datei .bowerrc erstellt, die beispielsweise folgenden Inhalt hat:

{
  "directory": "bower_components"
}

Neben der Installation mit Bower gibt es auch die Möglichkeit das Git-Repository von angular-translate zu klonen und anschließend das Modul manuell zu bauen. Weitere Informationen zum Installieren von angular-translate gibt es in der Installationsanleitung.

angular-translate ist nun in unserem Projekt verf√ľgbar und muss nur noch in unserer index.html eingebunden werden. Dadurch dass das Modul abh√§ngig von AngularJS ist, muss die Einbindung nach der Einbindung von AngularJS geschehen.

<script src="pfad/zu/angular.js"></script>
<script src="pfad/zu/angular-translate.js"></script>

Nachdem angular-translate eingebunden wurde, muss es nur noch als Abhängigkeit in unserer Anwendung deklariert werden.

angular.module('app', ['pascalprecht.translate']);

Sprachen registrieren

Da angular-translate als Abh√§ngigkeit der Applikation deklariert wurde, stehen nun s√§mtliche Komponenten per Dependency Injection zur Verf√ľgung. So auch der $translateProvider, mit dem sich der $translate Service konfigurieren l√§sst. Um innerhalb der Anwendung nun eine Sprache zu registrieren, nutzen wir die translations(...) Methode des $translateProvider. Sie erwartet einen einen Sprachschl√ľssel und einen JavaScript Hash (Objekt), der eine √úbersetzungstabelle darstellt. Jeder Schl√ľssel in der Tabelle entspricht einer sogenannten Translation ID, w√§hrend der Wert die konkrete √úbersetzung ist. Die Translation IDs k√∂nnen wir entsprechend der JavaScript-Regeln f√ľr Schl√ľsselbezeichner frei w√§hlen.

Gehen wir also von folgender √úbersetzungstabelle f√ľr Deutsch aus:

{
  APP_HEADLINE:  'Großartige AngularJS App',
  NAV_HOME:      'Zur Startseite',
  NAV_ABOUT:     '√úber',
  APP_TEXT:      'Irgendein Text √ľber eine gro√üartige AngularJS App.'
}

Diese Tabelle kann nun √ľber den $translateProvider f√ľr die Sprache ‚Äôde‚Äô registriert werden. (Hinweis f√ľr Upgrader: Seit Version 2 ist die Angabe des Sprachschl√ľssels obligatorisch!)

angular.module('app').config(function ($translateProvider) {
  $translateProvider.translations(‚Äėde‚Äô, {
    APP_HEADLINE:  'Großartige AngularJS App',
    NAV_HOME:      'Zur Startseite',
    NAV_ABOUT:     '√úber',
    APP_TEXT:      'Irgendein Text √ľber eine gro√üartige AngularJS App.'
  });

  // Nicht vergessen: die Standardsprache
  $translateProvider.preferredLanguage(‚Äėde‚Äô);
});

Wie man im Beispiel sieht, sollte man auch immer eine Standardsprache definieren, damit angular-translate auch weiß, welche Sprache gerade aktiv ist.

Es gibt seit Version 2 auch die M√∂glichkeit, die Sprache √ľber den Browser automatisch zu ermitteln (Language Negotation).

$translateProvider.determinePreferredLanguage();

Damit werden automatisch Sprachschl√ľssel nach dem Muster de_DE, en_US, etc. in den √úbersetzungstabellen gesucht.

Des Weiteren ist es ebenfalls m√∂glich, √úbersetzungstabellen mit Namensr√§umen zu √ľbergeben. Diese l√∂st angular-translate dann entsprechend auf. Der Vorteil bei der Verwendung von Namensr√§umen liegt darin, dass √úbersetzungstabellen nach Anwendungsgebieten strukturiert werden k√∂nnen. So ist selbst eine gro√üe Anzahl an √úbersetzungen leicht zu warten. Ein Beispiel dazu gibt es in der Dokumentation.

Filter und Direktive

Nachdem die Anwendung nun √ľber eine √úbersetzungstabelle in Kenntnis gesetzt wurde, k√∂nnen die Filter- oder die Direktivenkomponente verwendet werden, um die Translation IDs in der Views zu √ľbersetzen.

Eine View könnte also wie folgt aussehen:

...
<body ng-app="app">
  <header>
    <h1>{{ 'APP_HEADLINE' | translate }}</h1>
  </header>
  <nav>
    <ul>
      <li>{{ 'NAV_HOME' | translate }}</li>
      <li>{{ 'NAV_ABOUT' | translate }}</li>
    </ul>
  </nav>
  <p>{{ 'APP_TEXT' | translate }}</p>
</body>
...

In manchen Fällen ist die Verwendung des Filters eher unpassend. Da kommt dann die Direktive zum Einsatz. Das obige Beispiel sähe also mit der Verwendung der Direktive wie folgt aus:

...
<body ng-app="app">
  <header>
    <h1 translate="APP_HEADLINE"></h1>
  </header>
  <nav>
    <ul>
      <li translate="NAV_HOME"></li>
      <li translate="NAV_ABOUT"></li>
    </ul>
  </nav>
  <p translate="APP_TEXT"></p>
</body>
...

Sowohl die Filterkomponente als auch die Direktive unterst√ľtzen das Evaluieren √ľbergebener Interpolationsausdr√ľcke.

Nehmen wir die √úbersetzungstabelle

{ "GREETING": "Hallo, mein Name ist {{name}}" }

und das Template

<p translate="GREETING" translate-value-name="Pascal"></p>
```html

so erhalten wir in folgendes (gek√ľrztes) HTML-Resultat:

```html
<p>Hallo, mein Name ist Pascal</p>

Ausf√ľhrliche Beispiele dazu gibt es in der API-Dokumentation.

Service

Falls man Übersetzungen außerhalb eines Templates (also einem Filter oder einer Direktive) benötigt, so kann auch der $translate Service direkt verwendet werden. Die einfachste Verwendung sieht dabei wie folgt aus (Beispiel in einem Controller):

angular.module(‚Äėapp‚Äô).controller(‚ÄėMyCtrl‚Äô, function ($scope, $translate) {

  $scope.showBox = function () {
    $translate(‚ÄėAPP_TEXT‚Äô).then(function (text) {
      alert(text);
    });
  };

});

Es mag zun√§chst etwas umst√§ndlich sein, dass auch ‚Äúsimple‚ÄĚ √úbersetzungen nur √ľber Promises erreichbar sind. Dies ist allerdings technisch begr√ľndet: Alle √úbersetzungen durchlaufen intern einen komplexen, asychronen Protess, etwa der Fallback-Sprache oder weiteren noch zu ladenen Sprachdateien. Daher ist ein sychroner Zugriff und gleichzeitig eine korrekte √úbersetzung nicht m√∂glich.

Seit Version 2.1 ist es au√üerdem m√∂glich, direkt einen ganzen Satz via Service √ľbersetzen zu lassen.

Mehrsprachigkeit

Die Applikation macht bisher nichts weiter als Platzhalter mit ihren konkreten Werten zu ersetzen. Wirklich interessant und sinnvoll wird die Verwendung von angular-translate aber erst unter der Ber√ľcksichtung von Mehrsprachigkeit. Die √úbersetzungstabelle aus dem ersten Beispiel k√∂nnte in englischer Sprache also wie folgt aussehen:

{
  APP_HEADLINE:  'Awesome AngularJS App',
  NAV_HOME:      'Start',
  NAV_ABOUT:     'About',
  APP_TEXT:      'Some text about the awesome AngularJS app.'
}

Auch diese zweite √úbersetzungstabelle f√ľgen wir in der Konfigutationsphase des $translate Services hinzu. Allerdings muss jetzt eine anderer Schl√ľssel verwendet werden, n√§mlich zu welcher Sprache diese √úbersetzungstabelle geh√∂rt, damit angular-translate diese entsprechend identifizieren kann.

Um beide Sprachen zu registrieren, wird der bestehende Code somit folgendermaßen erweitert:

angular.module('app').config(function ($translateProvider) {
  // deutsche Sprache
  $translateProvider.translations('de_DE', {
    APP_HEADLINE:  'Großartige AngularJS App',
    NAV_HOME:      'Zur Startseite',
    NAV_ABOUT:     '√úber',
    APP_TEXT:      'Irgendein Text √ľber eine gro√üartige AngularJS App.'
  });

  // englische Sprache
  $translateProvider.translations('en_US', {
    APP_HEADLINE:  'Awesome AngularJS App',
    NAV_HOME:      'Start',
    NAV_ABOUT:     'About',
    APP_TEXT:      'Some text about the awesome AngularJS app.'
  });

  $translateProvider.preferredLanguage(‚Äėde_DE‚Äô);
});

Nun gibt es zwei Sprachen in unserer Applikation. Über die Methode preferredLanguage(...), wird angular-translate mitgeteilt, dass ’de_DE’ die bevorzugte, registrierte Sprache sein soll.

Gro√üartig! Unsere Anwendung unterst√ľtzt nun zwei Sprachen. Es bleibt aber die Frage, wie der Benutzer zwischen den unterst√ľtzten Sprachen wechseln kann.

Die Sprache zur Laufzeit wechseln

Wir sollten also nun auch eine M√∂glichkeit schaffen, um die Sprache zur Laufzeit wechseln zu k√∂nnen. Ein weiterer Teil des angular-translate Moduls ist der $translate-Service, der Schnittstellen anbietet, um zum Einen Konfigurationen auszulesen und zum Anderen die Sprache zur Laufzeit zu √§ndern. F√ľr Letzteres verwendet wir einfach die Methode use(...), welche den Sprachschl√ľssel der Sprache erwartet, zu der zur Laufzeit gewechselt werden soll.

Folglich ist es sehr einfach einen Controller zu implementieren, der sich um den Wechsel der Sprache k√ľmmert.

angular.module('app').controller('LangCtrl', function ($scope, $translate) {

  $scope.changeLang = function (key) {
    $translate.use(key).then(function (key) {
      console.log("Sprache zu " + key + " gewechselt.");
    }, function (key) {
      console.log("Irgendwas lief schief.");
    });
  };
});

Im Scope des LangCtrl gibt es eine Funktion changeLang(...), die einen Sprachschl√ľssel erwartet und diesen an use(...) weiter delegiert. Des Weiteren ist unschwer zu erkennen, dass use(...) ein Promise zur√ľckgibt. Das erm√∂glicht uns auf diese asynchrone Operation wie gewohnt zu reagieren.

Wir vervollst√§ndigen das Beispiel, indem wir innerhalb der View deklarieren, f√ľr welchen Teil des DOMs unser LangCtrl verantwortlich sein soll.

...
<body ng-app="app">
  <header>
    <h1>{{ 'APP_HEADLINE' | translate }}</h1>

    <ul ng-controller="LangCtrl">
      <li><a href="" ng-click="changeLang('en_US')">Englisch</a></li>
      <li><a href="" ng-click="changeLang('de_DE')">German</li>
    </ul>

  </header>
  <nav>
    <ul>
      <li>{{ 'NAV_HOME' | translate }}</li>
      <li>{{ 'NAV_ABOUT' | translate }}</li>
    </ul>
  </nav>
  <p>{{ 'APP_TEXT' | translate }}</p>
</body>
...

Der Benutzer unserer Applikation ist nun in der Lage die Sprache zur Laufzeit zu wechseln. In einem gew√∂hnlichen Anwendungsfall ist es w√ľnschenswert, dass sich die Anwendung die gew√§hlte Sprache merkt. Auch dieses Feature wird von angular-translate unterst√ľtzt. Die Einzelheiten dazu k√∂nnen in dem entsprechenden Teil der Dokumentation nachgelesen werden.

√úbersetzungen asynchron nachladen

Vor allem bei gro√üen √úbersetzungstabellen und vielen Sprachen ist es w√ľnschenswert die Daten und Logiken, die nicht initial gebraucht werden, asynchron nachzuladen, um so die Performance der Applikation zu verbessern. Auch f√ľr dieses Problem bietet angular-translate eine L√∂sung. F√ľr das Modul existieren einige Extensions, die als Add-On dazu installiert werden k√∂nnen. So gibt es f√ľr das asynchrone Nachladen von √úbersetzungsdateien zwei Extensions, die je nach Anwendungsfall zum Einsatz kommen k√∂nnen.

In unserem Beispiel verwenden wir den static-files-loader. Dieser kann genau wie angular-translate mithilfe von Bower installiert werden. Dazu f√ľhren wir folgenden Befehl auf der Kommandozeile aus:

$ bower install angular-translate-loader-static-files --save

Und wieder binden wir die Ressource entsprechend in unserer index.html ein:

<script src="pfad/zu/angular.js"></script>
<script src="pfad/zu/angular-translate.js"></script>
<script src="pfad/zu/angular-translate-loader-static-files.js"></script>

Der static-files-loader ermöglicht das asynchrone Nachladen von Übersetzungsdateien, indem ein bestimmtes Muster eingehalten wird. Was genau bedeutet das? Zunächst muss dem $translateProvider mitgeteilt werden, das Übersetzungsdaten nicht etwa per translations(...) registriert, sondern mit dem static-files-loader asynchron nachgeladen werden sollen. Dazu bietet der $translateProvider die Methode useStaticFilesLoader() an, die während der Konfigurationsphase zum Einsatz kommt. Die Methode erwartet ein Konfigurationsobjekt, welches das Muster der Namen der Übersetzungsdateien beschreibt. Der Name einer Übersetzungsdatei setzt sich wie folgt zusammen:

[:prefix]{{langKey}}[:suffix]

Wenn wir also unsere √úbersetzungstabellen in √úbersetzungsdateien transferieren und deren Namen lang-en_US.json und lang-de_DE.json lauten, w√ľrde das Konfigurationsobjekt folgenderma√üen aussehen.

angular.module('app').config(function ($translateProvider) {

  $translateProvider.useStaticFilesLoader({
    prefix: 'lang-',
    suffix: '.json'
  });

  $translateProvider.preferredLanguage('de_DE');
});

Selbstverst√§ndlich muss dem $translateProvider wieder mitgeteilt werden, welche Sprache initial verwendet werden soll. Was passiert nun im Hintergrund? angular-translate wei√ü, das es den static-files-loader zum Nachladen der √úbersetzungdateien nutzen und die Sprache mit dem Schl√ľssel de_DE inital verwenden soll. Da zum Start der App aber noch keine √úbersetzungstabelle vorhanden ist, l√§dt angular-translate diese so schnell wie m√∂glich automatisch nach.

Es ist auch möglich einen asynchronen Loader in Kombination mit der Registrierung einer Übersetzungstabelle zu verwenden. So könnte man die initale Sprache direkt vorhalten, während alle anderen Sprachen zur Laufzeit auf Anfrage nachgeladen werden.

angular.module('app').config(function ($translateProvider) {
  // deutsche Sprache
  $translateProvider.translations('de_DE', {
    APP_HEADLINE:  'Großartige AngularJS App',
    NAV_HOME:      'Zur Startseite',
    NAV_ABOUT:     '√úber',
    APP_TEXT:      'Irgendein Text √ľber eine gro√üartige AngularJS App.'
  });

  $translateProvider.useStaticFilesLoader({
    prefix: 'lang-',
    suffix: '.json'
  });

  $translateProvider.preferredLanguage('de_DE');
});

In diesem Fall ist die √úbersetzungstabelle f√ľr die deutsche Sprache bereits vorhanden. angular-translate muss also keine Daten asynchron nachladen. M√∂chten wir nun die Sprache zur Laufzeit wechseln, wird sich die use(‚Ķ)-Methode des $translate-Services um das asynchrone Nachladen der √úbersetzungsdaten k√ľmmern.

Was gibt es noch?

Neben den Extensions zum asynchronen Nachladen von √úbersetzungsdaten gibt es auch Extensions f√ľr eine m√∂gliche Fehlerbehandlung und das Merken der vom Benutzer zuletzt gew√§hlten Sprache. In der offiziellen Dokumentation gibt es eine Liste aller Extensions, die f√ľr angular-translate existieren.

Des Weiteren gibt es eine ausf√ľhrliche API Dokumentation, in der s√§mtliche Features und Funktionen nachgelesen werden k√∂nnen. angular-translate wird aktiv auf GitHub weiterentwickelt und wartet auf Eure Hilfe und Verbesserungsvorschl√§ge!