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 |
De | German (neutral) | de |
EnUs | English (USA) | en-US |
FrCa | French (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:
- Try to find a property that's named like the current culture.
- If no property is found, try to find a property that's named like the neutral version of the current culture.
- If still no property is found, try to find the property that's named like the fall back culture (
LocalizationManager.FallbackCulture
) - 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