zurück zum Artikel

Internationalisierung für Angular, Teil 2: ngx-translate

Daniel Schwab
Internationalisierung für Angular, Teil 2: ngx-translate

Angular bringt zwar eine Reihe Bordmittel zur Internationalisierung mit, allerdings gibt es mit ngx-translate eine Alternative, die sich auch mit anderen Angeboten wie Ionic nutzen lässt.

Im ersten Teil [1] wurde gezeigt, wie Angular die Internationalisierung mit allen Vor- und Nachteilen angeht. Als Alternative dazu hat sich ngx-translate heraus kristallisiert. Obwohl das Ziel dasselbe ist, unterscheidet sich der Ansatz der Bibliothek deutlich von der Angular-Implementierung. Mit ngx-translate werden Übersetzungen zur Laufzeit durchgeführt. Dafür liefert die Bibliothek eine Direktive, eine Pipe und ein Service mit. Durch den flexibleren Aufbau ist es möglich, Übersetzungen auszuwechseln, ohne die Applikation neu starten zu müssen. Als Format kommt JSON zum Einsatz und auch externe Datenquellen lassen sich verwenden.

Bevor sie mit der Übersetzung starten können, müssen Entwickler die Bibliothek im Projekt installieren und integrieren:

npm install @ngx-translate/core --save

Im AppModule beziehungsweise dem Root-Modul der Applikation ist das TranslateModule über den Bereich imports einzurichten. Die Methode forRoot kann noch erweiterte Konfigurationen, wie die Datenquelle, enthalten:

import { TranslateModule } from '@ngx-translate/core';

@NgModule({
imports: [
TranslateModule.forRoot()
] ,
bootstrap: [
AppComponent
]
})
export class AppModule {
}

Damit Direktiven und Pipes, die von ngx-translate kommen, im Template einer Komponente verfügbar sind, ist das TranslateModule in die Feature-Module zu importieren, welche die Internationalisierung nutzen möchten. Damit Entwickler diese Aufgabe nicht ständig wiederholen müssen, kann man sie in ein SharedModule exportieren, sofern vorhanden. Damit steht allen Modulen, die das SharedModule importieren, ebenfalls das TranslateModule zur Verfügung:

import { TranslateModule } from '@ngx-translate/core';

@NgModule({
exports: [
TranslateModule
]
})
export class SharedModule {
}

Um die Spracheinstellungen festzulegen, müssen Entwickler den Service TranslateService per Dependency Injection in die AppComponent holen, beziehungsweise in die Komponente, die im Bereich bootstrap des Root-Moduls angegeben ist.

Dafür sind drei Parameter einzustellen:

import { TranslateService } from '@ngx-translate/core';

@Component({
selector: 'flight-app',
templateUrl: './app.component.html'
})
export class AppComponent {
constructor(private translate: TranslateService) {

translate.setDefaultLang('de');
translate.use('de');
translate.addLangs(['en']);

}
}

Wie der Name der Methode setDefaultLang erahnen lässt, bestimmt sie die Standardsprache. Entwickler sollten eine auswählen, für die eine vollständige Übersetzung existiert. Sollte in der über die Methode use gesetzten Sprache ein Wert fehlen, sucht die Bibliothek stattdessen in der Standardsprache. Fehlt auch dort die Übersetzung, zeigt sie die definierte Referenz an.

Im Beispiel sind zwei Sprachen verfügbar. Die zweite ist über die Methode addLangs eingebunden. Die übergebenen Werte entsprechen dabei nicht tatsächlich einem regionalen Key sondern sind frei wählbar, ebenso wie die Dateinamen. Es ist jedoch sinnvoll, einen entsprechenden Key einzusetzen. Ein Loader nutzt die sich aus setDefaultLang und dem Array von addLangs zusammensetzende Sprach-ID später, um die Datenquelle zu bestimmen.

Damit die Bibliothek anhand der Sprach-ID Sprachdateien laden kann, bezieht sie die Übersetzungsdaten über einen TranslateLoader. Er ist innerhalb der Methode TranslateModule.forRoot zu konfigurieren. Standardmäßig wird dafür der HTTP-Loader von ngx-translate genutzt, den man zusätzlich installieren muss:

npm install @ngx-translate/http-loader --save

Im folgendem Codeauszug ist zu sehen, dass der Provider TranslateLoader eine eigene Factory erhält, die den Loader TranslateHttpLoader über die Methode createTranslateLoader zurückliefert. Hier lassen sich nun der Pfad sowie die Endung bestimmen oder ein Suffix und Präfix für die zuvor definierte Sprach-ID. Entwickler können zudem den Service anpassen, der den Aufruf tätigt. Das ist notwendig, wenn etwa ein eigener Wrapper-Service für HTTP existiert.

So wird bei Aufruf der Anwendung im Hintergrund über die URL http://..../locale/de.json die deutsche Übersetzung geladen:

