DekGenius.com
[ Team LiB ] Previous Section Next Section

3.5 Localization

The software market is a global one, and many programs will ship in regions where the users' first language will be different from the application developers' native tongue. While many software products get away with making the highly parochial assumption that everybody speaks English, .NET lets us do better than that. It provides support for building applications that support multiple languages.

The .NET Framework supplies facilities for localization of resources such as strings and bitmaps, and the Forms Designer can create forms that make use of this. To understand how to create localizable user interfaces, it is first necessary to understand the underlying localization mechanism that it is based on, so we will first look at global resource management, and then we will see how it is applied in a Windows Forms application.

3.5.1 Resource Managers

The programming model for localizable applications is based on a simple premise: whenever you require information that might be affected by the current language, you must not hardcode this information into your application. All such information should be retrieved through a culture-sensitive mechanism. (In .NET, the word culture is used to describe a locality; it implies all the relevant information, such as location, language, date formats, sorting conventions, etc.) The mechanism we use for this is the ResourceManager class, which is defined in the System.Resources namespace.

The ResourceManager class allows named pieces of data to be retrieved. (We'll see where this data is stored in just a moment.) For example, rather than hardcoding an error message directly into the source, we can do the following in C#:

ResourceManager resources = new ResourceManager(typeof(MyForm));
string errorWindowTitle = resources.GetString("errorTitle");
string errorText = resources.GetString("errorFileNotFound");
MessageBox.Show(errorText, errorWindowTitle);

The equivalent code in VB is:

Dim resources As New ResourceManager(GetType([MyForm]))
Dim errorWindowTitle As String = resources.GetString("errorTitle")
Dim errorText As String = resources.GetString("errorFileNotFound")
MessageBox.Show(errorText, errorWindowTitle)

This creates a ResourceManager object and asks it for two named resources: errorTitle and errorFileNotFound. It uses the strings returned by the ResourceManager as the error text and window title of a message box.

So where will the ResourceManager find this information? It will look for a resource file—a file that contains nothing but named bits of data, and it will expect to find it embedded as a named resource in an assembly. (Any .NET assembly can have arbitrary named files embedded in them. Any kind of file can be attached in this way—e.g., text files, bitmaps, binary files. But the ResourceManager will be looking for an embedded file in its special resource format.) It needs to know two things to locate the embedded resource file: the name of the resource file and the assembly in which it is embedded.

The name of the resource file is typically based on a class name. So in the previous code fragments, the ResourceManager will be looking for a file named after the MyForm class. It will always use the full name of the class, including its namespace, so if MyForm is defined in the MyLocalizableApp namespace, the ResourceManager will look for an embedded resource called MyLocalizableApp.MyForm.resources. (We will see shortly how to get Visual Studio .NET to add an appropriately named resource file to your project.)

But the ResourceManager also needs to know which assembly the resource file will be contained in. The assembly it will load is determined by the culture in which the code is running (i.e., what country and with which language).

A culture is identified by a two-part name. The first part indicates the spoken language, and the second part indicates the geographical location. For example, en-US represents the English-speaking U.S. locality, while fr-BE indicates the French-speaking Belgian culture. We need both the spoken language and the region to define a culture, because either on its own is not enough to determine how all information should be presented. For example, many localities have English as a first language, but can differ in other details. For example, although the en-US and en-GB cultures (American and British, respectively) both use the same language, dates are displayed differently—in the United Kingdom, the usual format is day/month/year, while in the U.S., the month is usually specified first. In this particular case, the country name alone would be sufficient, but that is often ambiguous, because many countries have more than one official language (e.g., Canada and Belgium).

The culture that is in force is determined by the Regional and Language Options Control Panel applet in Windows. The ResourceManager will use the two-part culture string to locate the assembly. It will always look for an assembly called AppName.resources.dll, where AppName is your application executable's name. The current culture merely determines the directories it will look in. If the culture is, say, fr-BE, it will first look for a subdirectory called fr-BE. (It will look for this directory beneath whatever directory your program happens to be running in.) If it doesn't find it there, it will then fall back to looking for generic French-language resources in an fr directory. Finally, if it finds neither of these, it will look in the application executable itself. This means that if there are no resources for the appropriate culture, it will revert to using whatever resources are built into the program itself. (These are referred to as the culture-neutral resources, but they are usually written for whatever culture the application developer calls home.)

Figure 3-6 shows the directory structure of a typical localized application. The executable file itself would live in the Localizable directory shown here. (There is no significance to that name—you can call the root directory anything.) This particular application has several culture-specific subdirectories, each of which contains an assembly called AppName.resources.dll (where AppName is whatever the main executable file is called). Both French and Dutch are supported. The resource DLLs in the fr and nl directories would contain resources appropriate to the French or Dutch languages respectively, which are independent of any particular French- or Dutch-speaking region. There are also location-specific resources supplied. For example, if there are any phrases that require slightly different idiomatic translations for French as spoken in France and French as spoken in Wallonia, these will be in the resource files in the fr-FR and fr-BE subdirectories, respectively. Note that this application should be able to function correctly in locales such as fr-CA and nl-NL—even though there are no subdirectories specific to these cultures, they will fall back to the fr and nl directories.

Figure 3-6. A localized directory structure
figs/winf_0306.gif

These resource assemblies in the culture-specific subdirectories are often referred to as satellite assemblies . This is intended to conjure up a picture of the main application assembly being surrounded by a collection of small but associated assemblies. (Satellite assemblies are typically smaller than the main application because they just contain resources; the main application assembly tends to be at least as large as the satellites because it usually contains both code and default resources.)

3.5.2 Resources and Visual Studio .NET

Visual Studio .NET can automatically build satellite resource assemblies for your application, and the Forms Designer can generate code that uses a ResourceManager for all localizable aspects of a form.

This raises an interesting question: what should be localizable? Text strings obviously need to be localizable, because they will normally need to be translated, but there are less obvious candidates too. Some languages are more verbose than others, and once the text of a label or button has been translated, the control may not be large enough to display it. This means that for localization of strings to be of any use, a control's size must also be localizable. And if controls need to be resized for localization purposes, this will almost certainly mean that other controls on the same form will need to be moved. So on a localizable form, the Forms Designer also retrieves the size and position of controls from the ResourceManager, rather than hardcoding them in. In fact, it retrieves almost all the properties that affect a control's appearance from the ResourceManager, just in case they need to be modified for a particular culture.

To get the Forms Designer to generate this localizable code, simply set the form's Localizable property (in the Misc category) to true. This will cause it to regenerate the entire InitializeComponent method so that all relevant properties are read from a ResourceManager. It also adds a new file to the project named after your form: if your form's class is MyForm, it will add a MyForm.resx file. By default, this file will be hidden, but if you go to the Solution Explorer window and enable the Show All Files button on its toolbar, your MyForm.cs or MyForm.vb file will grow a + symbol. If you click this, you will see the MyForm.resx file. This file contains all the culture-neutral values for your form's properties. It is hidden by default because you do not normally need to edit it directly; we will examine its contents shortly. (You may remember that the ResourceManager class will actually be looking for a .resource file, not a .resx file. Visual Studio .NET stores all resources in .resx files, but it compiles these into .resource files when it builds your component.)

Having made your form localizable, any properties that you edit will simply be changed in the resource file. So how do we exploit this to make a localized version of the form for some other culture? Alongside the Localizable property, you will see a Language property. This is usually (Default), to indicate that you are editing the default resource file. But you can change this to another culture. If you set it to German, you will see that another resource file is added to your application—MyForm.de.resx. Visual Studio .NET will compile this file into a satellite assembly in the de subdirectory.[10] If you do any further editing to the form, new property values will be stored in this file, meaning that those values will be used when running in a German culture. You can also specify a more specific culture—if you select German (Austria), Visual Studio will add a MyForm.de-AT.resx file. This will be built into a satellite assembly in the de-AT subdirectory, allowing you to supply properties that will be used specifically in the German-speaking Austrian culture.

[10] Each .resx file in a project will end up as a single embedded resource in some assembly. All the resource files for a given culture will be in the same assembly, so you will end up with one satellite assembly for each culture you support. The name of the embedded resource will be determined by the name of the .resx file. Visual Studio .NET always prepends the project's default namespace to the resource name, so MyForm.de.resx will end up being the MyNamespace.MyForm.resources resource in the satellite assembly in the de directory. Any .resx file whose name does not contain a culture code will end up in the main assembly, so MyForm.resx will become the MyNamespace.MyForm.resources resource in the main executable assembly.

So your form will now have multiple faces. Whenever you change the Language property, you will be shown how the form will look when displayed in the selected culture. Any edits you make will only apply to the selected culture. Visual Studio .NET takes care of the build process, creating whatever satellite assemblies are required in the appropriate directories. If you want to see the effects of this without modifying your computer's regional settings, you can modify the culture for your application with the following change to your Main method in C#:

[STAThread]
static void Main() 
{
    System.Threading.Thread.CurrentThread.CurrentUICulture =
        new System.Globalization.CultureInfo("fr-FR");
        Application.Run(new PropForm());
}

The corresponding VB code is:

<STAThread> Public Shared Sub Main()
   System.Threading.Thread.CurrentThread.CurrentUICulture = _
       New System.Globalization.CultureInfo("fr-FR")
   Application.Run(New PropForm())
End Sub

This sets the main thread's culture to fr-FR. This will cause the ResourceManager class to try to locate satellite assemblies containing French resources.

3.5.2.1 Resource Files

Visual Studio .NET will create and maintain the necessary resource files as you edit your forms for the cultures you choose to support. However, it is often useful to edit these files directly—for example, if you wish to support localization for any error messages you display in a message box, you will need to add your own entries to these files.

You can edit the .resx files that Visual Studio .NET creates—it provides a special user interface just for this purpose. If you double click on a .resx file for a form (having first made sure that the Solution Explorer is in Show All Files mode), you will see a grid representing the contents of the file, as shown in Figure 3-7.

Figure 3-7. Editing a .resx file
figs/winf_0307.gif

The Forms Designer uses a naming convention for resource entries. Properties of a control are always named as control.PropertyName, where control is the name of the control on the form and PropertyName is the property whose value is being stored. The value column indicates the value that the property is being set to; an empty value indicates that the property is not to be set. The property's type is also stored in this file—the ResourceManager needs to know the data type (e.g., a string, a Color, a Size, etc.) of each property to return the correct kind of object at runtime.[11] The default type is string, so for string lookups you don't need to supply anything other than the name and value. To add your own resource entries (e.g., error text), just type new entries at the bottom of the list. You may use whatever name you like, so long as it is unique within the resource file.

[11] Values are stored and retrieved using .NET's serialization facility. A type needs to support serialization to be used in a resource file.

You can also add new .resx files to a project. This allows you to add a resource file that is not attached to any particular form. (This is useful for custom control libraries, which will not necessarily contain any forms at all.) Visual Studio .NET uses the same naming convention here as it does for the .resx files it creates: if there is a culture name in the filename, it determines which satellite assembly the resource will be held in. And as before, the name of the embedded resource is determined by putting the project default namespace in front of the filename. So MyStuff.fr-BE.resx would create an embedded resource called MyAppNamespace.MyStuff.resources in the satellite assembly in the fr-BE subdirectory.

The easiest way to use such a custom resource file is to name it after some class in your code, and pass the type of that class to the ResourceManager when you construct it like so:

ResourceManager rm = new ResourceManager(typeof(MyClass));

or:

Dim rm As New ResourceManager(GetType([MyClass]))

This would create a ResourceManager that would look for a MyAppNamespace.MyClass.resources embedded resource, using the current culture to determine where to find the assembly.

    [ Team LiB ] Previous Section Next Section