When designing EMC VSI 4.0, early on I made a decision that the entire core framework was going to be i18n compatible from the ground up. Unfortunately this led to colorful weekend. While this topic has been discussed time and time again, it’s so important that it bears repeating: just because your copy of Windows is set to use a different region’s locale doesn’t mean that you’re not still using good ‘ol en-US when it counts.
As most .NET developers know, the System.Globalization.CultureInfo class is the starting point at which you determine the locale of a process. The CultureInfo class has two properties: CurrentCulture and CurrentUICulture.
The CurrentCulture property reflects the locale of the executing thread. The current culture is controlled through the Control Panel’s “Regional and Language Options” screen:
When you set the system’s regional settings you are affecting the date and time, number, currency, and various other formats that the system uses. However, an important distinction to make is that changing a system’s region does not affect the language of the operating system (OS). The value of the regional setting is reflected in the CurrentCulture property in the .NET Framework. This value is important because as I mentioned it is used to determine many formats. One such example is the date/time format. Parsing a date/time string in the en-US (English) locale is different than parsing a date/time string using the nl-BE (Dutch) locale.
However, the CurrentCulture property has nothing to do with the most common aspect of .NET i18n preparation, the Resource Manager.
The CurrentUICulture property is set to the locale returned by the Win32 API function GetUserDefaultUILanguage when the process is first started. The kicker is, though, that this low-level Windows function will return en-US despite what is configured in the “Regional and Language Options” control panel. The CurrentUICulture is only set to a non-English locale if:
- The Windows OS in question is a completely localized version of Windows built for a specific culture.
- If it is using the Multi-Language User Interface (MUI) pack and the MUI is set to use a language pack other than English.
The CurrentUICulture is what determines what visual elements are shown to a user in Windows forms-based applications. More importantly than that, however, the CurrentUICulture property is what the .NET ResourceManager uses to load locale-appropriate resource files.
Remember, the CurrentCulture can be completely different than the CurrentUICulture, and the latter is only set under very specific circumstances. Maybe you can see where this is going.
Th3 F4n P8rt
It bears repeating:
- The CurrentCulture property determines what formats are used when displaying and parsing things like dates, currency, numbers, etc. It is per-thread and controlled via the “Regional and Language Options” control panel.
- A completely separate property, CurrentUICulture, controls what visual elements are displayed to the user as well as what resource files are loaded for an assembly. This property is only set if the MUI is installed or a fully-localized version of Windows is being used.
So what’s the problem? I’ll tell you. Imagine you’re a good, internationally-concious developer (like me) and you place a lot of color codes in a resource file so that localization teams can create satellite assemblies later on to customize an application’s appearance for a specific culture.
Well, as it turns out this is a quick way to crash an application.
Since the color codes are formatted as “R, G, B”, and some cultures, such as nl-BE, use a comma instead of a decimal point, these values are interpreted as 32-bit integers when using the ColorConverter class to convert them to color objects.
So a person with their “Regional and Language Options” locale set to “Dutch (Belgium)” will quickly receive an unhandled exception about “122, 122, 122” not being a valid 32-bit integer.
The solution seems simple enough, right? Create a file called “Resources.nl-be.resx” and override the offending color formats with locale-appropriate ones:
Note: For the record, the appropriate way to separate numerical values for locales that forgo decimal points is with a semi-colon instead of a comma.
The problem is apparent: I’m storing values affected by CurrentCulture in a resource file which is only loaded when its culture matches that of CurrentUICulture. And since the CurrentCulture can easily be different than CurrentUICulture, the result is that in Belgium this particular application will crash even though there is a perfectly good resource file. The reason is that many Windows systems in Belgium that use the nl-BE locale simply use the English version of Windows and set the regional settings, which in no way affects the CurrentUICulture and thus prevents any locale-specific resource files from loading.
So what’s the solution?
The easiest solution is to take advantage of the generated code that Visual Studio creates when you enable the default Resource file for an assembly. The Resources class that is created contains a property named Culture. In your assembly that contains the Resources add a method that allows external callers to set the Resources class’s Culture property (the Resource class itself is scoped as internal). This will allow callers to set the culture that is used by the ResourceManger (which again, uses CurrentUICulture by default) when resources are queried. Essentially you’re allowing others to synchronize the CurrentCulture and CurrentUICulture values for that resource file. However, while this solution does apply to all threads it does not apply to all resource files.
Of course, you can also set the static property System.Threading.Thread.CurrentThread.CurrentUICulture to ensure that all ResourceManagers use the specified culture, but you’ll need to make sure that if you do that all threads have this set, not just the one on which the code is currently executing.
It’s Worth It
This may seem like a lot of work to support i18n preparation and localization efforts, but it’s worth it. Remember, we live in a big world, and you want to use software that you’re friends in Germany, Japan, and even Latveria create. So do unto others as you’d have them do unto you!