Multilingual .NET applications. Enter .NET localization

Creating multilingual applications is a huge topic. There are whole books devoted to it, and if you’re serious about it, you should definitely read those, because what you see on surface, is only the tip of an iceberg.

If you only want to play with localization or need a quick reference, hopefully this post will help.

Fist thing is, .NET is really well thought of if it comes to localization, so if you know what you’re doing, it’s pretty painless to create application that will be easy to translate to other languages (localization is a LOT bigger topic than showing labels in different languages, but I’ll leave this topic out.)

Second thing is, localization mechanism in .NET is greatly based on conventions, and it’s what we’ll focus here.

First obvious rule is, that each string, picture, video… generally speaking localizable resource must not be hardcoded.

If you don’t hardcode resources where do you keep them? In (surprise) Resources file. Generally you can use .resx files, or plain .txt, though the latter are useful only if you only keep strings.

resourceFile

Visual Studio has pretty good support for editing resources in .resx files, and later I’ll show you another tool that can do it as well.

To access the resources programmatically you need ResourceManager class. To instantiate it you need to give it the part to resources within an assembly, and the assembly itself. If it’s a little bit unclear, here’s the example:

resourcePath

If the Visual Studio project is named LocalizableApp, and the .resx file we want to use is in folder Properties, and the file itself is named Resources.resx, the whole base name for the resources is “LocalizableApp.Properties.Resources”.

That’s where the compiled .resx file (.resources) will be located within the assembly. You can see it, if you open the compiled assembly with Reflector.

resourceLocation

So to instantiate the ResourceManager you’d write code similar to the following:

var manager = new ResourceManager("LocalizableApp.Properties.Resources", typeof(Program).Assembly);

Each resource is identified by it’s unique name string. For example in the picture below, we have two resources with names, “greeting” and “howAreYou”

resourceEditor

You use these names to get the values of the resources. The resource manager does its magic, to provide you with resources best matching the CultureInfo you provided. If you don’t provide any Culture, current thread’s CurrentUICulture is used. So if you write:

Console.WriteLine("{0} {1}", manager.GetString("greeting"), manager.GetString("howAreYou"));

The CultureInfo object held in CurrentUICulture property of currently executing thread will be used. The default value for this property is the same as language version of Windows OS the program is executing on, which is reasonable. If your user uses Polish Windows she will probably want to interact with Polish version of your app as well.

You may not rely on this default though, and either override the Thread’s property, or specify the culture explicitly when calling methods on ResourceManager, like so:

culture = CultureInfo.GetCultureInfo("pl-PL");
Console.WriteLine("{0} {1}", manager.GetString("greeting", culture), 
    manager.GetString("howAreYou", culture));

Of course you wouldn’t hardcode the culture the way I did in the example, because it would defeat the purpose of using culture in the first place.

OK, so how to add another language?

You have the .resx file with the default, fallback resources, right? So now all you need is either send the file to a localization company, sit back and enjoy life, waiting for them to do the hard job, or do it yourself. To go with the latter option, you may want to use a tool to edit resource files, called Resourcer, by Lutz Roeder (Reflector, people!).

resourceResourcer

You can easily edit any kind of resources in it (not only strings). When you’re done translating your resources, save the file as .resources file, using following pattern: {baseResourceName}.{culture}.resources. For example if you were to translate our example app, to German language as spoken in Germany, you’d save your edited file with the following name: LocalizableApp.Properties.Resources.de-de.resources

It is important to obey this pattern, as by convention, this is what ResourceManager expects. If you fail to obey it, ResourceManager will not be able to pick your translated resources and will fall back to the default.

Having translated and properly named .resource file we need to make a satellite assembly out of it. To do it, you need Assembly Linker (al.exe) tool that is part of .NET SDK.

al

There are multiple properties you can set with al, the minimal version to get valid satellite assembly is shown on above screenshot. One thing that’s important to note, is the name of resulting satellite assembly. Again, by convention it has to be {NameOfBaseAssemblyWithoutExtension}.resources.dll, so if our application assembly was named LocalizableApp.exe, satellite assembly must be called LocalizableApp.resources.dll

Notice that the name of the assembly is the same for every culture. So how do we deal with that. Obviously we can’t have two files with the same name in one folder. The way it is handled, is through subfolders. Each satellite assembly is held in a folder which is named after the culture of the satellite assembly.

resourceFolders

For example if our LocalizableApp.exe is held in a folder, let’s say Debug like on the above screenshot, the de-de satellite assembly would be Debug\de-de\LocalizableApp.resources.dll and for instance pl assembly would be in Debug\pl\LocalizableApp.resources.dll.

I hope this is all clear by now. The greatest thing is, to add new language you don’t even need the actual base assembly. All you need is its resources and their location within the assembly.

Technorati Tags: , ,