Flutter Localization (i18n): Mehrsprachige Apps entwickeln

12 Min. Lesezeit9. Februar 2026Aktualisiert: 9. März 2026
Flutter localizationFlutter i18nFlutter multi languageFlutter intlFlutter ARBFlutter localeFlutter translationFlutter internationalization

# Flutter Localization (i18n): Mehrsprachige Apps entwickeln

Mehrsprachigkeit ist kein optionales Feature — sie ist eine grundlegende Produktanforderung fuer jede App mit globalem Anspruch. In Apps, die ich fuer mehr als 10 Maerkte ausgeliefert habe, hat eine von Anfang an saubere i18n-Architektur wochenlange Nacharbeiten erspart. Dieser Leitfaden fuehrt durch das komplette Flutter-Lokalisierungs-Setup: von der Grundkonfiguration ueber RTL-Unterstuetzung bis hin zu Uebersetzungs-Workflows fuer Teams.

Lokalisierung von Grund auf einrichten

Schritt 1: Abhaengigkeiten hinzufuegen

dart
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: ^class="code-number">0.19.class="code-number">0

flutter:
  generate: true

Das Flag `generate: true` weist Flutter an, aus Ihren ARB-Dateien automatisch Lokalisierungscode zu generieren.

Schritt 2: l10n-Konfiguration erstellen

Erstellen Sie `l10n.yaml` im Projektstammverzeichnis:

dart
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
nullable-getter: false

Schritt 3: ARB-Dateien erstellen

ARB-Dateien sind JSON-basiert und dienen als zentrale Quelle fuer alle Uebersetzungen. Erstellen Sie `lib/l10n/app_en.arb`:

dart
{
  class="code-string">"@@locale": class="code-string">"en",
  class="code-string">"appTitle": class="code-string">"My Application",
  class="code-string">"@appTitle": { class="code-string">"description": class="code-string">"The title of the application" },
  class="code-string">"welcomeMessage": class="code-string">"Welcome, {userName}!",
  class="code-string">"@welcomeMessage": {
    class="code-string">"placeholders": { class="code-string">"userName": { class="code-string">"type": class="code-string">"String", class="code-string">"example": class="code-string">"John" } }
  },
  class="code-string">"itemCount": class="code-string">"{count, plural, =class="code-number">0{No items} =class="code-number">1{class="code-number">1 item} other{{count} items}}",
  class="code-string">"@itemCount": {
    class="code-string">"placeholders": { class="code-string">"count": { class="code-string">"type": class="code-string">"num", class="code-string">"format": class="code-string">"compact" } }
  },
  class="code-string">"lastLogin": class="code-string">"Last login: {date}",
  class="code-string">"@lastLogin": {
    class="code-string">"placeholders": { class="code-string">"date": { class="code-string">"type": class="code-string">"DateTime", class="code-string">"format": class="code-string">"yMMMd" } }
  }
}

Dann `lib/l10n/app_de.arb` fuer Deutsch:

dart
{
  class="code-string">"@@locale": class="code-string">"de",
  class="code-string">"appTitle": class="code-string">"Meine Anwendung",
  class="code-string">"welcomeMessage": class="code-string">"Willkommen, {userName}!",
  class="code-string">"itemCount": class="code-string">"{count, plural, =class="code-number">0{Keine Eintraege} =class="code-number">1{class="code-number">1 Eintrag} other{{count} Eintraege}}",
  class="code-string">"lastLogin": class="code-string">"Letzte Anmeldung: {date}"
}

Schritt 4: MaterialApp konfigurieren

dart
import class="code-string">'package:flutter_localizations/flutter_localizations.dart';
import class="code-string">'package:flutter_gen/gen_l10n/app_localizations.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: const [
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: const [Locale(class="code-string">'en'), Locale(class="code-string">'tr'), Locale(class="code-string">'de')],
      locale: const Locale(class="code-string">'de'),
      home: const HomeScreen(),
    );
  }
}

Schritt 5: Uebersetzungen in Widgets verwenden

dart
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context);
    return Scaffold(
      appBar: AppBar(title: Text(l10n.appTitle)),
      body: Column(children: [
        Text(l10n.welcomeMessage(class="code-string">'Max')),
        Text(l10n.itemCount(class="code-number">5)),
        Text(l10n.lastLogin(DateTime.now())),
      ]),
    );
  }
}

Dynamischer Sprachwechsel

Nutzer sollten die Sprache wechseln koennen, ohne die App neu zu starten. Ein sauberes Muster mit `ChangeNotifier`:

dart
class LocaleProvider extends ChangeNotifier {
  Locale _locale = const Locale(class="code-string">'de');
  Locale get locale => _locale;
  void setLocale(Locale locale) {
    _locale = locale;
    notifyListeners();
  }
}

