Click here to Skip to main content
Click here to Skip to main content

WPF Inline Localization - A New Approach

, 19 Jul 2013
Rate this:
Please Sign up or sign in to vote.
Localizing a WPF Application with inline texts instead of Resource Files

Introduction

Localizing a WPF application can be very challenging and sometimes even frustrating.
There are several solutions to solve this problem. Most of them are focused on using Resource Files or some other central storage for the localized texts. This works in many projects, but in some cases it simply has a too big overhead.

In this article, you'll learn an approach that lets you put the localized texts directly where they are needed, which is:

  • in your XAML
  • in your C# code

Background

Over the years, I have developed many applications using WPF. Almost all of them needed some kind of localization mechanism. Usually I use Resource Files, XML Files or some other kind of central place to store the translated texts. There are great libraries for doing this, for example the WPF Localization Extension (http://wpflocalizeextension.codeplex.com/).

But using a central place for translations has its downsides.
Especially for small applications, the overhead of doing it that way can slow down the development process significantly. So I was looking for another approach to solve the problem.

One solution is to put the localized texts directly in to your code (C# and XAML) rather than storing them at a central place. This technique is what's described in this article.

So let's jump right in...

Preparation

To use the inline localization, you need to define two classes: One Markup Extension for the localization in XAML and one class for localization in code.

The Markup Extension Class

public class LocTextExtension : LocalizationMarkupExtensionBase
{
    public String En { get; set; }
    public String De { get; set; }
}

The Markup Extension class will be used when you specify localized texts in your XAML.
As you see, there are two properties defined (En and De).
These properties will store the localized texts for English and German. You can use other languages/cultures by simply adding additional properties. For details on how to name the properties, see section "Naming Properties for Translations" below.

The Code Translation Class

public class LocString : LocalizedStringBase
{
    public String En { get; set; }
    public String De { get; set; }
}

The code translation class will be used when you specify localized text which should be used in your C# code.
Here, we also have the properties that will store the localized texts.

Naming Properties for Translations

The properties defined in the markup extension class (LocTextExtension) and in the code translation class (LocString) must follow a convention guided by the name of the culture for which they should hold the translations. The property name must be named after the culture name (CultureInfo.Name) but without dashes and the first letter of the language and the first letter of the region code must be in upper case.

Here are some examples:

Property Name Language CultureInfo.Name
En English (neutral)en
DeGerman (neutral)de
EnUsEnglish (USA)en-US
FrCaFrench (Canada) fr-CA

Using the Code

Once you have defined your Markup Extension class and the code translation class, you can define localized texts directly in your XAML and code like this:

Localization in XAML

First, add the XML namespace declaration for your markup extension at the top of your XAML file like this:

<Window x:Class="WpfInlineLocalization.Example.TestWindow"
        xmlns:loc="clr-namespace:WpfInlineLocalization.Example"
... 

Then, you can use the markup extension anywhere in your XAML like this:

<Button Content="{loc:LocText En=Open Entry, De=Eintrag öffnen}" /> 

Localization in Code

To localize texts which are used in your code, simply use create a new instance of the LocText class and use it where you'd normally use a string, like this:

MessageBox.Show(new LocString {En = "Do you really want to delete this item?", 
De = "Wollen Sie den Eintrag wirklich löschen?"});  

Tips

Using Multi Line Texts in XAML

If you want to use texts with multiple lines in XAML you can do it like this:

<TextBlock>
  <TextBlock.Text>
    <loc:LocTextExtension xml:space="preserve">
    	<loc:LocTextExtension.En>Line 1
Line 2</loc:LocTextExtension.En>
    	<loc:LocTextExtension.De>Zeile 1
Zeile 2</loc:LocTextExtension.De>
    </loc:LocTextExtension>
  </TextBlock.Text>
</TextBlock> 

Escaping characters in XAML

In XAML you can't use certain characters like commas (,) and single quotes (').
To overcome this limitation you have two alternatives: Esacpe the character or use XML elements instead of attributes.

To escape an character simply put an backslash (\) in front of it like this:

<TextBlock Text="Hello\, how are you" />

And here is an example of using XML elements instead of attributes:

<TextBlock>
  <TextBlock.Text>
    <loc:LocTextExtension xml:space="preserve">
      <loc:LocTextExtension.En>Hello, how are you</loc:LocTextExtension.En>
    </loc:LocTextExtension>
  </TextBlock.Text>
</TextBlock> 

ReSharper Live Templates

In the download section, you will find the WpfInlineLocalizationTemplates.zip file which contains live templates you can import into ReSharper. There are two live templates included: One for inserting a LocString into C# code and one for inserting a LocText (Markup Extension) into XAML making it even easier for you to work with this approach. Simply type locs to insert a LocString into your code and type loct to insert a Markup Extension for localized text.

Points of Interest

Design-Time Support

The Markup Extension also works in design mode (e.g., Opening your XAML file in Expression Blend or in the Visual Studio WPF editor). The language that is used in the design mode is specified in via the LocalizationManager.DesignTimeCulture property. That way, you can preview your localized UI even in design mode.

Markup Extension

The Markup Extension class picks up a translation from the translation properties based on the current culture (Thread.CurrentThread.CurrentUICulture). It does this by overriding the ProvideValue method of the MarkupExtension class:

public abstract class LocalizationMarkupExtensionBase : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this.GetLocalizedValue();
    }
    ...
}

