About cultures, infos and UI in C#

Just recently, I encountered a very interesting problem where the validation of valid inputs in a text field behaved very strangely. Anyone who has had to write C# and WPF applications for different languages and cultures is already familiar with the CultureInfo class. It’s most commonly used when you want to convert a string into a number.

Image generated with Dall-E AI.

Different Cultures, Different Notations

The most common and well-known example is the date and time format. Not only are there different placement options for the day and month in different cultures, but the full spelling of the month must also be considered. Special date-picker fields are often used here, or a specific format is enforced.

DecimalSeparator.svg
Punkt-Länder Komma-Länder regional Punkt oder Komma Momayyez-Länder unbekannt
Von NuclearVacuumFile:BlankMap-World6.svg, CC BY-SA 3.0, Link

However, it gets interesting with floating-point numbers. While in Europe it’s most common to use „,“ as the decimal separator and „.“ as the thousands separator, in the USA, for example, it’s the exact opposite. Then there are countries that use both, such as Canada, where a distinction is made, especially between the English and French-speaking communities. Beyond these forms, there are also the Momayyez countries, which use a special character „٫“ that is very similar and close to the „normal“ comma.

Momayyez and Comma: ٫ ,

Switzerland, in turn, accepts both as a decimal separator, and the straight apostrophe ‚ is therefore used for digit grouping.

In other words, for the same number „one thousand point two,“ we have the following notations:

  • Germany: 1.000,2
  • Great Britain: 1,000.2
  • Switzerland: 1’000.2 or 1’000,2
  • Persian: ١٬٠٠٠٫٢

Converting Floating-Point Numbers

So what happens when, for example, a British person enters a number into a German program? Well, .NET doesn’t care about the position of a digit grouping separator and ignores it completely, while the decimal separator may only appear once. For example, 1....0..000,2 is also a valid number.

But back to the example: the British person types 100.2, and this number is converted to 1002. As mentioned before, group separators are irrelevant for the conversion. Therefore, with international software, we have to pay attention to this fine detail. But there is another challenge that probably won’t be immediately obvious to every developer: operating systems that were installed with „one culture/region“ and are operated with another language.

For example, some test systems are equipped with different localizations that can be switched with a simple click. In C#, however, this leads to potentially two localizations being active at the same time. These two localizations can be queried as follows:

using System.Globalization;

CultureInfo local1 = CultureInfo.CurrentCulture;
CultureInfo local2 = CultureInfo.CurrentUICulture;

As you can already guess, one has to do with the UI, but the problem is not yet directly apparent. Specifically, you can query the name of the culture, but this doesn’t necessarily tell us which symbols are now used for separation. This is because in Windows, it’s possible to explicitly define these, allowing for wild combinations. Alternatively, you can define artificial cultures in C# and assign them directly. To check what is currently set, you could query the following parameter:

public string NumberDecimalSeparator => CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;

However, let’s return to the word UI. What I noticed during debugging was that everything from the UI had a different setting than in the backend. This was mainly because the UI doesn’t explicitly query the system’s settings, but rather what UI language the system has. Therefore, it’s possible to have a „DE-de“ in CurrentUICulture while the backend uses an „en-US“ with CurrentCulture.

Cultural Adaptation

To prevent natural conversion errors, one would have to consider whether to forgo the system settings and always load predefined settings, or to set the default culture for the threads at program start. One could also say that only the UI culture should be used everywhere by only using CurrentUICulture for conversions.

I researched for a long time, but all solutions seem to be workarounds rather than good, functioning solutions. The problem is that no matter which solution you choose, there’s always a part you overlook or that doesn’t work. For example, you could force the framework in WPF to adapt to a culture.

FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(
            XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.Name)));

However, this would only change the framework elements. You are still not protected from another thread being started with a different culture and now running „incorrectly.“ Those still working with the .NET Framework might also be affected by using WPF elements that utilize the ConverterCulture binding property: https://learn.microsoft.com/en-us/dotnet/api/system.windows.data.binding.converterculture


Digit or Not a Digit?

One might have been able to cope with everything so far. But .NET 6 introduced me to a new problem that I hadn’t even considered. Although many cultures now use Arabic numerals on the computer, as I mentioned earlier, it’s quite possible to change many interesting things in Windows. This includes the digits themselves. The problem here is that this allows for inputs that currently require even more workarounds. I had thought in the meantime that I could simply combat the issue with suitable regular expressions, but I realized at some point while playing around with the system that I was mistaken. This was mainly because digits are not always just digits, and sometimes my regular expression couldn’t handle something.

In the end, I didn’t pursue the matter further and have since been setting a specific culture, hoping to be able to continue here someday to take every culture into account.


Special Case

On macOS, CultureInfo does not seem to work (yet). When I run this simple code, you can see that I get different values than expected and set.

At the time of the blog article, there was no way to automatically detect the localization. Therefore, it is recommended to select a localization specifically. This issue has also been reported on GitHub. You can check if the problem still exists with the following link:

CultureInfo is not returning proper system language on Mac Big Sur (Version 11.6) M1 (ARM64) #73237


Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert