Introduction
Localization is a concept that should be known by every developer creating products or applications supporting multiple languages. I was searching for the article about implementing localization for windows application using satellite DLLs. I found many articles which explain the subject well. I am sharing my experience and findings while doing this.
Hopefully this article will be helpful to the people looking to implement localization with satellite DLLs.
The Overview
There have been many applications where end users feel happy to use a software with their local language. Localization does exactly the same! It is the process of converting user interfaces to a user’s local culture. This involves converting strings to the user's local culture and can also involve time, date, and number formats.
Please note: I am not a language expert and I have used fish to translate from English into other languages. The example attached may have incorrect translations.
Satellite Assembly
In simple words, satellite assembly is a resource-only assembly. A definition from MSDN says something like this: "A .NET Framework assembly containing resources specific to a given language is called as satellite assembly". Thus a satellite assembly is a localised assembly having only resources in it and not a code. As the name suggests, each satellite assembly is associated with a master assembly known as the neutral assembly or main assembly. All the application code is present in main assembly which loads the required satellite assembly depending on the UI culture to get the localized resources. In my example, Localization.exe is the neutral (main) assembly which contains all the application code. This example has several satellite assemblies associated with the main assembly i.e. Localization.exe.
Deploying Satellite Assemblies
Each satellite assembly is deployed to AppBase directory according to the rules of the .NET assembly loader. Each satellite assembly should be deployed to the directory <culturename> under AppBase directory. E.g. If we want to deploy the satellite assembly localized for United States Engilsh, it should be deployed as ApplicationName.resources.dll (e.g. in my example Localization.resources.dll) under the directory 'de-DE' which is a subdirectory of AppBase as below:
Thankfully, Visual Studio and .NET compiler are intelligent enough and deploy the satellite assemblies to bin\debug\<culturename> or bin\release\<culturename> directory after successful compilation. See below:
Satellite assemblies can be strongly named if needed to put in GAC. To create satellite assembly manually, refer to this article.
How .NET Framework Loads Satellite Assembly
To explain how .NET Framework loads satellite assembly for appropriate culture, have a look at the resource files available for the application example of this article. This application has a default resource file, i.e. Localization.resx. All the default strings and any other resources will be stored here. Now if you want to localize this application to 'fr-FR' culture, you need to simply create another resource file named as Localization.fr-FR.resx.
Now before loading an appropriate satellite assembly .NET framework queries the current culture to the Thread's CultureInfo.CurrentUICulture
property. If your CurrentUICulture
is 'fr-FR', it will first look for the Localization.fr-FR.resx resource file, i.e. satellite assembly AppBase/fr-FR/Localization.resources.dll. If this file is missing, it will look for the neutral culture of the given UICulture
and it is the Localization.fr.resx file i.e satellite assembly AppBase/fr/Localization.resources.dll. If this resource file is also missing, default resource file named Localization.resx will be used.
Note: A neutral culture is specified by only the two-digit lowercase language code. For example, "fr" specifies the neutral culture for French, and "de" specifies the neutral culture for German.
Using the Code
Step 1: Create New Windows Application
Create a new C# Windows Application under New | Project. Let's call this application as Localization and let it create a solution directory. By this, you should have a blank form with Form1.cs file. Now add labels, radiobuttons and groupbox with some default text as shown below:
Step 2: Add Resource Files Required for Localization
Right click on the project in solution explorer and click add new item. From the available options, select 'Resource File' and rename 'Resource1.resx' to 'ProjectName.<culturename>.resx'. Thus if you intend to have application localized for de-DE, fr-FR and pt-BR, then you should have Localization.de-DE.resx, Localization.fr-FR.resx and Localization.pt-BR.resx files in your project as below:
In addition to this, you should add a default resource file named Localization.resx which will be embedded in your main assembly. This file specifies the resources in neutral culture and will be used to load these default resources in case you are trying to load a culture for which your application is not localized.
Now you are ready to feed in the key and value pair for the strings to be used in UI. Add keys and its appropriate value in the specified culture in the resx file as below :


