Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF

WPF Localization

Rate me:
Please Sign up or sign in to vote.
4.56/5 (5 votes)
10 Sep 2009CPOL5 min read 105K   28   18
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:
C#
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:
C#
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:

C#
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:

C#
 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:

C#
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.

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
GeneralUsing RESX Resources - Steps 9 Pin
Bertron 510-Sep-09 23:15
Bertron 510-Sep-09 23:15 
GeneralRe: Using RESX Resources - Steps 9 Pin
Sacha Barber10-Sep-09 23:37
Sacha Barber10-Sep-09 23:37 
GeneralRe: Using RESX Resources - Steps 9 Pin
Sacha Barber10-Sep-09 23:39
Sacha Barber10-Sep-09 23:39 
GeneralRe: Using RESX Resources - Steps 9 Pin
Bertron 511-Sep-09 0:28
Bertron 511-Sep-09 0:28 
GeneralRe: Using RESX Resources - Steps 9 Pin
Sacha Barber11-Sep-09 1:30
Sacha Barber11-Sep-09 1:30 
GeneralRe: Using RESX Resources - Steps 9 Pin
Bertron 511-Sep-09 1:51
Bertron 511-Sep-09 1:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.