import { Http } from '@angular/http';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';

export function createTranslateLoader(http: Http) {
return new TranslateHttpLoader(http, './locale/', '.json');
}
@NgModule({
imports: [
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (createTranslateLoader),
deps: [Http]
}
})
]
})
export class AppModule {}

Die Bibliothek ist nun einsatzbereit und die Dateien en.json und de.json als Datenquelle eingerichtet. Somit kann mit der Internationalisierung begonnen werden. Als Beispiel dient erneut das Übersetzen der Tabelle des ersten Artikelteils [2] beginnend mit dem Tabellenkopf.

Dazu definieren Entwickler die zuvor erwähnten Referenzen, die von der Direktive translate oder der gleichnamigen Pipe von ngx-translate benötigt werden, um eine Verbindung zur Übersetzungsdatei zu schaffen. An der Stelle zeigt sich der Unterschied zur Angular-Methode. Es wird kein Text zur Übersetzung markiert, sondern eine Funktion integriert, um selbst definierte Referenzen wie BOOKINGS.from beim Aufruf der Komponente zu ersetzen. Die Direktive translate nutzt dafür den Inhalt des HTML-Tag. Mit translateParams lassen sich zudem Parameter übergeben.

Als Alternative zur Direktive erzielt die Pipe translate den gleichen Effekt. Über die Expression benötigt man so jedoch keinen eigenen Tag. Damit Entwickler auch HTML nutzen können, ist es möglich, die Pipe an innerHTML zu übergeben. Attribute lassen sich auf die gleiche Weise übersetzen, wie BOOKINGS.bookingDate demonstriert:

<table>
<thead>
<tr>
<th translate>BOOKINGS.from</th>
<th translate [translateParams]="params">BOOKINGS.to</th>
<th>{{'BOOKINGS.passengers' | translate}}</th>
<th>{{'BOOKINGS.children' | translate:params}}</th>
<th [innerHTML]="'BOOKINGS.returnFlight' | translate"></th>
<th [title]="'BOOKINGS.bookingDate' | translate">#</th>
</tr>
</thead>
</table>

Die Datei de.json umfasst den Aufbau der Referenzen sowie deren Inhalt. Die Struktur können Entwickler selbst definieren. So auch die Verschachtelung in Untergruppen, die per Punktnotation im Template Verwendung findet. Im Beispiel trennt die Gruppe BOOKINGS die Tabelle klar ab. Das kann zur Übersichtlichkeit entscheidend beitragen:

{
"BOOKINGS": {
"from": "Von",
"to": "{{direction}} Nach",
"passengers": "Passagiere",
"children": "Davon Kinder ({{age}})",
"returnFlight": "<i>Rückflug</i>",
"bookingDate": "Tag der Buchung"
}
}

Was noch fehlt, ist das Objekt params, das BOOKINGS.to und BOOKINGS.children verwendet. Es kommt aus der Komponente, die das gezeigte HTML nutzt, und enthält die Werte direction und age. So können Entwickler die Daten der Übersetzung als Expression anhängen und sie im Tabellenkopf anzeigen:

@Component({
templateUrl: './booking-details.component.html'
})
export class BookingDetailsComponent {
[...]
params = {
direction: '=>',
age: '<12'
};
}

Angular bietet über die Pipes i18nPlural und i18nSelect die gleichen Funktionen wie im ersten Teil unter "Dynamische Übersetzungen" gezeigt. Die Syntax bleibt identisch. Um sie mit ngx-translate zu verbinden, ist zuerst die Komponente zu erweitern, um den Pipes die benötigte Syntax übergeben zu können.

Der TranslateService, der im letzten Abschnitt zum Einrichten von Sprachen zum Einsatz kam, kann auch Übersetzungen laden. So lässt sich der definierte JSON-Bereich BOOKINGS über die Methode get laden und dem Attribut bookingTranslation anhängen (Pfeile im Listing weisen lediglich auf formatbedingte Zeilenumbrüche hin):

@Component({
templateUrl: './booking-details.component.html'
})
export class BookingDetailsComponent implements OnInit {
bookingTranslation;

constructor(private translate: TranslateService) {
}

ngOnInit() {
this.translate.get('BOOKING2195155S').subscribe(res => ↲
this.bookingTranslation = res);
}
}

Beide Pipes nutzen die gleiche Syntax wie im Abschnitt "Dynamische Übersetzungen", somit lässt sich die Datei de.json wie folgt erweitern:

{
"BOOKINGS": {
"passengerPlural": {
"=1": "Passagier",
"other": "Passagiere"
},
"childrenPlural": {
"=0": "Keine Kinder",
"=1": "Ein Kind",
"=2": "Zwei Kinder",
"other": "Mehr als zwei Kinder"
},
"returnFlightSelect": {
"yes": "Ja",
"no": "Nein"
},
"dateFormat": "d.M.y"
}
}

