tva
← Insights

Flaggen im Widget: Warum ISO-Ländercodes besser sind als lokalisierte Namen

Der Fehler war subtil. Ein iOS-Widget zeigte auf Englisch den richtigen Ländernamen an, auf Deutsch jedoch das falsche Flaggen-Emoji. Mit französischen Regionaleinstellungen wurde bei einem anderen Land die falsche Flagge angezeigt. Die Flaggen waren nicht zufällig falsch – sie folgten einem Muster, das erst nach kurzem Hinsehen erkennbar wurde: Die Suche lieferte die Flagge des Landes, dessen Name in den aktuellen Regionaleinstellungen zufällig dem gespeicherten Anzeigenamen ähnelte.

Die Ursache war eine Ländersuche, die lokalisierte Ländernamen als Schlüssel eines Wörterbuchs verwendete. Namen, die in einer Sprache stabile Zeichenfolgen sind, werden in einer anderen zu anderen Zeichenfolgen. Zur Behebung mussten wir die Suche durchgängig auf ISO-3166-1-Alpha-2-Codes umstellen. Die Lehre daraus reicht weit über Flaggen-Emojis hinaus.


Wie der Fehler sichtbar wurde

Das Widget zeigte eine Liste von Ländern mit den zugehörigen Flaggen-Emojis und weiteren Daten an. Die ursprüngliche Implementierung speicherte Ländernamen als Kennung – die Zeichenfolge „Germany“ oder „France“ – und nutzte ein Wörterbuch mit diesen Namen als Schlüsseln, um das entsprechende Flaggen-Emoji zu ermitteln.

Mit englischen Regionaleinstellungen funktionierte das korrekt. Der gespeicherte Name lautete „Germany“, der Wörterbuchschlüssel ebenfalls „Germany“, und die Suche lieferte die richtige Flagge. Bei deutschen Regionaleinstellungen gibt NSLocale.current.localizedString(forRegionCode: "DE") jedoch „Deutschland“ zurück. Das Widget rief diese Methode auf, um Anzeigenamen dynamisch zu ermitteln. Der angezeigte Name wurde also zu „Deutschland“ – im jeweiligen Kontext korrekt –, doch die Suche im Wörterbuch mit englischen Schlüsseln schlug fehl, griff auf einen Standardwert zurück und zeigte die falsche Flagge an.

Noch tückischer waren Teilübereinstimmungen. In manchen Sprachen hat der lokalisierte Name eines Landes zufällig denselben Anfang wie der englische Name eines anderen Landes oder ähnelt ihm stark. Ohne strikte Prüfung auf Gleichheit lieferten unscharfe Suchverfahren plausibel wirkende, aber falsche Ergebnisse. Eine Qualitätssicherung, die nur mit englischen Regionaleinstellungen testet, würde das nie entdecken.

Warum lokalisierte Namen eine Falle sind

Das grundlegende Problem besteht darin, eine von den Regionaleinstellungen abhängige Zeichenfolge als Kennung zu verwenden. Kennungen müssen stabil sein – unabhängig davon, wer sie liest, wann und in welchem Sprachkontext. Der Name eines Landes in den aktuellen Regionaleinstellungen ist ein Anzeigewert. Ihn einem Nutzer anzuzeigen, ist richtig und sinnvoll. Ihn als Suchschlüssel, Datenbankkennung, Cache-Schlüssel oder sonstige stabile Referenz zu verwenden, ist falsch und fehleranfällig.

Das betrifft nicht nur Ländernamen. Währungsnamen, Sprachnamen, Anzeigenamen von Regionaleinstellungen und Zeitzonenbeschreibungen haben dieselbe Eigenschaft: Sie sind von den Regionaleinstellungen abhängige Zeichenfolgen, die das System für die Anzeige erzeugt, nicht zur Identifikation. NSLocale.current.localizedString(forCurrencyCode: "EUR") gibt auf Englisch und Deutsch jeweils „Euro“ zurück, was zufällig übereinstimmt. localizedString(forCurrencyCode: "GBP") liefert jedoch auf Englisch „British Pound“ und auf Deutsch „Britisches Pfund“. Beide als Schlüssel zu verwenden, führt zur selben Fehlerklasse.

Die Regel ist einfach: Verwenden Sie in der gesamten Logik den ISO-Code als Kennung und rufen Sie die Lokalisierungsmethode erst auf, wenn Sie eine Zeichenfolge für die Anzeige erzeugen. Speichern Sie niemals die lokalisierte Zeichenfolge. Verwenden Sie sie niemals als Schlüssel. Vergleichen Sie sie niemals programmatisch über verschiedene Regionaleinstellungen hinweg.