Now in case, if you forget to add String
key in Localization.de-DE.resx. While loading this string resource manager will fail to search for it in AppBase/de-DE/Localization.resources.dll and will finally fetch the same from the resources embedded in the main assembly, i.e. Localization.resx. More details follow below in the section 'Specifying a default resource'.
Note that the form has a property Localizable
which when set to true
creates a default resource file for that form.
Step 3: Set CurrentUICulture to Machine Culture
If you are using English version of Windows, your application will always use English resource by default, i.e., Application built on the machine with English as locale would have CurrentUICulture = en-US
by default. But this should be set to the appropriate culture automatically if the application is launched on a different locale machine. In order do this, you can add the following code to the application startup:
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
When an application is launched on a machine having English as a locale, Thread.CurrentThread.CurrentCulture
will be initialized to 'en-US
', If machine has German locale, this will be initialized to 'de-DE
' and so on... Thus the above code would make sure to set your CurrentUICulture
to the appropriate culture automatically.
Step 4: Instantiating a ResourceManger
ResourceManager
is a class which provides convenient access to culture-specific resources at run time. Add a member variable to your application as below:
private ResourceManager m_resourceManger = null;
Now, instantiate this object in the application load event as below:
m_resourceManger = new ResourceManager("Localization.Localization",
Assembly.GetExecutingAssembly());
Parameters
- baseName
The root name of the resources. This is typically will be in the form
For example, the root name for the resource file named "MyResource.de-DE.resx" is "MyResource". Project in the example with this article has a default namespace 'Localization
' hence baseName
used here is 'Localization.Localization
'.
- assembly
The main assembly for the resources.
This instantiation can also be done in the constructor of Form1
class if you are sure that Form1
is called only once in your application.
If all the resx files are kept under some folder say Resources as below :
Here Localization.de-DE.resx is kept under Resources folder hence baseName
would be Localization.Resources.Localization
. i.e. DefaultNamespace.FolderName.baseName.
Please note: Localization can be implemented in two ways:
- Creating a satellite assembly for culture specific resource file and using it in the executing assembly
- Creating a file based resource manager which enables accessing and reading from a resource file from a location outside the assembly manifest
and ResourceManager
can be instantiated slightly differently in both the cases. Refer to this article for more details.
Step 6: Update UI
Now, add a method 'UpdateUIControls
' to update all UI controls as below:
private void UpdateUIControls
{
try
{
if (m_resourceManger != null)
{
this.Text = m_resourceManger.GetString("A demo application");
this.lblTrans.Text = m_resourceManger.GetString("String");
this.lblMessage.Text = m_resourceManger.GetString
("This is a demo application for localization");
this.grpLanguages.Text = m_resourceManger.GetString("Select a language");
}
}
catch (System.Exception e)
{
MessageBox.Show(e.Message);
}
}
Call this method from the constructor right after InitializeComponent()
.
Step 7: Implementing Runtime Localization
Now register a CheckedChanged
event of each radiobutton
to a function say OnLanguageChange(...)
and set the appropriate culture to the UI and update UI controls with new culture as below:
private void OnLanguageChange(object sender, EventArgs e)
{
RadioButton radioButton = sender as RadioButton;
string culture = string.Empty;
switch(radioButton.Text)
{
case "U.S. English (en-US)":
culture = "en-US";
break;
case "German-Germany (de-DE)":
culture = "de-DE";
break;
case "French - France (fr-FR)":
culture = "fr-FR";
break;
case "Portuguese - Brazil (pt-BR)":
culture = "pt-BR";
break;
}
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(culture);
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(culture);
UpdateUIControls();
}
Specifying a Default Resource
If the application has default resource embedded into the main assembly, then resources from that file will be treated as default resources. But in case if you want to set one of the satellite resource file as a default one, then you need to add the below lines in AssemblyInfo.cs:
[assembly: NeutralResourcesLanguageAttribute
("en-US", UltimateResourceFallbackLocation.Satellite)]
Alternatively, this can be done programmatically as below:
if (m_resourceManger != null && m_resourceManger.GetResourceSet
(Thread.CurrentThread.CurrentUICulture, true, false) == null)
{
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
}
CurrentCulture Vs CurrentUICulture
The property CurrentCulture
is for setting the culture that is used with formatting and sort options, whereas the property CurrentUICulture
is used for the language of the user interface. While CurrentUICulture = CurrentCulture
is fine, the opposite is not true, CurrentCulture = CurrentUICulture
will result in an exception of type System.NotSupportedException
if CurrentUICulture
has been set to a neutral culture. More details follow here.
History
- 02/18/2010: Initial draft