Click here to Skip to main content
15,868,139 members
Articles / Operating Systems / Windows
Article

Localizing WPF Applications using Locbaml

Rate me:
Please Sign up or sign in to vote.
4.98/5 (47 votes)
1 Mar 2007CPOL25 min read 443.6K   3.2K   122   77
This article shows how to localize a WPF application using the Locbaml utility

A window with just one plain button

Table of Contents

Introduction
Background
What You Will Need To Follow This Tutorial (Software Prerequisites)
Approach #1: Targeted Localization Without Using LocBaml.exe
Creating The WPF Application
Adding a Button to the Window
Adding the string resources
Replacing the button text
Getting a public resources class
Performing the localization
Setting the initial startup language
Responding to the button click
Approach #2: Localization with XAML ResourceDictionary and LocBaml
Adding a UICulture tag pair to the project configuration
Setting the NeutralResourcesLanguage Attribute in the project
Adding the ResourceDictionary to the project
Merging the ResourceDictionary into the application
Adding UId's to your XAML
Extracting the Resources to a .CSV file using LocBaml.exe
Generating the localized assembly and installing it
Setting the current thread's UI Culture
Replacing the button text with a DynamicResource
Where are we now?
Putting up a message box in response to clicking the button
Adding a button handler
Localizing to get the new string
Approach #3: Linked Localized Baml streams with resgen compiled string resources
Adding the .Resx files
Compiling and linking to get your satellite resource DLL
Fixing the button handler to use a Resource Manager
A comparison of the three approaches of localizing WPF applications
Conclusion
References and Acknowledgements
History

Introduction

It is this article's goal to show three different ways of localizing a Windows Presentation Foundation (WPF) .EXE application. Step by step instructions to re-create the three sample applications are provided so that the reader will not get trapped and frustrated by little details; all the i's need to be dotted and all the t's need to be crossed for all of this to work.

A previous incarnation of this article only showed one approach which I had discovered; this approach, while having certain advantages will be presented last. After publishing the original article and posing a question on one of the Microsoft Forums, Michael Weinhardt (Microsoft) sent me a second, simpler way of doing localization using LocBaml.exe [LocBaml.exe is a program from Microsoft we use to localize Binary XAML code]. I was just about to jump into explaining how this works, when a collaborator, C.J. Baker, explained to me how he was doing localization without using LocBaml. His approach is by far the simplest way of doing localization and will be presented first. The end of the article will discuss the pros and cons of each approach. As a consequence, I hope that this article will help other developers who wish to, or are required to localize their WPF applications.

Background

The company I work for is in the process of developing a new software system, and we decided that WPF would be the presentation layer for the new system. Since we sell our products all over the world, being able to localize the UI has been an important asset in the past and is a requirement for the new software system we are designing.

In all three scenarios the example is simple WPF .EXE application that has one main window containing a simple button. Clicking on the button brings up a localized message box.

What You Will Need To Follow This Tutorial (Software Prerequisites)

Approach #1: Targeted Localization Without Using LocBaml.exe

The idea with approach #1 is pretty simple. When authoring the XAML file for the UI in question, replace the text with a static reference to a string property exposed through the ResourceManager class. It is much easier to do this while designing the UI, rather than retrofitting existing UI. One major drawback with the approach I am about to show is that you lose design-time support in Visual Studio and XamlPad. The latest version of Expression/Blend does seem to handle resources better and does give you design-time support. Let us try to follow the steps required for this approach.

Creating The WPF Application

Visual Studio New Project Dialog, creating WPF project

Adding A Button To The Window

Opening the created Window1.xaml, we add a button and resize the main window a bit to make it smaller. The simplistic application xaml code is presented below:

XML
<Window x:Class="WPF_Localize.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF_Localize" Height="155" Width="194">
    <Grid>
        <Button Margin="30,20,35,26" Name="button1">Hello World</Button>
    </Grid>
</Window> 

We can now build the application with Ctrl-Shift-B and run it with Ctrl-F5. You should get a window that looks like this:

A window with just one plain button

Adding the string resources

Now that have the basic application built, we can start localizing it. Visual Studio has already created a file called Resources.resx which you can find in the Properties folder, right under the main solution in the Solution Explorer. Create a string resource for the button text as shown below:

Visual Studio showing the resource editor

Replacing The Button Text

