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.
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.
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",
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 '
baseName used here is '
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
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
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)
Call this method from the constructor right after
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;
case "U.S. English (en-US)":
culture = "en-US";
case "German-Germany (de-DE)":
culture = "de-DE";
case "French - France (fr-FR)":
culture = "fr-FR";
case "Portuguese - Brazil (pt-BR)":
culture = "pt-BR";
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(culture);
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(culture);
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:
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
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
CurrentUICulture has been set to a neutral culture. More details follow here.
- 02/18/2010: Initial draft