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:
- Create a normal WPF App, and use a
ResourceDictionary
for string
s
- Use
MergedDictionary
in App.resources
- Add
[assembly: NeutralResourcesLanguage(”en-US”, UltimateResourceFallbackLocation.Satellite)]
in AssemblyInfo.cs
- Add
new <UICulture>en-US</UICulture>
in all ProjectGroup
tags for current project
- Open command prompt at project level, run msbuild /t:updateuid <PROJECT NAME>.csproj
- Open command prompt at project level, run msbuild /t:checkuid <PROJECT NAME>.csproj
- Build it from VS
- Obtain 1st cultures CSV file, LocBaml.exe /parse en-US/<PROJECT NAME>.resources.dll /out:<PROJECT NAME>.csv
- Edit CSV, save as new csv file name, such as <PROJECT NAME><nn-XX CULTURE STRING>.csv
- 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
- Create new <nn-XX CULTURE STRING> directory in Bin folder
- Copy <PROJECT NAME>.resources.dll from step 10, which was placed on C: to the folder created in step 11
- Create a constructor for App class like follows:
public App()
{
string culture = "fr-CA";
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:
- Create a normal WPF App
- 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
- Within any Window that you want to use localized text, add a
clr-namespace
such as xmlns:properties=”clr-namespace:HelloWorldUsingResXFiles.Properties”
- Hook up a items text to a resource file text, like
<Button Margin=”10″ Name=”button1″ Content=”{x:Static properties:Resources.MainButtonText}”></Button>
- 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
- 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
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.
- Create normal WPF App, and use a
ResourceDictionary
for string
s
- Use
MergedDictionary
in App.resources
- Make sure any localizable controls using a
DynamicResource
like <Label Content=”{DynamicResource label1}”/>
- Create a new culture assembly with a sensible name, such as
Culture_fr-CA
- For assembly created in step 4, create mirror image
ResourceDictionary
that matches the original Assemblies ResourceDictionary
but with translated string
s
- Compile the culture assembly to some folder under main Assemblies bin\debug folder. This demo assumes this folder is called “CultureFiles”
- When main app runs, get the current culture and Load the matching Assembly from disk
- From the currently loaded Culture Assembly, extract the
ResourceDictionary
- Using the extracted
ResourceDictionary
, replace the current Application.MergedResources
dictionary with the newly extracted culture ResourceDictionary
- 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
.

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.

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:

After:

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.

Advantages of this Method
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