Next, we need to do two things: add an extra namespace xmlns identifier to the Window attributes, and change the button text to point to the resource we have just added. When we do this, the code Window1.xaml code looks as follows:

XML
<Window x:Class="WPF_Localize.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF_Localize" Height="155" Width="194"
    xmlns:properties="clr-namespace:WPF_Localize.Properties">
    <Grid>
        <Button Margin="30,20,35,26" Name="button1">
        "{x:Static properties:Resources.MainButtonText}"
        </Button>
    </Grid>
</Window> 

Compile, run, and you get this lovely dialog free of charge, courtesy of our friends at Microsoft:

General Protection Fault

Since we also feel sorry about the inconvenience, we need courage and a debugger to find a way out of this problem. Running in Debug mode within Visual Studio gets us access to the actual XamlParseException thrown. The beginning of the exception trace reads as follows:

System.Windows.Markup.XamlParseException was unhandled
Message="Cannot create instance of 'Window1'
defined in assembly 'WPF_Localize, Version=1.0.2596.37261,
Culture=neutral, PublicKeyToken=null'. Exception has been thrown by the 
target of an invocation. Error in markup file 'Window1.xaml' Line 1 
Position 9."
Source="PresentationFramework"
etc...

This is actually a pretty good clue to the actual problem: another assembly called PresentationFramework is trying to create something, and the only thing we've added is the reference to the string. We can now take a look at the code-behind file for the resources. Yes, Visual Studio was kind enough to generate a class called Resources; you can find it beneath the Resources.resx file in the Resources.Designer.cs file. (It is located under Properties, remember?) If we take a look at the file, we see that both our that the class and the static string property are marked as internal, meaning that only our assembly has access to it.