i18nPlural und i18nSelect nutzen nun die vorbereiteten Daten zur Übersetzung der Tabelle, indem sie diese den Pipes anhängen. Dabei kommt das zuvor in der Komponente befüllte Attribut bookingTranslation zum Einsatz. Damit ist außerdem die Übergabe eines Formatstrings an die Pipe date möglich:

<table class="table table-striped">
<tbody *ngIf="bookingTranslation">
<tr *ngFor="let booking of bookings">
<td>{{booking.from}}</td>
<td>{{booking.to}}</td>
<td>{{'BOOKINGS.passengers' | translate:booking}} {{ booking.passengers↲
| i18nPlural:bookingTranslation.passengerPlural }}</td>
<td>{{booking.children | ↲
i18nPlural:bookingTranslation.childrenPlural}}</td>
<td>{{booking.returnFlight |↲
i18nSelect:bookingTranslation.returnFlightSelect}}</td>
<td>{{booking.date | date:bookingTranslation.dateFormat}}</td>
</tr>
</tbody>
</table>

Die erste Sprache beziehungsweise Region wurde erfolgreich in eine JSON-Datei ausgelagert und dazu genutzt, eine Tabelle mit Flugdaten zu übersetzen. Es existiert mit der Datei en.json jedoch eine weitere Übersetzung. Um von der Standardsprache Deutsch auf Englisch zu wechseln, erhält das Template zwei Buttons:

<button (click)="selectLang('de')">DE</button>
<button (click)="selectLang('en')">EN</button>
<table>
[...]
</table>

Sie rufen bei Betätigung die Methode selectLang, die lediglich eine Anforderung der Methode use des TranslateService enthält, in der Komponente auf. Die Übergabe der gewünschten Sprach-ID reicht aus, um die Sprache umzustellen.

Die Übersetzungen im Tabellenkopf würden jetzt korrekt angezeigt. Anders sieht es dort aus, wo das Attribut bookingTranslation genutzt wird, da es vom Wechsel nichts mitbekommen hat. Dagegen kann jedoch der Event Emitter onLangChange helfen, der TranslateService bereitstellt. Über das Observable erhält der Entwickler die Option, weitere Schritte durchzuführen, sobald die Sprache wechselt. So lässt sich etwa bookingTranslation erneuern:

@Component({
templateUrl: './booking-details.component.html'
})
export class BookingDetailsComponent implements OnInit {
[...]
selectLang(lang) {
this.translate.use(lang);
}

ngOnInit() {
this.translate.get('BOOKINGS').subscribe(res => ↲
this.bookingTranslation = res);

this.translate.onLangChange.subscribe((event: LangChangeEvent) => {
this.translate.get('BOOKINGS').subscribe(res => ↲
this.bookingTranslation = res);
});
}
}

Als Alternative zu der in Angular verwendeten Methode zeigt ngx-translate den klassischen Weg. Über selbstdefinierte Referenzen können Entwickler eine Übersetzung im JSON-Format aufbauen. Durch eine mitgelieferte Pipe sowie eine Direktive und einen Service erhalten sie die notwendigen Werkzeuge dafür. Ein Sprachwechsel zur Laufzeit ist ebenso möglich wie das Nutzen unterschiedlicher Datenquellen.

Dafür benötigt ngx-translate mehr Leistung während die Anwendung läuft und mehr Code im JavaScript. Das kann je nach Anwendung überhaupt keine Auswirkung beziehungsweise Relevanz haben, ist jedoch ein entscheidender Unterschied zu Angular selbst. Zudem bleiben die ursprünglichen Texte nicht in den Templates erhalten. An ihrer Stelle kommen selbst definierte Referenzen als Platzhalter zum Einsatz, weshalb auch zwei Sprachdateien notwendig sind, um das im ersten Teil gezeigte Beispiel zu wiederholen.

Ob über Angular-Bordmittel oder mit ngx-translate, beide Ansätze haben ihre Berechtigung und sollten somit nicht mit gut oder schlecht bewertet werden.

Daniel Schwab
arbeitet bei Infonova GmbH als Frontend-Architekt. Dort beschäftigt er sich mit der Konzeption und Entwicklung von webbasierten Anwendungen sowie deren Integration im Enterprise-Umfeld. Als Co-Autor im aktuellen Buch [3] "Angular: Plattform für moderne Webanwendungen" gilt sein Augenmerk insbesondere der neuen Version des populären Google-Frameworks.
(jul [4])


URL dieses Artikels:
https://www.heise.de/-3722374

Links in diesem Artikel:
[1] https://www.heise.de/ratgeber/Internationalisierung-mit-Angular-Bordmitteln-3703621.html
[2] https://www.heise.de/ratgeber/Internationalisierung-mit-Angular-Bordmitteln-3703621.html
[3] http://angular.at/
[4] mailto:jul@heise.de