class=class="code-string">"code-comment">// In der MaterialApp:
Consumer<LocaleProvider>(
  builder: (context, provider, _) => MaterialApp(
    locale: provider.locale,
    localizationsDelegates: AppLocalizations.localizationsDelegates,
    supportedLocales: AppLocalizations.supportedLocales,
    home: const HomeScreen(),
  ),
)

Pluralisierung und Geschlecht

Das ICU-Nachrichtenformat behandelt Pluralisierung elegant. Flutter unterstuetzt `plural`, `select` und `gender` direkt in ARB-Dateien:

dart
{
  class="code-string">"notificationCount": class="code-string">"{count, plural, =class="code-number">0{Keine Benachrichtigungen} =class="code-number">1{class="code-number">1 Benachrichtigung} other{{count} Benachrichtigungen}}",
  class="code-string">"@notificationCount": { class="code-string">"placeholders": { class="code-string">"count": { class="code-string">"type": class="code-string">"num" } } },
  class="code-string">"genderGreeting": class="code-string">"{gender, select, male{Sehr geehrter Herr {name}} female{Sehr geehrte Frau {name}} other{Guten Tag, {name}}}",
  class="code-string">"@genderGreeting": {
    class="code-string">"placeholders": { class="code-string">"gender": { class="code-string">"type": class="code-string">"String" }, class="code-string">"name": { class="code-string">"type": class="code-string">"String" } }
  }
}

Sprachen wie Polnisch, Arabisch und Russisch haben komplexe Pluralregeln jenseits von Singular und Plural. Das ICU-Format verwaltet `zero`, `one`, `two`, `few`, `many` und `other` automatisch basierend auf dem Locale.

Datums- und Zahlenformatierung

Formatieren Sie Daten und Zahlen niemals manuell. Verwenden Sie immer `intl`:

dart
import class="code-string">'package:intl/intl.dart';

final dateFormatter = DateFormat.yMMMd(class="code-string">'de');
print(dateFormatter.format(DateTime.now())); class=class="code-string">"code-comment">// "class="code-number">9. Maer. class="code-number">2026"

final currencyFormatter = NumberFormat.currency(locale: class="code-string">'de', symbol: class="code-string">'\u20ac');
print(currencyFormatter.format(class="code-number">1234.56)); class=class="code-string">"code-comment">// "class="code-number">1.234,class="code-number">56 \u20ac"

final compactFormatter = NumberFormat.compact(locale: class="code-string">'de');
print(compactFormatter.format(class="code-number">1500000)); class=class="code-string">"code-comment">// "class="code-number">1,class="code-number">5 Mio."

RTL-Sprachunterstuetzung

Die Unterstuetzung von Rechts-nach-links-Sprachen wie Arabisch, Hebraeisch und Persisch erfordert besondere Aufmerksamkeit beim Layout.

Flutter handhabt die Textrichtung automatisch ueber `flutter_localizations`. Die Grundregel: Ersetzen Sie hartcodierte `left`/`right`-Werte durch `start`/`end`:

dart
class=class="code-string">"code-comment">// Falsch - funktioniert nicht bei RTL
Padding(padding: EdgeInsets.only(left: class="code-number">16.0))

class=class="code-string">"code-comment">// Richtig - respektiert die Textrichtung
Padding(padding: EdgeInsetsDirectional.only(start: class="code-number">16.0))

Einige Icons muessen in RTL-Layouts gespiegelt werden:

dart
Widget buildArrowIcon(BuildContext context) {
  final isRtl = Directionality.of(context) == TextDirection.rtl;
  return Transform.flip(flipX: isRtl, child: const Icon(Icons.arrow_forward));
}

In Apps, die ich fuer mehr als 10 Maerkte ausgeliefert habe, teste ich grundsaetzlich immer mit Arabisch — auch wenn die App offiziell kein Arabisch unterstuetzt. So lassen sich richtungsabhaengige Layout-Fehler zuverlaessig aufdecken.

Uebersetzungs-Workflow fuer Teams

ARB-Dateien organisieren

Ich empfehle eine domaenbasierte Schluesselbenennnung, damit Projekte uebersichtlich bleiben:

dart
{
  class="code-string">"auth_loginButton": class="code-string">"Anmelden",
  class="code-string">"auth_signupButton": class="code-string">"Registrieren",
  class="code-string">"profile_editTitle": class="code-string">"Profil bearbeiten",
  class="code-string">"settings_languageLabel": class="code-string">"Sprache",
  class="code-string">"error_networkTimeout": class="code-string">"Verbindung abgelaufen. Bitte versuchen Sie es erneut."
}

Integration mit Uebersetzungsdiensten