C#
[global::System.Runtime.
CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
...
internal static string MainButtonText {
    get {
        return ResourceManager.
        GetString("MainButtonText", resourceCulture);
    }
}

Getting A Public Resources Class

It would be very convenient to have both a public class and public members, so that we can get at the string. Search and replacing internal with public is not a good idea, because the code behind file is auto generated by Visual Studio. At the next edit of our Resources, everything would be returned to internal.

Fortunately, another Codeproject author has already tackled the problem with a custom code generator that makes everything public. Using it is very simple; close Visual Studio, download the installer, run it, and make one more change to the Resources.resx properties. You can find the article and its download here. Now locate the file Resources.resx, right click and choose properties. Change the Custom Tool Property so that it reads ResXFileCodeGeneratorEx, by adding 'Ex' to the tool name as shown below:

Resources.resx properties window

If you do a build or if you change a string and save the .resx file, Visual Studio invokes the new code generator which produces the the code-behind file with the public class and public string property.

C#
// This class was auto-generated by the StronglyTypedResourceBuilderEx 
// class via the ResXFileCodeGeneratorEx custom tool.
// To add or remove a member, edit your .ResX file then rerun the 
// ResXFileCodeGeneratorEx custom tool or rebuild your VS.NET project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute
("DMKSoftware.CodeGenerators.Tools.StronglyTypedResourceBuilderEx", "2.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public sealed class Resources {
...
public static string MainButtonText {
    get {
        return ResourceManager.
        GetString("MainButtonText", _resourceCulture);
    }

If you now attempt to look at your Window in the designer view, the limitations of the Visual Studio 'Orcas' extension become apparent:

Visual Studio chocking trying to render xaml referencing static resources

However, the latest incarnation of Microsoft Expression/Blend (Beta 2) seems to handle our window fine; if you run the program (which you can by hitting F5 in Expression) you will get your normal English button text being read dynamically from the resource.

Visual Studio chocking trying to render xaml referencing static resources

Performing The Localization

But we are not done yet. We have yet to localize the program into another language, and address what happens when we actually click the button. Let us proceed with the following steps:

  1. Select Resources.resx, and copy it to the clipboard using Ctrl-C.
  2. Use Ctrl-V to paste a copy into the project.
  3. Rename the Copy of Resources.resx; to Resources.de-DE.resx, by selecting the file, right clicking and choosing rename. "de-DE" happens to be the Culture/Language code for German-Germany; you can find a complete listing of all the culture identifiers here.
  4. Double-click the Resources.de-DE.resx [should be in the properties folder!], and provide the localized translation, in this case the free translation "Herzliche Grüsse!"
  5. Click the Save icon, and/or recompile.

If you now examine the Resources.de-DE.Designer.cs file, you will notice that it is completely empty! I struggled with this, and thought that the new Resource Generator tool was broken. It turns out it is not; this is done on purpose. The code-behind file is needed only once, so it only gets generated once. The tool pays attention to the naming convention [basename].[Locale Identifier].resx; if there is a valid locale identifier, the code-behind does not get generated. Another thing to mention, is the fact that Visual Studio is smart enough to generate our satellite DLL called WPF_Localize.resources.dll in a folder called de-DE beneath \bin\Debug.

Setting The Initial Startup Language

Now we only need to do one more thing to get our German version of the button text that we have localized: a call to set Culture property in the automatically generated code-behind file. At this point, it should be noted that this setting of the language/culture needs to happen before the WPF Window/Page is created. The safest place to put this code is in the constructor of the application, because we can be fairly sure that this will be executed before any Window/WPF code gets to run.

C#
public partial class App : System.Windows.Application
{
    public App()
    {
        WPF_Localize.Properties.Resources.Culture = new CultureInfo("de-DE");
    }
}

Running the program now gives us the much anticipated German version of the program. Since we did not localize the application title, it remains the same for both the English and German versions. Its also possible to start out with one culture, and set the [app name].Properties.Resources.Culture to a different culture. If you do so, and have a corresponding .resx file, and you show your next window, it will show up in the new language. In this way, we can switch languages on the fly.

Responding To The Button Click

What now remains is to add a Click="OnButtonClicked" property to the button, and a button handler as shown below to the Window1.xaml.cs file.

C#
private void OnButtonClick(object sender, RoutedEventArgs e)
{
    MessageBox.Show(Properties.Resources.MessageBoxText);
}

Of course, in order for this to compile, we need to add a MessageBoxText string to both the Resources.resx which gets us the code-behind public property, and to the Resources.de-DE.resx, which gets us the localized string. Running the program yields the desired result, as shown below and concludes the demonstration of Approach #1.

Approach#1 running, showing text in German

Approach #2: Localization with a XAML ResourceDictionary, and LocBaml

The idea behind solving the problem with Approach #2 is a slightly different one. It involves coercing Visual Studio in creating a satellite DLL, creating a <ResourceDictionary> which contains <system:String> entries, and referencing this string in the UI trough a "{DynamicResource string}". Once this is done, we use LocBaml to extract the identifiers to be translated (which includes the system:string); perform the translation, and use LocBaml again to generate our satellite DLL. Does this sound as confusing to you as it sounds to me writing it? A demonstration of the individual steps required might help.

Create a WPF Window application, as shown in Approach #1. The window should have the "Hello World" button ready to go, as described above.

Adding a <UICulture> Tag Pair To The Project Configuration

We now need to adjust the project configuration, so that the LocBaml tool can be used. For this, close the solution of the project that was just created. This involves opening up the project's .csproj file and adding a <UICulture>en-US</UICulture> line to the <PropertyGroup> section of the file. To open the .csproj file, choose File->Open File... add the line as per image below.

How to add the UICulture tag

Now save the .csproj file, close the window, and reload the original solution.

Setting The NeutralResourcesLanguage Attribute In The Project

There are two ways to set the NaturalResourcesLanguage assembly attribute, either through the UI or through code. Doing it through code is easier, since it only involves un-commenting one line. Its also safer, since it the UltimateResourceFallbackLocation can be specified. Let us quickly do this: In the project, find & open the Properties Folder. Open the AssemblyInfo.cs file. Under the first block of [assembly:] attributes, you will find a commented instruction block, followed by the following line, which you need to uncomment:

C#
[assembly: NeutralResourcesLanguage("en-US", 
           UltimateResourceFallbackLocation.Satellite)] 

You might ask what this is for? This attribute gives instructions to the CLR loader on how to find the right resource DLL for the current culture, and what to do if it can't find a matching culture. It also basically says to the system: If you can't find a matching culture resource DLL, just use the one with the "en-US" culture. The following link gives a bit more info on the resource fallback process.

Recompile and re-run your application, to make sure it still runs OK. If you have edited the .csproj file correctly in step 2 you should get a US culture resource DLL built along with your .EXE file, sitting in its own "en-US" folder under [PathToProject]bin\Debug. Looking at the output window (View->Output) will also show you that AL.exe, the .NET assembly linker tool was invoked to create your satellite resource DLL.

Adding the ResourceDictionary to the project

Right click the WPF_Localize project, and add a new file of type ResourceDictionary. Modify the file, so that it looks as the one below and save it as StringResources.xaml.

XML
<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:system="clr-namespace:System;assembly=mscorlib">
    <system:String x:Key="buttonText">Hello World!</system:String>
</ResourceDictionary>

Merging The ResourceDictionary Into The Application

Before we can use the string we have just created in XAML, we need to make it "public" for other XAML code. To do this, modify the App.xaml so that it looks like this:

XML
<Application x:Class="StringLocalizationSample.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Window1.xaml" >
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="StringResources.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Adding UId's To Your Xaml

The next step is to go through your .xaml file, and add a UId attribute all fields that need to be localized. That was the bad news. The good news is that a tool, msbuild, can do this for you automatically. This is how to do it:

  1. Start a Visual Studio command prompt, or use View->Shell Window, navigate to the project folder using 'cd'
  2. Type: msbuild /t:updateuid WPF_Localize.csproj [Highlight below]

Using msbuild from command line window

Check to make sure that the UId's have been embedded in the .xaml file by looking at the xaml source:

XML
<Window x:Uid="Window_1" x:Class="WPF_Localize.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF_Localize" Height="155" Width="194">
    <Grid x:Uid="Grid_1">
        <Button x:Uid="button1" Margin="30,20,35,26" Name="button1">Hello World
        </Button>
    </Grid>
</Window>

Compile the project to make sure nothing has been broken. There is also a "CheckUid" target to check for duplicates and another target to remove the UIDs if you don't need them anymore.

Extracting The Resources To A .CSV File Using Locbaml.exe

If you have not done so already, download LocBaml.exe from the project files (see link at the top of the page). [The file is in the top level folder of the .zip file]. If you are interested in recompiling LocBaml yourself, you'll need the WPF samples from the SDK; LocBaml is located under [PathToWPFSamples]\GlobalizationLocalization\LocBaml. Follow the following steps to extract the localizable resources to a .CSV file.

  1. Copy LocBaml.exe to the [Project]\bin\Debug\en-US directory
  2. Copy the executable WPF_Localize.exe to the same folder
  3. Open a command prompt, and navigate the same directory
  4. Use the following command to generate your .CSV file: locbaml /parse WPF_Localize.resources.dll /out:trans.csv

If you have done things correctly, you should be able to see the .csv file and open it in Excel. Be mindful that not all versions of Excel support saving in Unicode, which could be important if you plan to translate to a far-east language. In this case, use a unicode-compatible text editor. If you don't need to use Unicode, you can save as a normal .CSV file.

The file that was just produced by the tool [you will get a garbage file if you did not follow the above instructions first - it is usually not impossible to find the source of the problem by debugging LocBaml.exe] has the following layout:

Showing Excel, editing the CSV file

  • [Baml Name] Identifies the BAML (compiled XAML) stream; values are in the form AssemblyManifestStreamName:SubStreamName
  • [ResouceKey] Identifies localizable resouce; values are in the form Uid:Element Type.$Property
  • [LocalizationCategory] An entry from the LocalizationCategory enumeration, indicating the type of content.
  • [Readable] Indicates if the resource is visible for translation.
  • [Modifiable] Indicates whether this value can be modified in the translation.
  • [Comments] Localization comments.
  • [Value] The value of this resource.

We will go ahead and modify cell G2, the Window Title and cell G5, the button text, filling the values "Localisation avec WPF", and "Salut tout le monde", the French equivalents. Save the spreadsheet as .CSV[Windows]. You will need to do this for every language you intend to support.

Generating The Localized Satellite Dll And Installing It

Next, we need to combine the translated info with the original US satellite DLL to generate our french satellite DLL. Again, this is done through a command line command using locbaml.

  1. Open a command prompt, and navigate to the en-US directory
  2. Create a sister directory to en-US named fr-FR (name is important!) using the command md ..\fr-FR. You can find a listing of culture identifiers here.
  3. Close Excel and use the command: locbaml /generate /trans:trans.csv /out:..\fr-FR /cul:fr-FR WPF_Localize.resources.dll to generate the french satellite DLL.

Setting the Current Thread's UI Culture

We have completed the localization of the code, however if you attempt to run it, you probably won't see our translated UI. Why not, you might ask?. Only French users will see it, if their default locale is French. How could one programmatically switch between languages?. The trick here is to change the current Thread's CurrentUICulture before any Windows are displayed. This must be done in the constructor of the App class as shown below:

C#
public App()
{
    string culture = "fr-FR";
    // culture = "en-US";
    System.Threading.Thread.CurrentThread.CurrentUICulture = 
        new System.Globalization.CultureInfo(culture);
    System.Threading.Thread.CurrentThread.CurrentCulture = 
        new System.Globalization.CultureInfo(culture);
}

You could, of course read the "fr-FR" string from an application .config file that is stored on a per user basis.

Replacing The Button Text With a DynamicResource

Next, we need to replace our static "Hello World" button text with a reference to our newly created ResourceDictionary. The resulting code looks like this:

XML
<Window x:Uid="Window_1" x:Class="WPF_Localize.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF_Localize" Height="154" Width="194">
    <Grid x:Uid="Grid_1">
        <Button x:Uid="Button_1"
            Margin="30,20,35,26"
            Name="button1"
            Content="{DynamicResource buttonText}">
        </Button>
    </Grid>
</Window>

Translated app

Where Are We Now?

What we have now is a localized application that has all UI elements displayed to the user through XAML translated into the culture(s) we have decided to support. The good thing about this approach is that we can decide to support a single language now, and add other languages after the product has been released. In theory if we have a good XAML design, we just need a new translation, and re-generate the satellite DLL appropriate for the culture we are targeting. We'll also need to update our application installer or add-on installer to install the DLL in the correct folder. As soon as the user switches the current thread to the new locale, and restarts, the UI will display the new localized UI. Of course your new UI will display correctly only if you allow WPF to use its auto-layout features; remember that some label text can be significantly longer in another language. The following MSDN blog as a good article entitled "Best Practices for globalization and Localization in WPF", which speaks to XAML UI design as it relates to localization and globalization; the article can be found here. In practice, it is always important to test the application, and have a native speaker review the finished product; translation glitches do happen and testing is the only way to make sure you end up with a high quality product.

Putting Up A Message Box In Response To Clicking The Button

The next thing I would like my application to do is to respond to the mouse click and to bring up a little message box. This leads us to the next problem: How do I localize the MessageBox.Show() call, so that the user gets the message in the appropriate language? Let us continue the tutorial, to enable the application to show a localized string appropriate to the currently set culture.

Adding A Button Handler

First, lets add a message handler to the system. Current versions of Visual Studio 2005 are a bit primitive in that regard, users must create the message handler manually. Gone are the "Double Click on the button to get a default handler written automatically. Adding the handle is a two step process, adding a click handler in the XAML, and writing the handler in the code-behind file. This is what the Window1.xaml file looks like after adding the click handler:

XML
<Window x:Uid="Window_1" x:Class="WPF_Localize.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF_Localize" Height="154" Width="194">
    <Grid x:Uid="Grid_1">
        <Button x:Uid="Button_1"
            Margin="30,20,35,26"
            Name="button1"
            Content="{DynamicResource buttonText}"
            Click="OnButtonClicked">
        </Button>
    </Grid>
</Window>

And the corresponding code-behind file:

C#
// add this to the Window1.xaml.cs, below the Window1() // constructor
private void OnButtonClick(object sender, RoutedEventArgs e)
{
    string localizedMessage = (string)Application.
    Current.FindResource("messageText");
    MessageBox.Show(localizedMessage);
}

Localizing Again To Get The New String

We have now used a new string resource, called 'messageText' but we have not made the addition of the string to the file StringResources.resx. After the addition (make sure it gets a unique x:UId), we have to re-localize extracting to a new .CSV file, merging the old and new .csv translations, adding a translation for the new string, and running LocBaml.exe a second time to generate our French satellite resource DLL that contains the new string. It is thus advantageous to do the localization when the UI design has stabilized. The result is an application that has both the main window and the messagebox localized to the French language. This concludes Approach #2 for WPF localization.

Approach #3: Linking Localized Baml Streams With Resgen Compiled String Resources

This approach shares many steps with Approach #2, but it does not use a WPF ResourceDictionary, choosing instead to store the resources in a .resources stream. The UI is still localized with LocBaml as in approach #2, but strings are stored in .resx files. Since many steps from approach two are the exact same ones, please follow the steps under the following headings of Approach#2:

  1. Create a WPF application, as shown in Approach#1, "Creating The WPF Application"
  2. Add a button to the Window UI, the click handler in the XAML, and the handler in the code-behind file.
  3. Add A <UICulture> to the project configuration.
  4. Set the NaturalResourcesLanguagesAttribute in the project.
  5. Add UId's to your Xaml.
  6. Extract the resources to a .CSV file using LocBaml.exe.
  7. Generate your Localized Satellite Dll and Install it.
  8. Set the current thread's UI Culture. (add an application constructor)

Now run your project. If you have done things correctly, you should get a French main UI, and an English message box text.

Adding The .Resx Files

We now need a string table to hold all the strings that we are going to localize. For now we only have one message, but since we intend to support the en-US (which is also the invariant culture) and French (fr-FR), we need two resource files. Let's do this now. Follow the following steps to get your new French resource files:

  1. In the solution open Properties and highlight Resources.resx
  2. Do Ctrl-C followed by Ctrl-V, this gets you a Copy of Resources.resx
  3. Highlight the copy, press F2 to rename the file to Resources.fr-FR.resx
  4. Double click the new file to get the .resx editor, enter "Le message francais" in the String1 value.

Compiling And Linking To Get Your Satellite Resource Dll

Now that we have the .resX files that we need (remember, it is important to use the correct culture identifiers), it is time to compile and link. The goal of the following steps is to get a satellite resource DLL that has both the localized .BAML (compiled XAML) and the string table as assembly module resources. All of this only works if all of the details are correct, an explanation follows:

cls
resgen .\Properties\Resources.resx
copy .\Properties\Resources.resources bin\debug\en-US
cd .\bin\Debug\en-US
copy ..\wpf_localize.exe .
REM COPY LOCBAML.EXE to C:\ , otherwise this won't work!
copy C:\LocBaml.exe .
locbaml /generate ..\..\..\obj\debug\WPF_Localize.g.en-US.resources 
        /tran:trans.csv /cul:en-US /out:.
ren Resources.resources WPF_Localize.Properties.Resources.en-US.resources
al /template:"..\WPF_Localize.exe" /embed:WPF_Localize.g.en-US.resources 
   /embed:WPF_Localize.Properties.Resources.en-US.resources /culture:en-US 
   /out:WPF_Localize.resources.dll
cd ..\..\..

The batch file, which is run from the project directory (you can set a post-build step in Visual Studio) does the following. We first compile the string resources. We then copy the main executable to the working folder, which needs to be done so that the tools run properly. Then we copy locbaml.exe into the current folder; woe to you if you forget to do this. We now use locbaml.exe to localize the compiled xaml resources [WPF_Localize.g.en-US.resources] that were generated automatically during the build. We now need rename the actual string resource, so that the ResourceManager from the .exe can actually find the strings as resources. I found out the correct naming sequence by writing and localizing a command line application with strings only, and disassembling the resource assembly manifest. Finally we call the linker, al.exe; we simply ask to combine the string resources along with the translated .baml resources to generate our satellite resource dll. You would add a /keyfile:[path to keyfile.snk] to the al command line to sign you satellite resource.

Below, I present the batch file, called doit_fr.bat used to generate the French resource assembly. Note that it points to the US resources when generating the French resources, the US resources are treated as a template, which is copied and localized.

cls
resgen .\Properties\Resources.fr-FR.resx
copy .\Properties\Resources.fr-FR.resources bin\debug\fr-FR
cd .\bin\Debug\fr-FR
copy ..\wpf_localize.exe .
REM Copy LOCBAML.EXE to C:\ for the script to work
copy C:\LocBaml.exe .
REM Note: We are referring to the WPF_Localize.g.en-US.resources
REM as the template, even though we are generating the French
locbaml /generate ..\..\..\obj\debug\WPF_Localize.g.en-US.resources 
        /tran:trans.csv /cul:fr-FR /out:.
ren Resources.fr-FR.resources WPF_Localize.Properties.Resources.fr-FR.resources
al /template:"..\WPF_Localize.exe" /embed:WPF_Localize.g.fr-FR.resources 
   /embed:WPF_Localize.Properties.Resources.fr-FR.resources /culture:fr-FR 
   /out:WPF_Localize.resources.dll
cd ..\..\..

Be careful, your diligently crafted resource DLL is usually overwritten [with the US containing-baml resources only] if you compile, its a good idea to do this as a post build step. I suggest that you run the application outside of Visual Studio - this guarantees that your satellite DLL's willl not be overwritten.

Fixing The Button Handler To Use A Resource Manager

The last step, now that we have the resource DLL is to actually use them. Since Visual Studio has already created a Properties.Resources class that exposes our string table, we can just use it. Remember, if you want to change the locale, this is done in the App.xaml.cs.

C#
private void OnButtonClick(object sender, RoutedEventArgs e)
{
    MessageBox.Show(WPF_Localize.Properties.Resources.String1);
}

A comparison of the three approaches to localizing WPF applications

The first approach uses x:Static [public string property in .Resx file which allows you to translate only the identifiers that you choose on the form. Of the three approaches, it is also one of the simplest to use and understand. Having the strings in a Resource .resx file is also a plus. Also, with this approach, you can switch languages on the fly by setting the Resources.Culture; any new WPF windows displayed will show the new language. This is not possible (as far as I understand) in the other two approaches. One of the drawbacks of this way of doing things is that you must replace the strings in your XAML file as you localize, which is not required in the third approach. The biggest problem with this approach however is that there is no [at the time of writing] Visual Studio design-time support; your Window won't load at design time and you will have to run your application every time to see changes in the UI. Also, localizing the program after the fact may be more difficult with this approach. We are using this approach in one of our applications, and it works reliably.

The second approach used a ResourceDictionary combined with a DynamicResource in combination with LocBaml.exe to get to the localization. It suffers from the same lack of designer tool support in Visual Studio and is more complex than the first approach since we have to use LocBaml.exe. Personally, I don't like how the strings are defined in the ResourceDictionary: its less convenient than using a .resx file which comes with a Visual Studio supported editor. Its not possible to switch languages on the fly with this approach. Using this method of localization after the fact (as opposed to doing it during the development) will be a lot of work.

The third approach uses both a .Resx file for strings, and LocBaml for localizing the UI and uses the .NET linker to link the resources together. This is by far the most difficult approach, as it requires some understanding on how resources are named and used. Its advantages are that the Visual Studio designer works, and that you don't need to change your XAML file at all. Another advantage of using this method is that you can first finish your application, and do the localization of the UI afterwards [You still need to isolate all strings given to the user in code-behind files] Disadvantages include having to translate the entire UI because we are using LocBaml. Its also not possible to switch languages on the fly with this approach.

Conclusion

Its a good idea to worry about localisation early on in a project, developing with at least one other language or pseudo-language. By this I mean that you could add "french-" to the beginning of every identifier; this way you don't need to know french to see if the UI changed. UI designers can also make sure that longer text wrapping will be OK - although WPF is way better than Forms at doing layout, UI designers must still design with auto-layout in mind. The advantage of installing the localized DLL's in their own folder is that you can decide to support a new language just by installing the appropriate resource DLL later on. Finally, if you know of a better/easier way to do this type localization, please, let me know!

References and Acknowledgements

Thanks to Wei-Meng Lee and his article on www.devx.com which shows how to do a similar thing with Visual Basic .Net. Thanks to C.J. Baker and Michael Weinhardt for providing approaches to solving the WPF localization problem.

History

  • February 16, 2007 - Added a table of contents.
  • February 14, 2007 - Major rewrite of the article, adding two other approaches.
  • January 24, 2007 - Revised article and added the section on how to localize the MessageBox message.
  • Initial Version, January 20, 2007

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) AB SCIEX
Canada Canada
I was born and grew up in Northern Germany grew up in Quebec in a French Language environment. I finished High School in Fergus, Ontario. After a 4 year training as a Pipe Organ Builder in Germany, I returned to Canada to get a B.Sc. in Computer Science. I'm currently working for a company called AB SCIEX working on Mass Spectrometer Software, am married, and have three often wonderful children. What you believe in matters - I am a follower of Jesus Christ - we attend a German-Lutheran congregation in downtown Toronto.

Comments and Discussions

 
GeneralRe: Thanks! Pin
Friedrich Brunzema26-Jan-07 13:51
Friedrich Brunzema26-Jan-07 13:51 
GeneralRe: Thanks! Pin
Josh Smith26-Jan-07 15:19
Josh Smith26-Jan-07 15:19 

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.