Flaggen-Emojis aus ISO-Codes erzeugen

Flaggen-Emojis sind in Unicode als Paare von Regional Indicator Symbols codiert. Die Zeichenfolge für eine bestimmte Landesflagge entspricht dem ISO-3166-1-Alpha-2-Code des Landes, wobei jeder Buchstabe durch das entsprechende Regional Indicator Symbol ersetzt wird. Diese Symbole reichen von U+1F1E6 (für A) bis U+1F1FF (für Z).

In Swift lässt sich ein Flaggen-Emoji mit einer kurzen Hilfsfunktion aus einem ISO-Code erzeugen:

func flagEmoji(for isoCode: String) -> String {
    let base: UInt32 = 0x1F1E6 - 65 // offset from A
    return isoCode.uppercased().unicodeScalars.compactMap {
        UnicodeScalar(base + $0.value)
    }.map { String($0) }.joined()
}

Diese Funktion nimmt „DE“ entgegen und gibt 🇩🇪 zurück. Aus „SG“ wird 🇸🇬. Sie benötigt weder ein Wörterbuch noch eine Zuordnungsdatei oder laufende Pflege. Die Zuordnung ist durch den Unicode-Standard definiert und folgt direkt den ISO-3166-1-Alpha-2-Codes.

Der einzige relevante Sonderfall ist, dass nicht für jeden Code aus zwei Buchstaben ein entsprechendes Flaggen-Emoji existiert. Codes wie UN und EU, die in ISO 3166-1 reserviert und keinem Land zugewiesen sind, ergeben Flaggen-Emojis, die nicht auf allen Plattformen einheitlich dargestellt werden. Die obige Funktion führt außerdem keine Validierung durch: Sie wandelt alle übergebenen Zeichen um. Daher sollte die Eingabe vor der Umwandlung mit der Liste der zugewiesenen ISO-3166-1-Alpha-2-Codes abgeglichen werden (genau zwei Buchstaben A–Z). Die Beschränkung auf zugewiesene Länder- und Gebietscodes sowie Tests auf der Zielplattform decken diesen Fall ab.

Die Zuordnung für 150+ Länder, die wir weiterhin brauchten

Die algorithmische Erzeugung von Flaggen-Emojis aus ISO-Codes löst die Anzeige. Wir mussten Länder aber auch strukturiert speichern und abfragen: Das Widget filterte Länder nach Region, sortierte sie nach verschiedenen Kriterien und ordnete ihnen zusätzliche Metadaten zu. Dafür brauchten wir eine saubere Zuordnung.

Unsere Zuordnung verknüpft ISO-3166-1-Alpha-2-Codes mit einem kanonischen englischen Namen für interne Zwecke und Logs, einem numerischen ISO-3166-1-Code für Systeme, die numerische Kennungen benötigen, sowie einer regionalen Gruppierung. Der englische Name wird dabei niemals als Suchschlüssel verwendet. Er dient ausschließlich dem Debugging und der administrativen Anzeige in nicht lokalisierten Kontexten. Alle für Nutzer sichtbaren Namen werden beim Rendern über NSLocale.current.localizedString(forRegionCode:) erzeugt.

Diese Trennung ist die entscheidende Architekturentscheidung. Die Datenschicht arbeitet mit ISO-Codes. Die Darstellungsschicht übersetzt in die aktuellen Regionaleinstellungen. Dazwischen werden die beiden nicht vermischt. Die Suche nach einer Flagge, die regionale Gruppierung und die Sortierreihenfolge arbeiten sämtlich mit ISO-Codes. Nur der endgültig angezeigte Text wird lokalisiert.

Die Lösung auf andere Daten ausweiten

Nachdem der Flaggenfehler behoben war, prüften wir den restlichen Code auf dasselbe Muster. Das Problem trat an drei weiteren Stellen auf: in einem Währungsanzeigemodul, das lokalisierte Währungsnamen als Cache-Schlüssel verwendete, in einer Sprachauswahl, die lokalisierte Sprachnamen in den Nutzereinstellungen speicherte, und in einem Datumsformatierungsmodul, das von den Regionaleinstellungen abhängige Monatsnamen verglich.

Die Lösung folgte in jedem Fall demselben Muster. Ersetzen Sie die von den Regionaleinstellungen abhängige Zeichenfolge durch ihre stabile ISO- oder IETF-Entsprechung: Währungscodes nach ISO 4217 statt Währungsnamen, BCP-47-Sprachtags statt lokalisierter Sprachnamen und numerische Monatswerte statt Monatsnamen als Zeichenfolgen. Lokalisierte Zeichenfolgen gehören ausschließlich in die Darstellungsschicht.