The LocalizationMarkupExtensionBase.GetLocalizedValue method simply tries to find a property matching the name of the current culture and uses the value of that property as translation. It works like this:

  1. Try to find a property that's named like the current culture.
  2. If no property is found, try to find a property that's named like the neutral version of the current culture.
  3. If still no property is found, try to find the property that's named like the fall back culture (LocalizationManager.FallbackCulture)
  4. If still no property can be found or the property returned null, return LocalizationManager.MissingLocalizationText (to indicate a missing translation)

Code Localization

For the localization in code, a simple trick is used.
The LocalizedStringBase class has an overload for the implicit cast operator (to string) which enables you to use this class (and its subclasses) everywhere where you'd normally use a string:

public abstract class LocalizedStringBase
{
    public static implicit operator String(LocalizedStringBase localizedString)
    {
        return localizedString.GetLocalizedValue();
    }
    ...
}

Here the GetLocalizedValue method of the LocalizedStringBase class is used to get the translated text. It works exactly like the GetLocalizedValue method of the LocalizationMarkupExtensionBase class.

You can also use the code localization together with String.Format like this:

var articleId = 101;
MessageBox.Show(String.Format(new LocString 
{En = "Do you really want to delete the article '{0}' 
?", De = "Wollen Sie wirklich den Artikel '{0}' 
löschen?"}, articleId)); 

Performance

The code uses reflection to find and retrieve the values of the localization properties.
But reflection can be a little slow. Therefore the ReflectionHelper class is used. Whenever the ReflectionHelper.GetPropertyValue is called to retrieve the value of a property, it creates a delegate which is used to access the property and caches that delegate. The next time the same property is accessed, the cached delegate is used. This speeds up the property access (by about 30 to 50 percent based on my tests).

Advantages and Disadvantages using this Approach

While this approach can save a lot of development time for many projects, it is not perfect for any project.
It has many advantages, but it has also some drawbacks you have to consider.

Advantages

  • The localized texts are exactly where they are used, making it much easier to translate them because you always see the context in which they are used.
  • You don't have to define keys for translations like you'd have to using resource files.
    This is especially nice because sometimes you have a lot of (explanatory) texts (like tool tips) in the UI for which it is quite hard and annoying to find useful key names.
  • You have the translations for each text close together, rather than for example constantly having to switch between multiple resource files.
  • You do not have to deal with resource files, XML files, etc.
  • It is a very simple approach and therefore easy to understand, especially for developers who are new to your project.
  • It's very simple and fast to implement.
  • It has a minimal runtime overhead and is therefore one of the fastest localization techniques.
  • It's much easier and faster to use when you need to localize an application which previously was not localized (uses static texts).
  • Since only very little code is involved, it is extremely unlikely to throw Exceptions which enables you to use localization even in parts of the UI that deals with Exceptions (e.g. Error Dialogs).
    Usually localization libraries are quite complex and might not work when an Exception was previously thrown.

Disadvantages

  • Translations are spread over the whole code, making it much harder to add a new language to a localized application.
  • It does now work good when someone other than a developer does the translation.
    For example, the translations cannot be easily sent to another person to translate them (like it would be with resource files or Excel sheets).
  • Translations cannot be reused as easily as with other approaches.
  • The language cannot be switched at runtime. Doing so will only affect controls that are created after the language was changed. However, I have very rarely seen projects that really needed support for switching the UI language at runtime. There are only very few cases where this is a necessity.

That said, I would recommend this approach for projects meeting these criteria:

  • It's a relatively small project.
  • The developer(s) is also the person who does the localization.
  • The application will support only a small number of languages (e.g. 2 to 5 languages).

Any Questions?

If you have any questions about this article and the code or if you need help using it, don't hesitate to contact me. You can reach me at info@rent-a-developer.de.

History

  • 18th July 2013: Initial version
  • 19th July 2013: Added Live Templates for ReSharper

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

David Liebeherr
Software Developer (Senior) rent-a-developer
Germany Germany
David Liebeherr is a certified Microsoft .NET / C# developer located in Germany.
 
He is a freelancer specialized in the development of business applications with .NET.
He has over 10 years of experience in WPF, Silverlight, SharePoint, ASP.NET and C# in general.
 
You can reach him at www.rent-a-developer.de or info@rent-a-developer.de

Comments and Discussions

 
QuestionComment PinmemberFatCatProgrammer20-Jul-13 6:41 
AnswerRe: Comment PinprofessionalDavid Liebeherr20-Jul-13 9:04 
GeneralMy vote of 4 Pinmemberjohannesnestler18-Jul-13 21:56 
GeneralRe: My vote of 4 PinprofessionalDavid Liebeherr19-Jul-13 3:14 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 19 Jul 2013
Article Copyright 2013 by David Liebeherr
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid