Lokalisierung in .NET MAUI 10: Mehrsprachige Apps mit .resx, RTL und Kultur-Switching
Lokalisierung in .NET MAUI 10 mit .resx, FlowDirection und dynamischem Sprach-Switching ohne Neustart. Praxisanleitung mit RTL, Pluralen, iOS/Android-Metadaten und Pseudo-Lokalisierung im CI.
Lokalisierung in .NET MAUI 10 funktioniert über sprachspezifische .resx-Ressourcendateien, die zur Laufzeit anhand von CultureInfo.CurrentUICulture aufgelöst werden, kombiniert mit FlowDirection-Bindings für RTL-Sprachen wie Arabisch oder Hebräisch. Wer mehrsprachige Apps in den Store bringen will, braucht aber ehrlich gesagt mehr als nur ein paar Übersetzungen: dynamisches Sprachumschalten ohne App-Neustart, korrekte Plural- und Datumsformatierung, Pseudo-Lokalisierung im Test und eine sinnvolle Fallback-Strategie sind die Stellen, an denen Apps in der Praxis scheitern. In diesem Leitfaden zeige ich dir jeden Schritt mit Code, den ich in drei produktiven MAUI-Apps tatsächlich eingesetzt habe.
.NET MAUI 10 nutzt das Standard-.NET-Lokalisierungsmodell mit .resx-Dateien und ResourceManager. Eigene Frameworks sind meist unnötig.
Für RTL-Unterstützung reicht FlowDirection="{Binding FlowDirection}" auf der App-Wurzel, aber nur, wenn alle Custom-Layouts es weiterreichen.
Dynamisches Sprachumschalten ohne Neustart erfordert ein INotifyPropertyChanged-basiertes LocalizedString-Markup-Extension-Pattern.
Plural-Regeln (1 Datei, 2 Dateien, 0 Dateien) gehören in PluralRules aus Microsoft.Extensions.Localization, nicht in if/else-Ketten.
Pseudo-Lokalisierung ([!!Ëñglïsh!!]) im CI-Build deckt abgeschnittene Strings vor dem ersten Release auf.
iOS und Android brauchen zusätzlich native Metadaten: CFBundleLocalizations in Info.plist und resources.xml-Ordner pro Sprache.
Warum Lokalisierung in MAUI schwerer ist, als sie aussieht
In den meisten Tutorials wirkt Lokalisierung trivial: eine .resx-Datei pro Sprache, eine Markup-Extension, fertig. Ich habe das in drei Produktiv-Apps ausgerollt, und jedes Mal kam die Komplexität an einer anderen Stelle hoch. Bei der ersten App war es das dynamische Umschalten ohne Neustart. Bei der zweiten die RTL-Unterstützung mit Custom-Controls, die FlowDirection einfach nicht weiterreichten. Bei der dritten waren es Plural-Regeln für Polnisch und Russisch, die mit naiver String-Interpolation einfach nicht funktionierten.
Die Lage 2026 ist deutlich besser als zu Xamarin.Forms-Zeiten. Mit der offiziellen Lokalisierungsdokumentation für .NET MAUI und Microsoft.Extensions.Localization hat Microsoft das Modell so weit angeglichen, dass man dieselben Mechanismen wie in ASP.NET Core verwenden kann. Aber: MAUI ist UI-Framework und Plattform-Bridge zugleich. Das bedeutet, dass viele Lokalisierungs-Effekte nicht im Code, sondern im nativen Layout-System landen, und genau dort liegen die Fallstricke.
Wenn du von Xamarin.Forms migrierst, lohnt sich vorab ein Blick auf den Xamarin.Forms zu MAUI Migrationsleitfaden, weil sich das Resource-Generator-Verhalten (PublicResXFileCodeGenerator) und die Custom-Renderer-Migration darauf auswirken, wie deine Übersetzungen in der neuen App ankommen.
Schritt 1: .resx-Ressourcen einrichten
Lege im Projekt einen Ordner Resources/Strings an. Pro Sprache eine Datei, die invariante als Basis:
AppResources.resx ist der Fallback, in der Regel Englisch
AppResources.de.resx für Deutsch
AppResources.ar.resx für Arabisch (RTL)
AppResources.pl.resx für Polnisch (komplexe Plurale)
Wichtig ist der Custom Tool: Für die Basisdatei setzt du PublicResXFileCodeGenerator, damit eine öffentliche, stark typisierte Klasse generiert wird. Die übrigen Sprachdateien brauchen keinen Code-Generator, sie liefern nur die Werte. Trägt man dort versehentlich auch einen Generator ein, kollidieren die Klassen beim Build (genau das habe ich mir in einem frühen Projekt antrainiert, und es kostet jedes Mal ein paar Minuten Such-Frust).
Die NeutralLanguage-Angabe ist nicht kosmetisch. Ohne sie versucht MAUI auch englische Strings über Satellite-Assemblies aufzulösen, was bei AOT-Builds zu fehlenden Resources führt.
Schritt 2: Strings im XAML binden
Es gibt zwei verbreitete Wege, Strings aus dem Resource-Manager im XAML zu nutzen: eine statische x:Static-Referenz oder eine Markup-Extension. Letztere ist Pflicht, sobald du Sprachen zur Laufzeit wechseln willst, ohne die App neu zu starten.
So sieht die einfache Variante aus, wenn ein Neustart akzeptabel ist:
Und so die produktionsreife Variante mit eigener Markup-Extension, die INotifyPropertyChanged bedient:
[ContentProperty(nameof(Key))]
public class TranslateExtension : IMarkupExtension<BindingBase>
{
public string Key { get; set; } = string.Empty;
public BindingBase ProvideValue(IServiceProvider serviceProvider) =>
new Binding
{
Mode = BindingMode.OneWay,
Path = $"[{Key}]",
Source = LocalizationManager.Instance
};
object IMarkupExtension.ProvideValue(IServiceProvider sp) => ProvideValue(sp);
}
Im XAML wird daraus ein angenehmes Text="{loc:Translate WelcomeTitle}". Der LocalizationManager ist ein Singleton, das die Kultur hält und bei Änderung PropertyChanged für den Indexer feuert. Das Detail folgt im nächsten Abschnitt.
Schritt 3: Wie kann ich die Sprache zur Laufzeit ändern?
Das ist die Frage, die mir Junior-Entwickler am häufigsten stellen, und der Punkt, an dem Stack-Overflow-Antworten verlässlich falsch liegen. Es reicht nicht, einfach CultureInfo.CurrentUICulture zu setzen. Bestehende Bindings sind dann längst aufgelöst. Du brauchst eine Quelle, die UI-seitig ein PropertyChanged-Signal auslöst.
public sealed class LocalizationManager : INotifyPropertyChanged
{
public static LocalizationManager Instance { get; } = new();
private CultureInfo _culture = CultureInfo.CurrentUICulture;
public CultureInfo Culture
{
get => _culture;
set
{
if (Equals(_culture, value)) return;
_culture = value;
CultureInfo.DefaultThreadCurrentCulture = value;
CultureInfo.DefaultThreadCurrentUICulture = value;
Thread.CurrentThread.CurrentUICulture = value;
AppResources.Culture = value;
// Index-Notify: triggert Re-Binding aller [Key]-Pfade
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs("Item[]"));
FlowDirection = value.TextInfo.IsRightToLeft
? FlowDirection.RightToLeft
: FlowDirection.LeftToRight;
}
}
public FlowDirection FlowDirection { get; private set; }
public string this[string key] =>
AppResources.ResourceManager.GetString(key, _culture) ?? $"!{key}!";
public event PropertyChangedEventHandler? PropertyChanged;
}
Der entscheidende Trick ist "Item[]" als PropertyName. Damit teilt WPF/MAUI-Binding allen indexierten Bindings auf [Key] mit, dass sich der Wert geändert hat. Ich habe das in drei Apps so eingesetzt und es funktioniert in MAUI 10 mit dem aktuellen Binding-Compiler problemlos.
Den aktuellen Sprachcode persistierst du am besten in Preferences und liest ihn beim App-Start ein. Ehrlich gesagt: bei .NET MAUI 10 mit Native AOT lade ich die Kultur explizit in MauiProgram.CreateMauiApp(), weil der Standard-CultureInfo manchmal auf en-US hängenbleibt, selbst wenn das Gerät auf Deutsch steht. Ein bekannter Fallstrick.
Schritt 4: RTL-Sprachen mit FlowDirection korrekt unterstützen
RTL ist mehr als nur Text rechtsbündig. Es kehrt das gesamte Layout um: HorizontalStackLayout beginnt rechts, Pfeile zeigen invertiert, Scroll-Indikatoren erscheinen links. .NET MAUI unterstützt das nativ über die FlowDirection-Property, aber wirklich zuverlässig nur dann, wenn du sie auf der App-Wurzel setzt und Custom-Controls die Eigenschaft korrekt erben.
Wichtig: Wenn du Custom-Renderer aus Xamarin-Zeiten oder eigene Handler-Implementierungen benutzt, prüfe deren MapFlowDirection-Mapping. Ich habe in einem Migrationsprojekt zwei Tage gesucht, bis ich gefunden hatte, dass ein selbst geschriebener ChipHandler die Property einfach ignorierte. Zwei Tage. Mehr Hintergrund zu Handler-Migration findet sich in unserem Migrationsleitfaden.
Achte zusätzlich auf Icons: Pfeile, Zurück-Buttons, Slider-Tracks müssen unter RTL gespiegelt werden. Dafür bietet MAUI keine Auto-Spiegelung. Du musst entweder zwei Asset-Versionen pflegen oder ScaleX="-1" bedingt setzen.
FlowDirection in Custom-Layouts weitergeben
Wenn du eigene Layout-Klassen schreibst, überschreibe OnHandlerChanged und gib FlowDirection über EffectiveFlowDirection an Kindelemente weiter, sonst bleiben sie LTR und brechen das Layout.
Plurale, Datums- und Zahlenformatierung
"1 Datei" vs. "2 Dateien" sieht im Deutschen einfach aus. Aber: Polnisch hat fünf Plural-Klassen, Russisch vier, Arabisch sechs. Wer hier mit if (count == 1) ... else ... arbeitet, produziert für jede dieser Sprachen falsche Strings. Der saubere Weg geht über das ICU MessageFormat oder, wenn du leichtgewichtig bleiben willst, über die Plural-Helfer in Microsoft.Extensions.Localization.
Für Datums- und Zahlenformatierung reicht CultureInfo, aber nur, wenn du sie konsistent durchreichst:
Ein häufiger Fehler: JSON-Daten vom Server kommen in en-US-Format zurück, du parst sie mit InvariantCulture, formatierst sie aber später mit der UI-Kultur. Das ist korrekt, aber es funktioniert nur, wenn du das Parsing im Datenlayer wirklich invariant hältst. Ich erzwinge das per JsonSerializerOptions und prüfe es im Build mit einem kleinen Analyzer.
Native Plattform-Metadaten für iOS und Android
Auch wenn deine .resx-Dateien perfekt sind: iOS und Android entscheiden anhand nativer Metadaten, ob deine App im jeweiligen Store als "lokalisiert" gilt und welche Sprache sie für Systemdialoge nutzt.
iOS: In Platforms/iOS/Info.plist trägst du CFBundleLocalizations als Array ein, mit allen Codes (en, de, ar, pl) sowie CFBundleDevelopmentRegion für die Standard-Sprache. Ohne diese Einträge zeigt iOS die App-Beschreibung im App Store nur auf Englisch an, selbst wenn der Code übersetzt ist.
Android: Lege Platforms/Android/Resources/values-de/strings.xml, values-ar/strings.xml usw. an, mit mindestens dem App-Namen. Sonst sieht der Nutzer im Launcher den englischen Namen, obwohl die App komplett übersetzt ist. Für Play-Store-Listings ist das per-language-Listing in der Google Play Console obligatorisch.
Wenn du den Veröffentlichungsprozess sauber aufsetzt, schau auch in unseren Praxisleitfaden zur App-Store-Veröffentlichung. Dort steht, wo genau die lokalisierten Metadaten im Build-Prozess auftauchen müssen.
Pseudo-Lokalisierung im CI-Pipeline
Pseudo-Lokalisierung ist der unterschätzteste Trick im Lokalisierungs-Toolkit, und ich predige das auf jedem Code-Review. Die Idee: Vor dem ersten Release generierst du automatisch eine "Sprache" wie qps-ploc, die jeden String mit Akzenten umschreibt und um etwa 30 % verlängert ("Sign in" wird "[!!Šïğñ ïñ !!]").
Das deckt drei Klassen von Bugs auf, bevor ein echter Übersetzer einsteigt: abgeschnittene Strings in zu engen UI-Containern, hartkodierte englische Strings, die in keiner .resx liegen, und kaputte Encodings in Build-Pipelines. Die Pseudo-.resx kann automatisiert aus der Basis erzeugt werden. Ich verwende dafür ein kleines T4-Template im CI, das ich beim ersten Mal in einer halben Stunde geschrieben habe und seitdem in jedes neue Projekt mitnehme.
Typische Fehler aus der Praxis
Eine kurze Liste von Dingen, die ich produktiv gesehen habe, jedes Mal Stunden Debugging:
Doppelte .resx-Code-Generatoren: Alle Sprachen haben PublicResXFileCodeGenerator, Build kollidiert. Nur die Basis braucht den Generator.
Kultur nur in App.xaml.cs gesetzt: Background-Threads (HttpClient, JsonSerializer) verwenden Default-Kultur. Immer DefaultThreadCurrentUICulture verwenden.
FlowDirection nicht weitergereicht: Custom-Handler oder MVVM-Komponenten ignorieren EffectiveFlowDirection, Layout bricht in RTL.
String-Konkatenation statt Platzhalter: "Hallo, " + name ist für RTL-Sprachen falsch. Immer {0} in der Resource, dann string.Format mit CultureInfo.
Hartkodierte Zahl/Datum-Formate: $"{price:0.00}" ignoriert das Kommazeichen-Trennzeichen der Kultur. Nutze ToString("C", culture).
Fehlende Fallback-Strategie: Resource-Key in einer Sprache vergessen, und schon erscheint !Key! im UI. Logge fehlende Keys im Debug-Build.
Wer Lokalisierung in einer realistisch großen App umsetzt, kommt nicht ohne automatisierte Tests aus. Wie ich UI-Strings in CI prüfe (Snapshot-Tests pro Sprache mit Appium), ist im Teststrategie-Leitfaden für MAUI beschrieben. Für die Performance-Auswirkungen vieler Satellite-Assemblies beim Startup lohnt ein Blick in den Performance-Leitfaden. Native AOT scheidet bei zu vielen ungenutzten Ressourcen aggressiv aus, was zu verzögerter Sprachauflösung führen kann.
Wer tiefer in die Format-Mechanik einsteigen will, sollte die CultureInfo-Referenz lesen. Dort sind die Subtleties von InvariantCulture vs. InstalledUICulture erklärt, die regelmäßig zu Bugs führen.
Häufig gestellte Fragen
Welche Dateiformate werden für Übersetzungen in .NET MAUI verwendet?
Der Standard sind .resx-Dateien pro Sprache, die zur Buildzeit in Satellite-Assemblies kompiliert werden. Alternativen wie .po/.json sind möglich, erfordern aber Custom-Loader und verlieren die Code-Generierung.
Kann ich die Sprache in MAUI ohne App-Neustart ändern?
Ja, mit einem LocalizationManager-Singleton, das INotifyPropertyChanged über den Indexer "Item[]" feuert. Bindings auf [Key] werden dann automatisch neu aufgelöst, ohne Page-Neuaufbau.
Wie unterstütze ich RTL-Sprachen wie Arabisch oder Hebräisch?
Setze FlowDirection auf der Application-Wurzel via Binding an deinen LocalizationManager. Achte darauf, dass Custom-Handler die Property im Mapping korrekt weiterreichen und Icons gespiegelte Varianten haben.
Warum zeigt mein iOS-Build nur englische Strings im App Store an?
Du musst CFBundleLocalizations in der Info.plist mit allen unterstützten Sprachcodes pflegen. Ohne diesen Eintrag erkennt der App Store die App als nur-Englisch, selbst wenn die .resx-Dateien vollständig sind.
Wie teste ich Übersetzungen vor der Beauftragung eines Übersetzers?
Generiere im CI eine Pseudo-Lokalisierung (z.B. qps-ploc), die Strings mit Akzenten umschreibt und um etwa 30 % verlängert. So findest du abgeschnittene UI-Elemente und vergessene hartkodierte Strings, bevor echte Übersetzungen Geld kosten.
Praxisleitfaden 2026 für Push-Notifications in .NET MAUI 10: Firebase Cloud Messaging, APNs, Token-Management, ASP.NET Core Backend, Berechtigungen, Troubleshooting und DSGVO-konforme Best Practices.
Shell-Routing, Tabs, Flyout und MVVM-Navigation in .NET MAUI 10 richtig umsetzen — mit GoToAsync, IQueryAttributable, modaler Navigation und INavigationService. Praxisleitfaden mit Code-Beispielen.
Bauen Sie eine robuste Offline-First-App mit .NET MAUI: SQLite mit EF Core, Konnektivitätsüberwachung, persistente Request-Queues, Sync-Engine mit Push/Pull, drei Konfliktlösungsstrategien und Hintergrundsynchronisierung auf Android und iOS.