In Produktions-Apps verwende ich einen dieser Workflows:

  • **Manuelle Uebersetzung**: ARB-Dateien exportieren, an Uebersetzer senden, zurueckimportieren. Funktioniert bei kleinen Projekten mit weniger als 100 Texten.
  • **Localizely / Crowdin / Phrase**: Vorlagen-ARB hochladen, Uebersetzer arbeiten in einer Web-Oberflaeche, fertige ARBs herunterladen. Diese Tools verwalten Pluralregeln und markieren fehlende Uebersetzungen.
  • **CI/CD-Integration**: Eine GitHub Action, die bei jedem PR auf fehlende Schluessel prueft.
  • Fehlende Uebersetzungen erkennen

    Ein einfaches Dart-Skript kann alle ARB-Dateien parsen, die Schluessel mit der Vorlage abgleichen und die CI fehlschlagen lassen, wenn Uebersetzungen fehlen. In jedem Projekt halte ich ein `scripts/check_translations.dart` bereit, das `app_en.arb` als Referenz nimmt und alle anderen ARB-Dateien dagegen prueft.

    Haeufige i18n-Fehler

    Fehler 1: Hartcodierte Texte

    Jeder nutzersichtbare Text muss durch das Lokalisierungssystem laufen — einschliesslich Fehlermeldungen, Tooltips und Barrierefreiheits-Labels.

    dart
    class=class="code-string">"code-comment">// Falsch
    Text(class="code-string">'Willkommen zurueck!')
    class=class="code-string">"code-comment">// Richtig
    Text(AppLocalizations.of(context).welcomeBack)

    Fehler 2: String-Verkettung

    Bauen Sie Saetze niemals durch Verkettung uebersetzter Fragmente zusammen. Die Wortstellung variiert zwischen Sprachen erheblich.

    dart
    class=class="code-string">"code-comment">// Falsch - Wortstellung funktioniert in anderen Sprachen nicht
    Text(l10n.sie + class="code-string">' ' + l10n.haben + class="code-string">' ' + count.toString() + class="code-string">' ' + l10n.nachrichten)
    class=class="code-string">"code-comment">// Richtig - einen parametrisierten Text verwenden
    Text(l10n.messageCount(count))

    Fehler 3: Kein Fallback-Locale

    Wenn eine Uebersetzung fehlt und es kein Fallback gibt, kann die App abstuerzen oder rohe Schluessel anzeigen. Flutter faellt automatisch auf das erste Locale in `supportedLocales` zurueck — platzieren Sie daher Ihre Hauptsprache immer an erster Stelle.

    Fehler 4: Textexpansion ignorieren

    Deutsche Texte sind typischerweise 30 % laenger als englische. Arabisch kann noch breiter sein. Testen Sie Ihre Oberflaeche immer mit langen Uebersetzungen. Waehrend der Entwicklung verwende ich Pseudo-Lokalisierung, um die Expansion zu simulieren.

    Fehler 5: Hartcodierte Datums- und Zahlenformate

    dart
    class=class="code-string">"code-comment">// Falsch - nur ein bestimmtes Format
    Text(class="code-string">'${date.day}.${date.month}.${date.year}')
    class=class="code-string">"code-comment">// Richtig - locale-bewusst
    Text(DateFormat.yMd(Localizations.localeOf(context).toString()).format(date))

    Fehler 6: Barrierefreiheit vergessen

    Screenreader sind auf lokalisierte `Semantics`-Labels angewiesen. Wenn Sie nur sichtbaren Text lokalisieren, erhalten sehbehinderte Nutzer in anderen Sprachen eine fehlerhafte Erfahrung:

    dart
    Semantics(
      label: AppLocalizations.of(context).closeButtonLabel,
      child: IconButton(icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context)),
    )

    Lokalisierung testen

    dart
    testWidgets(class="code-string">'zeigt deutsche Willkommensnachricht an', (tester) async {
      await tester.pumpWidget(MaterialApp(
        locale: const Locale(class="code-string">'de'),
        localizationsDelegates: AppLocalizations.localizationsDelegates,
        supportedLocales: AppLocalizations.supportedLocales,
        home: const HomeScreen(),
      ));
      expect(find.text(class="code-string">'Willkommen, Max!'), findsOneWidget);
    });

    Fazit

    Ein solides i18n-Setup reduziert den Aufwand bei der Erschliessung neuer Maerkte erheblich. Die fruehe Investition in eine saubere ARB-Struktur, locale-bewusste Formatierung und einen durchdachten Uebersetzungs-Workflow zahlt sich vielfach aus. In Apps, die ich fuer mehr als 10 Maerkte ausgeliefert habe, waren es die Teams, die Lokalisierung als erstklassiges Architekturthema behandelten, die problemlos in neue Regionen skalieren konnten — ohne Notfall-Hotfixes.

    Gerne unterstuetze ich Sie bei der Umsetzung einer skalierbaren Lokalisierungs-Pipeline — vom ARB-Setup bis zum CI-integrierten Uebersetzungs-Workflow.

    Verwandte Artikel

    Haben Sie ein Flutter-Projekt?

    Ich entwickle hochleistungsfähige Flutter-Anwendungen für iOS, Android und Web.

    Kontakt aufnehmen