Die systematische Prüfung lohnte sich. Jeder dieser Fehler wäre bei Tests mit englischen Regionaleinstellungen unsichtbar geblieben und erst im Produktivbetrieb auf Geräten mit anderen Spracheinstellungen aufgetreten. Da die App sich an ein mehrsprachiges Publikum richtete, wären die Auswirkungen erheblich gewesen.

Das Ausmaß des Problems im Produktivbetrieb

Ein Grund für das Fortbestehen dieser Fehlerklasse ist, dass sie in Entwicklungsumgebungen mit nur einer Regionaleinstellung unsichtbar bleibt. Wer mit englischen Regionaleinstellungen eine App für ein internationales Publikum entwickelt, kann die gesamte Länderanzeige implementieren und testen, ohne den Fehler jemals auszulösen. Er tritt erst auf, wenn die Regionaleinstellungen des Geräts nicht denen entsprechen, mit denen das Wörterbuch oder die Zuordnung ursprünglich erstellt wurde – genau das trifft auf die Mehrheit der Zielgruppe zu.

Bei Widgets ist diese Lücke größer als in der Haupt-App, weil Widgets im eingeschränkten Kontext einer Erweiterung ausgeführt werden. Die Widget-Erweiterung verwendet die aktuellen Regionaleinstellungen des Systems, die von den beim Entwickeln getesteten Einstellungen abweichen können. Auch das Debuggen ist weniger direkt als in der Haupt-App: Sie führen das Schema der Widget-Erweiterung in Xcode aus (oder verwenden Debug > Attach to Process), und der Timeline- und View-Code wird nach dem Zeitplan von WidgetKit ausgeführt, statt auf Ihre eigenen Interaktionen zu reagieren. Um Darstellungsfehler durch Regionaleinstellungen in Widgets zu erkennen, müssen Sie die Erweiterung mit den relevanten Regionaleinstellungen ausführen, Xcode-Vorschauen mit beispielhaften Timeline-Einträgen nutzen und den tatsächlichen Rendering-Kontext der Erweiterung sorgfältig prüfen – ein Schritt, der leicht übersprungen wird, bis ein Nutzer das Problem im Produktivbetrieb meldet.

Die praktische Konsequenz ist, sämtliche von den Regionaleinstellungen abhängige Anzeigelogik als Risikokategorie zu behandeln, statt nur einen einzelnen Fehler zu beheben. Bei Code-Reviews sollte geprüft werden, ob solche Zeichenfolgen als Kennungen verwendet werden. Die Prüffrage ist einfach: Durchläuft diese Zeichenfolge eine Lokalisierungsmethode, bevor sie als Schlüssel oder Vergleichswert verwendet wird? Falls ja, muss das Design geändert werden. ISO-Codes, numerische Kennungen und andere von den Regionaleinstellungen unabhängige Referenzen sollten lokalisierte Zeichenfolgen auf jeder Ebene außer der endgültigen Anzeige ersetzen.

Tests mit verschiedenen Regionaleinstellungen

Die unmittelbarste Lehre aus diesem Fehler ist, dass Tests mit verschiedenen Regionaleinstellungen zur regulären Testmatrix gehören und nicht erst nachträglich berücksichtigt werden dürfen. iOS macht das relativ einfach: Im Simulator lassen sich die Regionaleinstellungen ändern, ohne die Systemsprache zu wechseln, und UI-Tests können nach Regionaleinstellungen parametrisiert werden.

Speziell für die Flaggen- und Länderanzeige ergänzten wir Snapshot-Tests mit einer festen Auswahl von Geräteeinstellungen – Englisch, Deutsch, Französisch, Japanisch und Arabisch. Sie prüfen, dass die Flaggen-Emojis für eine feste Auswahl von ISO-Codes mit allen diesen Einstellungen korrekt bleiben. Die Tests erkennen Regressionen sofort, sobald jemand eine von den Regionaleinstellungen abhängige Suche in den Anzeigepfad einführt.

Das allgemeinere Prinzip lautet: Jede Funktion, die Lokalisierung, Internationalisierung oder regionsabhängige Anzeigen betrifft, sollte automatisierte Tests mit mehreren Regionaleinstellungen haben. Manuelle Qualitätssicherung mit nur einer Einstellung ersetzt sie nicht. Fehler aufgrund von Annahmen über Regionaleinstellungen sind häufig und bleiben bei Tests mit nur einer Einstellung durchgängig unsichtbar.

Weiterführende Beiträge

Weiterführende Beiträge