Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF Localization

0.00/5 (No votes)
11 Sep 2009 1  
WPF localization

Recently at work, I was asked to look into Localization techniques when working with WPF/XAML. There are some excellent sources around that cover the various different techniques such as this excellent article which outlines the following techniques.

Using Locbaml

Locbaml is a localization tool that Microsoft has as a free download, available from here. This tool may be used to translate compiled XAML (binary XAML, BAML) resources into a CSV and may be used to translate a CSV file into a new DLL.

Here are the steps involved with doing this:

  1. Create a normal WPF App, and use a ResourceDictionary for strings
  2. Use MergedDictionary in App.resources
  3. Add [assembly: NeutralResourcesLanguage(”en-US”, UltimateResourceFallbackLocation.Satellite)] in AssemblyInfo.cs
  4. Add new <UICulture>en-US</UICulture> in all ProjectGroup tags for current project
  5. Open command prompt at project level, run msbuild /t:updateuid <PROJECT NAME>.csproj
  6. Open command prompt at project level, run msbuild /t:checkuid <PROJECT NAME>.csproj
  7. Build it from VS
  8. Obtain 1st cultures CSV file, LocBaml.exe /parse en-US/<PROJECT NAME>.resources.dll /out:<PROJECT NAME>.csv
  9. Edit CSV, save as new csv file name, such as <PROJECT NAME><nn-XX CULTURE STRING>.csv
  10. Need to do translation for new culture. Do something like LocBaml.exe /generate en-US/HelloWorld.resources.dll /trans :HelloWorldfr-CA.csv /out:c:\ /cul:fr-CA
  11. Create new <nn-XX CULTURE STRING> directory in Bin folder
  12. Copy <PROJECT NAME>.resources.dll from step 10, which was placed on C: to the folder created in step 11
  13. Create a constructor for App class like follows:
public App()
{
    string culture = "fr-CA"; //Where this is the culture you want to use
    System.Threading.Thread.CurrentThread.CurrentUICulture =
        new System.Globalization.CultureInfo(culture);
    System.Threading.Thread.CurrentThread.CurrentCulture =
        new System.Globalization.CultureInfo(culture);
}

Advantages of this Method

  • You can do it at the end of the project
  • You don’t have to interfere with your main Assembly to install new cultures

Disadvantages of this Method

  • It is laborious and error prone
  • Not well suited for teams of developers as a new CSV file is created each time you use the LocBaml tool, so you need to know what was different between newly exported CSV file and the translation CSV file

Using RESX Files

An alternative approach is to not use Locbaml at all and simply use RESX (resource files).

Here are the steps involved with doing this:

  1. Create a normal WPF App
  2. There will already be a resx file within the Properties folder, so simply add a string value to that for the text you want to localize. Make sure to select public from Access Modifier drop down
  3. Within any Window that you want to use localized text, add a clr-namespace such as xmlns:properties=”clr-namespace:HelloWorldUsingResXFiles.Properties”
  4. Hook up a items text to a resource file text, like <Button Margin=”10″ Name=”button1″ Content=”{x:Static properties:Resources.MainButtonText}”></Button>
  5. Copy and paste the existing resx file within the Properties folder to a new file with the following name Resources.nn-XX.resx OR Resources.nn.resx
  6. Modify the App.cs file to include whatever culture should be used, for example:
public App()
{
    HelloWorldUsingResXFiles.Properties.Resources.Culture = new CultureInfo("fr-CA");
}

Advantages of this Method

  • Very easy to implement

Disadvantages of this Method

  • All RESX files must be stored in Main Assembly
  • As all cultural resource files are in Main Assembly, may make doing automated build harder

Another Way: Use Dynamically Loaded Assemblies With ResourceDictionaries

I just couldn’t help but think there was a better way, so I had a think about this, and it seems just like the way skins are applied in WPF. You wire certain control properties to DynamicResources where a new XAML file (the skin) is loaded which effects the control that is referencing the resource. So I had a think about it and thought why not do the same for Localization strings. So that’s what I did. Here is the idea.

