Angular bringt Internationalisierung von Haus aus mit, über das Paket @angular/localize. Du markierst die Texte in Templates mit dem Attribut i18n und in TypeScript mit $localize. Die CLI extrahiert sie in eine Übersetzungsdatei (XLIFF, JSON…), und beim Build erzeugt sie pro Sprache eine eigene Variante der Anwendung, in der die Texte bereits ersetzt sind. Das Ergebnis ist so schnell wie die unübersetzte Anwendung: kein Key-Lookup zur Laufzeit.
Neben Texten bedeutet i18n auch Plurale und Formatierung. „2 Dateien“, aber „20 Dateien“ heißt auf Rumänisch „20 de fișiere“; 1,499.50 im Englischen und 1.499,50 im Rumänischen; das Datum mit dem Namen des Wochentags in der Sprache des Benutzers. Hier kommen ICU-Ausdrücke und Locale-Daten ins Spiel.
Texte markieren
ng add @angular/localize installiert das Paket und lädt @angular/localize/init als Polyfill. Das Attribut i18n markiert ein Element; sein optionaler Wert hat die Form meaning|description@@id. Die Bedeutung (meaning) und die Beschreibung helfen den Übersetzenden. Eine eigene ID (@@cart.title) verhindert, dass die Übersetzung verloren geht, wenn du den Quelltext änderst. Für Attribute verwendest du i18n-alt, i18n-title usw. Im Code ist $localize ein Tagged Template mit derselben Syntax, und die Interpolationen erhalten Namen (${count}:count:).
ng add @angular/localize
# fügt @angular/localize/init zu den Polyfills und die Typen zur tsconfig hinzu — ohne das starten i18n und $localize nichtICU: plural und select
ICU-Ausdrücke wählen den Text anhand eines Werts aus. plural nutzt die CLDR-Kategorien der Sprache (LOCALE_ID) sowie exakte Treffer (=0). select wählt anhand eines Strings. Rumänisch hat drei Kategorien:
onefür 1;fewfür 0, 2–19 und 101–119;otherfür den Rest (20, 100, 120…), wo „de“ erscheint: „20 de fișiere“ (20 Dateien).
Die Regel steckt in den Locale-Daten von Angular (@angular/common/locales/ro). ICU-Ausdrücke funktionieren in jedem Template, auch ohne Übersetzungen.
Du hast 2 Dateien hochgeladen als Editor.
CLDR-Kategorie für 2 im Rumänischen:few
Extraktion und Übersetzungsdateien
ng extract-i18n sammelt alle markierten Nachrichten. Standardmäßig schreibt es messages.xlf (XLIFF 1.2); mit --format kannst du auch XLIFF 2, XMB, JSON oder ARB wählen. Du kopierst die Datei für jede Sprache (messages.en.xlf), die Übersetzenden füllen die <target>-Elemente aus, und die IDs verknüpfen die Quelle mit ihrer Übersetzung.
ng extract-i18n --output-path src/locale # Standard: messages.xlf (XLIFF 1.2)
ng extract-i18n --format=xlf2 --output-path src/locale
ng extract-i18n --format=json --output-path src/localeBuild pro Sprache vs. Übersetzungen zur Laufzeit
Der Standardansatz ist zur Build-Zeit: In angular.json deklarierst du sourceLocale und locales, und mit "localize": true erzeugt die CLI einen Ordner pro Sprache (/ro/, /en/…). Der Server wählt den Ordner anhand der URL oder der Präferenz des Benutzers. Die Sprache zu wechseln bedeutet, zu einem anderen Ordner zu navigieren, also neu zu laden.
Die Alternative ist zur Laufzeit: ein einziger Build, und in main.ts lädst du die Übersetzungen mit loadTranslations() vor dem Bootstrap. Auch hier bedeutet ein Sprachwechsel einen Reload, weil die mit i18n markierten Texte in Templates beim Erstellen der Komponenten ausgewertet werden. Wenn du die Sprache ohne Reload wechseln musst, nimm eine Übersetzungsbibliothek für die Laufzeit.
{
"projects": {
"shop": {
"i18n": {
"sourceLocale": "ro",
"locales": {
"en": "src/locale/messages.en.xlf",
"de": "src/locale/messages.de.xlf"
}
},
"architect": {
"build": {
"options": { "localize": true }
}
}
}
}
}Dein Warenkorb
Du hast 3 Artikel im Warenkorb.
Die Nachrichten sind $localize-Aufrufe mit IDs (@@cart.title), übersetzt mit loadTranslations(). Dieses Beispiel hat eigene Buttons und folgt nicht der Sprache der Seite.
Das Beispiel wertet die $localize-Aufrufe im Code neu aus. Mit i18n markierte Texte in Templates ändern sich so nicht, weshalb echte Anwendungen die Seite neu laden, wenn sich die Sprache ändert.
Formatierung nach Locale
Die Pipes date, number, currency und percent (und die Funktionen formatDate, formatNumber…) nutzen LOCALE_ID. Für jede andere Sprache als en-US musst du die Daten mit registerLocaleData registrieren. Diese Seite tut das für ro, mit RON als Standardwährung (DEFAULT_CURRENCY_CODE). Alle Pipes akzeptieren auch einen expliziten locale-Parameter.
import { registerLocaleData } from '@angular/common';
import localeRo from '@angular/common/locales/ro';
import { DEFAULT_CURRENCY_CODE, LOCALE_ID } from '@angular/core';
registerLocaleData(localeRo); // erforderlich für jede andere Locale als en-US
export const appConfig: ApplicationConfig = {
providers: [
{ provide: LOCALE_ID, useValue: 'ro' }, // verwendet von date, number, currency, percent, ICU
{ provide: DEFAULT_CURRENCY_CODE, useValue: 'RON' },
],
};| Locale | Datum | Zahl | Währung (EUR) | Prozent |
|---|---|---|---|---|
ro | vineri, 9 octombrie 2026 | 1.234.567,89 | 1.499,50 € | 18,8 % |
en | Friday, October 9, 2026 | 1,234,567.89 | €1,499.50 | 18.8% |
de | Freitag, 9. Oktober 2026 | 1.234.567,89 | 1.499,50 € | 18,8 % |
ja | 2026年10月9日金曜日 | 1,234,567.89 | €1,499.50 | 18.8% |
Wie diese Seite die Sprache wechselt
Die Seite verwendet für ihre eigenen Texte kein Build-Zeit-i18n. Die Sprache der Oberfläche ist ein Signal: der Service LangStore (in core/i18n), mit lang() und den Werten 'en' oder 'ro'. Der Button in der Topbar ruft toggle() auf, was die Wahl in localStorage unter dem Schlüssel atlas-lang speichert. Beim Start wird der Schlüssel gelesen; fehlt er, ist die Sprache Englisch. Ein effect hält das Attribut lang des <html>-Elements synchron.
Der Wechsel lädt die Seite nicht neu: Texte werden neu ausgewertet, wenn sich das Signal ändert. Es gibt zwei Arten zweisprachiger Texte:
- Die Oberfläche (Topbar, Menüs, Suche) hat Keys in
messages.ts, typisiert mitMessageKey. Diet-Pipe übersetzt sie:{{ 'topic.minRead' | t: { n: 4 } }}. TypeScript verlangt, dass jeder Key in beiden Sprachen existiert. - Die Texte dieser Website liegen in Wörterbüchern, eine JSON-Datei pro Sprache (
i18n/en/…,i18n/ro/…,i18n/de/…). Jeder Text hat einen Schlüssel und wird mit dert-Pipe gelesen:{{ 'signals:title' | t }}. Die Pipe ist impure, daher wird sie bei einem Sprachwechsel neu ausgewertet.
Der Fließtext einer Seite steht in ihrem Template in beiden Sprachen (@if (lang.lang() === 'en')), und ihr Code liegt in zwei .snippets-Dateien. Das obige $localize-Beispiel ist ein anderer Mechanismus mit eigenen Buttons: Es folgt nicht der Sprache der Seite.
Wann ja, wann nein
- Öffentliche Anwendungen, SEO, Performance: Jede Sprache ist ein optimierter Build, ohne Keys zur Laufzeit.
- Sprachen, die zur Build-Zeit bekannt sind und sich selten ändern (die Präferenz im Konto, die URL
/en/). - Teams mit professionellen Übersetzenden, die in XLIFF arbeiten.
- Funktioniert gut mit SSR / Prerendering: eine Variante pro Sprache (siehe SSR).
- Ein Sprachwechsel ohne Reload ist eine Produktanforderung.
- Die Übersetzungen kommen aus einem CMS und ändern sich ohne Deployment.
- Dutzende Sprachen, und du willst keine Dutzende Builds.
- Kleine interne Anwendungen in einer einzigen Sprache: einfach
LOCALE_ID+ Pipes + ICU, ohne Extraktion.
Häufige Stolperfallen
- Texte verketten (
'You have ' + n + ' items'): Die Wortstellung unterscheidet sich zwischen Sprachen. Verwende eine einzelne Nachricht mit Platzhalter oder einen ICU-Ausdruck. - Plurale mit
n === 1 ? … : …: Im Rumänischen bekommst du „20 fișiere“ statt „20 de fișiere“. Verwende das ICUplural. - Nachrichten ohne eigene ID: Die generierte ID ändert sich bei jeder Korrektur des Textes, und die Übersetzung geht verloren.
- Fest eincodierte Formate (
'dd/MM/yyyy',toFixed(2) + ' lei'): Sie umgehen die Locale. Verwende die Pipes. - Nicht registrierte Locale:
LOCALE_IDaufroohneregisterLocaleDatawirft bei der ersten Pipe einen Fehler.
Spickzettel
MARKIEREN i18n="Bedeutung|Beschreibung@@id" · i18n-title="@@id" · $localize`:@@id:Text ${x}:name:`
ICU {n, plural, =0 {…} one {…} few {…} other {…}} · {x, select, a {…} other {…}}
Rumänisch: one = 1 · few = 0, 2–19, 101–119 · other = 20+, 120+ („20 de fișiere“ = 20 Dateien)
EXTRAKTION ng extract-i18n [--format=xlf|xlf2|xmb|json|arb] [--output-path src/locale]
BUILD angular.json: i18n.sourceLocale + i18n.locales · "localize": true → ein Bundle pro Sprache
RUNTIME loadTranslations() vor dem Bootstrap · clearTranslations() · neue Sprache = Reload
FORMATIERUNG registerLocaleData + LOCALE_ID + DEFAULT_CURRENCY_CODE · Pipes mit dem Parameter locale
ALTERNATIVE Übersetzungsbibliotheken zur Laufzeit (z. B. Transloco, ngx-translate) — Sprachwechsel ohne Reload