Erst letztens stieß ich auf ein sehr interessantes Problem, wo die Überprüfung von gültigen Eingaben in einem Textfeld sich sehr seltsam verhielt. Wer also schon mal in C# und WPF Anwendungen für unterschiedliche Sprachen und Kulturen schreiben musste, dem ist bereits die CultureInfo-Klasse bekannt. Am gängigsten vor allem, wenn man aus einer Zeichenkette eine Zahl konvertieren möchte.

Unterschiedliche Kulturen, unterschiedliche Schreibweisen
Am geläufigsten und bekanntesten ist die Datums- und Uhrzeitschreibweise. Es gibt hier nicht nur unterschiedliche Stellungsoptionen von Tag und Monat in unterschiedlichen Kulturen, auch das Ausschreiben des Monats muss hier beachtet werden. Oft und gerne werden hier spezielle Datepicker-Felder genutzt oder ein Format vorgegeben.
Punkt-Länder Komma-Länder regional Punkt oder Komma Momayyez-Länder unbekannt
Von NuclearVacuum – File:BlankMap-World6.svg, CC BY-SA 3.0, Link
Jedoch wird es spannend bei Fließkommazahlen. Während in Europa es am gängigsten ist, dass man „,“ als Dezimaltrennzeichen nutzt und „.“ als Tausendertrennzeichen, so ist dies bspw. in den USA genau umgekehrt. Dann gibt es noch Länder, die beides führen, wie bspw. Kanada, wo vor allem zwischen der englisch und französisch sprechenden Gesellschaft unterschieden wird. Außerhalb dieser Form gibt es noch die Momayyez-Länder, die ein gesondertes Zeichen „٫“ nutzen, dass sehr ähnlich und sehr nah dem „normalen“ Komma kommt.
Mommayez und Komma: ٫ ,
Die Schweiz wiederum akzeptiert beides als Dezimaltrennzeichen und für die Zifferngruppierung wird daher dort das gerade Hochkomma ‚ genutzt.
Sprich, wir haben also für dieselbe Zahl von „tausend Komma zwei“ folgende Schreibweisen:
- Deutschland 1.000,2
- Großbritannien 1,000.2
- Schweiz 1’000.2 oder 1’000,2
- Persisch ١٬٠٠٠٫٢
Konvertieren von Fließkommazahlen
Was passiert nun, wenn man bspw. als britische Person Zahl in ein deutsches Programm eingibt? Nun .NET ist die Position eines Zifferngruppierungszeichens egal und ignoriert es völlig, während das Dezimaltrennzeichen nur ein Mal vorkommen darf. Bspw. ist 1….0..000,2 ebenfalls eine gültige Zahl.
Aber zurück zum Beispiel, die britische Person tippt ein: 100.2, dann wird diese Zahl per Konverter zu 1002. Wie zuvor erwähnt, Dezimaltrennzeichen sind irrelevant für die Konvertierung. Daher müssen wir bei internationaler Software auf dieses feine Detail achten. Doch es gibt noch eine weitere Herausforderung, die voraussichtlich nicht jedem Entwickler sofort auffällt. Betriebssysteme, die mit „einer Kultur/Region“ installiert wurden und mit einer anderen Sprache betrieben werden.
Beispielsweise werden ein paar Testsysteme mit verschiedenen Lokalisationen ausgestattet, welche mit einem einfachen Klick gewechselt werden können. Unter C# führt das jedoch, dass potenziell zwei Lokalisierungen gleichzeitig aktiv sind. Diese beiden Lokalisierungen könne wie folgt abgefragt werden:
using System.Globalization;
CultureInfo local1 = CultureInfo.CurrentCulture;
CultureInfo local2 = CultureInfo.CurrentUICulture;
Wie man schon erahnen kann, hat das eine, was mit der UI zu tun, aber direkt ersichtlich ist die Problematik noch nicht. Spezifisch kann man hier zwar den Namen der Kultur abfragen, aber das muss nicht zwangsläufig dazu führen, dass wir wissen, welche Symbole nun zum Trennen verwendet werden. Dies liegt daran, dass es in Windows möglich ist, diese sogar explizit zu bestimmen, sodass man auch wilde Kombinationen möglich sind. Alternativ kann man auch künstliche Kulturen in C# definieren und diese direkt zuzuweisen. Um zu prüfen, was aktuell eingestellt ist, könnte man folgende Parameter abfragen:
public string NumberDecimalSeparator => CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
Jedoch besinnen wir uns auf das Wort UI zurück. Was mir bei einer Fehlersuche auffiel war, dass alles von der UI eine andere Einstellung hatte als im Backend. Das lag vor allem daran, dass bei der UI nicht das System seine Einstellung explizit abgefragt wird, sondern eher was für eine UI Sprache das System hat. Daher ist es möglich im CurrentUICulture ein „DE-de“ zu haben, während das backend mit CurrentCulture ein „en-US“ nutzt.
Kulturelle Anpassung
Um natürliche Konvertierungsfehler vorzubeugen, müsste man sich nun überlegen, ob man auf die Systemeinstellungen verzichtet und immer vordefinierte Einstellungen lädt oder für eine der beim Programmstart die Standardkultur für die Threads setzt. Man könnte auch sagen, dass man überall nur die UI-Kultur nutzt, indem man nur CurrentUICulture beim Konvertieren nutzt.
Ich habe lange recherchiert, aber alle Lösungen scheinen eher Workarounds zu sein, anstatt gute und funktionierende Lösungen zu sein. Das Problem ist, dass egal welche Lösung man wählt. Man immer einen Teil hat, den man übersieht, oder der nicht funktioniert. Beispielsweise könnte man das Framework in WPF zwingen, sich einer Kultur anzupassen.
FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(
XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.Name)));
Jedoch würde man damit nur die Framework-Elemente umstellen. Jedoch ist man dann immer noch nicht davor bewahrt, dass ein anderer Thread mit einer anderen Kultur gestartet worden ist und nun „falsch“ läuft. Wer noch mit .NET Framework arbeitet könnte auch noch davon betroffen sein, dass er WPF elemente nutzt die das ConverterCulture Binding Property nutzt: https://learn.microsoft.com/en-us/dotnet/api/system.windows.data.binding.converterculture
Ziffer oder keine Ziffer?
Mit dem bisherigen hätte man ja noch irgendwie klarkommen können. Doch .NET 6 führte mich zu einem neuen Problem, dass ich noch gar nicht auf dem Schirm hatte. Zwar nutzen viele Kulturen am Rechner mittlerweile arabische Zahlen, doch wie ich zuvor erwähnt hatte, ist es in Windows durchaus möglich viele interessante Dinge umzustellen. So auch die Ziffern. Das Problem hierbei ist, dass dadurch Eingaben möglich werden, die aktuell für noch viel mehr Workarounds fordert. Ich dachte nämlich in der Zwischenzeit, dass ich mit geeigneten regulären Ausdrücken die Sache einfach bekämpfen könnte, doch ich hatte mich irgendwann beim Herumspielen am System bemerkt, dass ich mich geirrt habe. Das lag vor allem dran, dass Ziffern nicht gleich Ziffern sind und mir manchmal mein regulärer Ausdruck mit etwas nicht klarkam.
Im Endeffekt habe ich erstmal das ganze nicht weiterverfolgt und stelle seitdem gezielt eine Kultur ein, in der Hoffnung hier mal weiter machen zu können um auch jede Kultur zu berücksichtigen.
Sonderfall
Unter MacOS scheint CultureInfo (noch) nicht zu funktionieren. Wenn ich diesen simplen Code ausführe, sieht man, dass ich andere Werte erhalte als erwartet und eingestellt sind.

Zum Zeitpunkt des Blogartikels gab es keine Möglichkeit, die Lokalisierung automatisch zu erkennen. Daher wird empfohlen, eine Lokalisierung spezifisch auszuwählen. Auf GitHub wurde dieses Problem auch bereits gemeldet. Ihr könnt mit folgendem Link prüfen, ob das Problem weiterhin besteht:
CultureInfo is not returning proper system language on Mac Big Sur (Version 11.6) M1 (ARM64) #73237
Schreibe einen Kommentar