Keep a Main Assembly which expects to get localization resources from a ResourceDictionary, and then at runtime, load in an extra Assembly which contains just the ResourceDictionaries for the matching CurrentCulture where the application is running.

The loaded Assemblies ResourceDictionaries will be used to replace the default one within the main Assembly.

  1. Create normal WPF App, and use a ResourceDictionary for strings
  2. Use MergedDictionary in App.resources
  3. Make sure any localizable controls using a DynamicResource like <Label Content=”{DynamicResource label1}”/>
  4. Create a new culture assembly with a sensible name, such as Culture_fr-CA
  5. For assembly created in step 4, create mirror image ResourceDictionary that matches the original Assemblies ResourceDictionary but with translated strings
  6. Compile the culture assembly to some folder under main Assemblies bin\debug folder. This demo assumes this folder is called “CultureFiles
  7. When main app runs, get the current culture and Load the matching Assembly from disk
  8. From the currently loaded Culture Assembly, extract the ResourceDictionary
  9. Using the extracted ResourceDictionary, replace the current Application.MergedResources dictionary with the newly extracted culture ResourceDictionary
  10. All controls that refer to the Dynamic resources should be ok now with new cultural strings

In my main App, I simply have the following ResourceDictionary:

1:  <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
2:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3:      xmlns:system="clr-namespace:System;assembly=mscorlib">
4:      <system:String x:Key="label1″>label1</system:String>
5:      <system:String x:Key="label2″>label2</system:String>
6:      <system:String x:Key="label3″>label3</system:String>
7:      <system:String x:Key="label4″>label4</system:String>
8:      <system:String x:Key="label5″>label5</system:String>
9:  </ResourceDictionary>

Then, in the App file, I have the following:

 1:  <Application x:Class="LooseXAML.App"
 2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4:      StartupUri="Window1.xaml">
 5:
 6:      <Application.Resources>
 7:          <ResourceDictionary>
 8:              <ResourceDictionary.MergedDictionaries>
 9:                  <ResourceDictionary Source="Dictionary_en-US.xaml" />
10:              </ResourceDictionary.MergedDictionaries>
11:          </ResourceDictionary>
12:      </Application.Resources>
13:
14:  </Application>

Then somewhere, I may reference these resources like:

1:  <Label Content="{DynamicResource label1}"/>

So that is all pretty standard stuff. Next, I created a new Assembly with a single ResourceDictionary in it. I am using the culture string as part of the Assembly name and or the actual ResourceDictionary.

37339/image10.png

The actual ResourceDictionary MUST contain the name items as the main Assembly.

1:  <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
2:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3:      xmlns:system="clr-namespace:System;assembly=mscorlib">
4:      <system:String x:Key="label1″>french label1</system:String>
5:      <system:String x:Key="label2″>french label2</system:String>
6:      <system:String x:Key="label3″>french label3</system:String>
7:      <system:String x:Key="label4″>french label4</system:String>
8:      <system:String x:Key="label5″>french label5</system:String>
9:  </ResourceDictionary>

From here, I compile this assembly and put it somewhere visible from the main Assembly, such as bin\debug\CultureFiles.

37339/image-thumb11.png

From there is just a question of loading the separate culture Assemblies and extracting the contained ResourceDictionary and replacing the current Main Assemblies ResourceDictionary. Thus forcing all resourced referencing controls to update with the new resource data.

There is a small demo application that demonstrated this approach here which you may test by clicking on the fr-CA listbox item.

Before:

37339/image-thumb12.png

After:

37339/image-thumb13.png

But you MUST ensure that your current culture is set to French-CANADA via control panel for it to work. This means change region Options AND languages. If you don’t do this, the CurrentCulture will not be correct for the demo code to work.

37339/image-thumb14.png

Advantages of this Method

  • Very easy to implement

Disadvantages of this Method

  • Lots of different Assemblies to manage
  • Culture Assemblies ResourceDictionary entries must match Main Assembly otherwise a lookup may fail at runtime

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here