Flutter Localization (i18n): Mehrsprachige Apps entwickeln
# 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
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^class="code-number">0.19.class="code-number">0
flutter:
generate: trueDas 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:
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
nullable-getter: falseSchritt 3: ARB-Dateien erstellen
ARB-Dateien sind JSON-basiert und dienen als zentrale Quelle fuer alle Uebersetzungen. Erstellen Sie `lib/l10n/app_en.arb`:
{
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:
{
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
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
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`:
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:
{
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`:
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`:
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:
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:
{
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:
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.
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.
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
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:
Semantics(
label: AppLocalizations.of(context).closeButtonLabel,
child: IconButton(icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context)),
)Lokalisierung testen
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
Was ist Flutter? Ein vollständiger Leitfaden für Einsteiger
Erfahren Sie, was Flutter ist, wie es funktioniert und warum moderne Produktteams darauf setzen. Entdecken Sie Dart, Widget-Architektur und plattformübergreifende Entwicklung.
Flutter Navigation: Skalierbares Routing mit go_router
Implementieren Sie moderne Flutter-Navigation mit go_router inklusive Nested Routes, Redirects und Deep Links.
Flutter Accessibility (a11y): Apps für alle Nutzer
Setzen Sie Barrierefreiheit in Flutter um: Semantics, Kontrast, Textskalierung und tastaturfreundliche Bedienung.
Haben Sie ein Flutter-Projekt?
Ich entwickle hochleistungsfähige Flutter-Anwendungen für iOS, Android und Web.
Kontakt aufnehmen