Globalizing and Localizing a WPF application

This article describes how to localize a WPF application using the WPF Localization Extension project from Codeplex. On one hand, this is a fine tool. On the other hand, it's a crying shame that WPF developers need to rely on external sources for something as basic as localization. As you know, localization support is nicely integrated in Microsoft's other development stacks, like WinForms and ASP.NET. So far for the ranting, let's get to the solution.

Terminology

Globalization is making your application ready to be localized. This boils down to separating culture-specific elements (texts, images, colors, ...) from the code. The globalization code analysis rules may help you to do this.

Localization is tailoring your application toward a specific language and region by providing these culture-specific elements (e.g. in a resource file or a database).

Options for WPF localization

These are a couple of options for localizing a WPF application:

  • use the clumsy and error prone LocBaml tool. Don't get me wrong: I really like the idea of skipping the globalization step and then do localization as an afterthought. But just read the intro of this article for a list of reasons NOT to use LocBaml,
  • use classic RESX files, or
  • use WPF ResourceDictionaries.

More details about the pros and cons of these alternatives can be found here.

The WPF Localization Extension project

The WPF Localization Extension allows you to localize your application using classic .resx files. The project revolves around a custom markup extension that allows you to declaratively (in XAML) bind control properties to localized resources. The library offers a lot of functionality, but more important: it is well-written and well-documented (at least in its source code). I built a small sample application that walks through some of its features. Here's how this application looks like. You find its source code at the end of this article:

The project is decorated with the following resource files: 

Declarative Localization

The markup extension and its namespaces are registered by the following declaration on top your XAML file (you might want to change the project's source code to choose a more appropriate namespace):

xmlns:lex="http://schemas.root- project.org/xaml/presentation"


You can now set up a binding from a control's property to a resource, like this:

<TextBlock Text="{lex:LocText Key=Tomato, Dict=Translations, Assembly=U2UConsult.WPF.Localization.Sample}" />


It's possible to ignore the current culture, and force a specific culture for a binding, like this:

<TextBlock Text="{lex:LocText Key=Duck, Dict=Translations, Assembly=U2UConsult.WPF.Localization.Sample, ForceCulture=nl}" />


My sample application only fetches string resources, but WPF Localization Extension> supports the following resource types out of the box:

  • Brush,
  • Double,
  • FlowDirection,
  • Image,
  • Text, and
  • Thickness.

For other data types you'll need to plug in a Converter.

Programmatic Localization

Sometimes we need to localize a control in source code, e.g. when the control is dynamically created. In this case we can set the binding programmatically, like this:

LocTextExtension loc = new LocTextExtension("U2UConsult.WPF.Localization.Sample:Translations:Pumpkin");

loc.SetBinding(this.PumpkinTextBlock, TextBlock.TextProperty);


If data binding is not needed (or not possible), the you can fallback to a lookup in the .resx file using GetLocalizedObject, like this:

string pinguin = LocalizeDictionary.Instance.GetLocalizedObject<string>(

    "U2UConsult.WPF.Localization.Sample",

    "Translations",

    "Pinguin",

    CultureInfo.GetCultureInfo("nl-BE"));

this.PinguinTextBlock.Text = pinguin;

 

Design time support

Localization at run time is one thing, but as a developer we like to see the result without running the application. The LocalizeDictionary.DesignCulture dependency property provides a Culture for Visual Studio's designers. It can be set in XAML, as follows:

lex:LocalizeDictionary.DesignCulture="nl-BE"


[By the way: the design time support only started working on my VS2010 after I recompiled the codeplex source with VS2010.]

On top of that, the markup extension provides a DesignValue property that sets the value in Visual Studio's Designer, regardless of the (Design-)Culture. Here's a sample:

<TextBlock Text="{lex:LocText Key=Tomato, Dict=Translations, Assembly=U2UConsult.WPF.Localization.Sample, DesignValue=#Tomato#}" />


Here's how these two features look like at design time:

Finally, the markup extension also provides a InitialValue for Blend support.

Missing cultures and missing translations

I guess we all agree that our application should never totally crash if a string can not be found in a resource file, or if the user has an unexpected or unsupported locale. Here are some techniques to get around this.

Missing resources

It's not a good idea to just fallback to a default language for missing entries, without a warning to the end user. In the best case this will only create confusion. But just take a look at the following interlingual homographs and consider what would happen if they appear in the middle of a half-translated window:

  • coin is french for corner,
  • file is dutch for traffic jam, and
  • gift is german for poison

So the invariant culture should NOT be English (or whatever 'real' language), but still remain comprehensible. In my app, the invariant resource file looks like this:

When a translation (or the whole culture) falls back to the invariant culture, the user interface looks like this (Tomato and Pumpkin are not translated):

Missing cultures

If a specific culture is not found (e.g. 'fr-NL') then it makes sense to first check if the language exists ('fr'), and only use the invariant culture as a last resort. That's exactly what the following code does. The 'French' and 'Dzongkha' buttons in the sample application test all code paths:

private void SwitchCulture(string culture)

{

    CultureInfo ci = CultureInfo.InvariantCulture;

    try

    {

        ci = new CultureInfo(culture);

    }

    catch (CultureNotFoundException)

    {

        try

        {

            // Try language without region

            ci = new CultureInfo(culture.Substring(0, 2));

        }

        catch (Exception)

        {

            ci = CultureInfo.InvariantCulture;

        }

    }

    finally

    {

        LocalizeDictionary.Instance.Culture = ci;

        this.CultureTextBlock.Text = ci.EnglishName;

    }

}

 

Source code

Last but not least, here's the source code of the small sample project: U2UConsult.WPF.Localization.Sample.zip (2,41 mb)

Enjoy!