Recently a client requested being able to use a specific custom culture on his website running on an Azure VM. Initially I thought it would be as easy as just adding/enabling the language in question or maybe even install a language pack on the VM but it turned out it was a bit more complex. This specific culture (ru-UA) was not available on the Azure servers and I wasn't able to find it in a compatible language pack to install either.
Fortunately the culture seemed to be available on our own developer machines running Windows 10. My colleague who had the task assigned made a few attempts and successfully managed to make a simple command-line tool to export this culture from his own machine to a file. Now you would think it would be just as easy to actually import this culture on the server, but trying to do that would cause all kinds of errors - none of which made much sense. It seems like there's not really a lot of people who has actually been doing this kind of stuff so trying to find any clues as to what was wrong wasn't really an easy task.
I spent some time debugging the .NET classes related to managing cultures and after a while I had an idea about what could be causing the errors. It seemed like I was able to run the import as long as the culture already existed on my system. While this was a great discovery it didn't really help me much, since the whole purpose of importing a culture would be to get that specific culture installed on a machine where it wasn't already available. Googling a bit more it also seemed like I wasn't the first one who had run into this exact issue (1, 2, 3).
After spending some more time trying to work around this limitation I finally managed to trace the actual problem. In order to register the culture on the machine, you have to load it from the exported file. It isn't possible to actually recreate this culture without loading it from the file, since a few of the properties do not have any sort of setters available from the outside. When loading a culture from a file you use this method:
public static CultureAndRegionInfoBuilder CreateFromLdml(string xmlFileName)
Even though you would think that this method should simply just read the XML content of the file and create a culture object in memory, it apparently does something else. While trying to create the culture object this method actually seems to try to access a culture with that same name, which is what is causing the weird unexpected "Culture name 'xxxxx' is not supported", when trying to create a import routine.
From my failed attempts at creating a culture manually, I however knew it wasn't really a problem creating and registrering a simple culture. The problem was however to populate this culture with all the right data, since a lot of the properties weren't writable. So I simply had to use the Load method if I wanted my culture to be exactly as the one stored in the XML file - but how can we trick this method into running successfully?
The Load method actually just requires the culture to exist, so I tried creating an empty placeholder culture using the name of the culture I wanted to import. With this culture registered, I was finally able to successfully have the Load method import the XML file containing my custom culture.
Now - with this culture loaded, all I would need to do was to unregister the temporary placeholder culture again and then register the real culture loaded from the XML file. To be honest, I didn't really expect this to work but it turns out it actually did - I managed to import the missing culture on the target machine!
// Build and register a temporary culture with the name of what we want to import. // CreateFromLdml method will fail when trying to load a culture from file if it doesn't already exist. var tempCulture = new CultureAndRegionInfoBuilder(cultureName, CultureAndRegionModifiers.None); tempCulture.LoadDataFromCultureInfo(CultureInfo.CurrentCulture); tempCulture.LoadDataFromRegionInfo(RegionInfo.CurrentRegion); tempCulture.Register(); // Now load up the culture we actually want to import var culture = CultureAndRegionInfoBuilder.CreateFromLdml(filePath); // Unregister the temporary culture CultureAndRegionInfoBuilder.Unregister(cultureName); // Register the real culture loaded from file culture.Register();
I would classify this as a bug and I assume the reason why this has never been addressed by Microsoft is most likely that it's not really something a lot of people ever use or encounter.
Since we needed to actually work with this in an automated setup running Azure Pack, I also created a simple command-line tool for importing and exporting cultures. The source code is available here on Github - I haven't spent much time polishing it so you may find some bugs. It however works great for equipping our servers with the extra cultures we need, as long as we have a source to get these cultures exported from.