Introduction
In this article, I'm presenting an easy way to localize a WPF application. Localization can be done in XAML, code-behind or both.
This solution enables you to:
- Localize text, images, brushes, margins, control width and height, flow direction (or any other enumeration) and virtually any property that can be converted from text. Localization is done directly in XAML by using a markup extension.
- Localize custom controls located in a class library assembly
- Localize properties in code behind and change the values of localized properties
- Localize both dependency and non-dependency properties
- Change the current language on-the-fly with immediate results
- Use multiple resource files in the same Window/User control
- Use multiple languages (cultures) in the same Window/User control
- Support multiple UI threads
- Support code-behind localization from a non-UI thread
- Support data templates and can be used in styles
Added 2011-09-10
- Localization of bindings: Format data values
Localization in XAML
To localize your text, images, etc. in XAML, do the following:
- Open the default resource file created by VS in your WPF application project. It is located in "Properties\Resources.resx".
- Create entries in the
string
resources of the file. For example enter "HelloWorld
" for name and "Hello World!
" for value.
- Use the following syntax to display the text in your application:
<TextBlock Text="{Loc HelloWorld}" />
That's it. You can localize any string
in your application that way.
To localize an image (if you need culture-specific images), add the image in the "Images
" section of the resource file and set its name. Then reference the image in your application by its name. For example, if you add an image and name it "MyImage
", use the following in XAML to display it:
<Image Source="{Loc MyImage}" />
In case you need to localize the size of a control, its color, margin, flow direction, font, etc., you can do that too:
<Grid Name="myGrid" Height="{Loc myGrid_Height}"
Width="{Loc myGrid_Width}" Margin="{Loc myGrid_Margin}"
FlowDirection="{Loc myGrid_FlowDirection}">
<TextBlock Name="myText" FontFamily="{Loc myText_FontFamily}"
FontBold="{Loc myText_FontBold}" Foreground="{Loc myText_Color}"/>
</Grid>
Resource file:
myGrid_Height = 50
myGrid_Width = 100
myGrid_Margin = 20,10,20,10
myGrid_FlowDirection = LeftToRight
myText_FontFamily = Arial
myText_FontBold = True
myText_Color = Red
In the resource file, you put exactly the same values you would put in XAML.
You can localize pretty much any value. Primitive values (char, boolean, numbers), DateTime
and enumerations are supported out of the box. Other values like margins (the .NET "Thickness
" type), brushes and font families are supported if they have an associated TypeConverter
that can be used to convert them from their string
representation.
If you need, you can also use a custom converter the same way you use it in bindings:
<Image Source="{Loc MyImage, Converter={StaticResource MyConverter},
ConverterParameter=10}"/>
Resource Files
By default, the solution looks for resources in the resource file created automatically by VS in your WPF application project. It is located in "Properties\Resources.resx". If you want to use a different resource file, you must explicitly specify it in XAML.
To do this, you must assign a value to the "LocalizationScope.ResourceManager
" attached property. As the name implies, the "LocalizationScope
" type exposes several attached properties that control localization in a part of your window or user control. You can set these properties on any element in XAML and their values will affect the localization of that element and all its children.
<Window ...>
...
<Grid LocalizationScope.ResourceManager="...">...</Grid>
...
</Window>
To assign a value to the LocalizationScope.ResourceManager
property in XAML, you must use the "ResourceManager
" markup extension included in the solution. There are two ways to specify a resource file by using the extension - by referring to its corresponding auto-generated type or by entering an assembly name and base name.
Auto-generated Type
VS automatically generates a code-behind file for any resource file. The code-behind file contains an autogenerated class that exposes all resources contained in the file. While this autogenerated class is not used by the localization solution, its type is used to initialize a System.Resources.ResourceManager
instance. The "ResourceManager
" type uses the namespace
and name of the resource file's class to locate the resources in the assembly.
To refer to the type, you must:
- Make the type
public
(open the resource file and change the "Access Modifier" from "Internal
" to "Public
")
- Declare a
namespace
in XAML referring to the type's namespace
:
<Window ... xmlns:res="clr-namespace:MyProject.Properties">
...
<Grid LocalizationScope.ResourceManager="{ResourceManager res:MyResources}">...
</Grid>
...
</Window>
In the above example, the resource file is named "MyResources" and is located in the "Properties" folder of the project "MyProject
".
Assembly Name and Base Name
The second way to specify a resource file is to enter the name of the assembly containing the resources and the base name of the resources.
<Window ...>
...
<Grid LocalizationScope.ResourceManager="{ResourceManager AssemblyName='MyProject',
BaseName='MyProject.Properties.MyResources'}">...</Grid>
...
</Window>
A small advantage in this case is that you do not have to make the auto-generated type public
.
Bindings
Databound properties can be localized. Localization can be used to add localized text to a value and/or to ensure that a date or a number is formatted according to the selected language.
- Only dependency properties of type "
System.String
" and "System.Object
" are supported.
- The format
string
can be stored in resources or in XAML.
- A special "
LocBinding
" extension is used to localize bindings.
<TextBlock>
<TextBlock.Text>
<LocBinding ResourceKey="MyNameIs">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</LocBinding>
</TextBlock.Text>
<TextBlock.Text>
<LocBinding ResourceKey="CurrentDate">
<Binding Source="{x:Static sys:DateTime.Now}" Path="."/>
</LocBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock>
<TextBlock.Text>
<LocBinding StringFormat="My name is {0} {1}">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</LocBinding>
</TextBlock.Text>
<TextBlock.Text>
<LocBinding StringFormat="Today is {0:d}">
<Binding Source="{x:Static sys:DateTime.Now}" Path="."/>
</LocBinding>
</TextBlock.Text>
</TextBlock>
A value of the "ContentControl.Content
" property (Label
, ListBoxItem
, etc.) cannot be localized directly. The reason is that WPF ignores MultiBinding.StringFormat
for this property. To localize it, use the following:
<ListBoxItem>
<ListBoxItem.Content>
<TextBlock>
<TextBlock.Text>
<LocBinding ResourceKey="...">
...
</LocBinding>
</TextBlock.Text>
</TextBlock>
</ListBoxItem.Content>
</ListBoxItem>
Localization in code-behind
Dependency and non-dependency properties of any descendant of the "DependencyObject
" type can be localized in code-behind.
See the demo project. I'll cover this in a new article when I have enough time. The demo project is pretty self explaining.
Setting the Current Language
The .NET framework exposes two properties to set the current culture - Thread.CurrentCulture
and Thread.CurrentUICulture
. The CurrentCulture
property controls formatting dates and numbers. The CurrentUICulture
property controls which resources are accessed. To set the current language, set one or both of these properties and then call the LocalizationManager.UpdateValues()
method.
Thread.CurrentThread.CurrentCulture = myNewCulture;
Thread.CurrentThread.CurrentUICulture = myNewCulture;
LocalizationManager.UpdateValues();
Other Features
Missing Resources
Missing string
resources are replaced with the following string
: "[MyResourceKey]
". Therefore, if you have...
<TextBlock Text="{Loc HelloWorld}" />
...and forget to enter "HelloWorld
" in resources, you will see "[HelloWorld]
" on the screen.
All other types of resources are replaced with their default value (null
for reference types, zero for numbers, etc.).
Multiple Languages
You can set the Culture
and UICulture
properties on any element in XAML via the "LocalizationScope.Culture
" and "LocalizationScope.UICulture
" attached properties. Setting these properties define the cultures used by the control and its children.
<Grid Name="myGrid" LocalizationScope.UICulture="English (United States)">
...
</Grid>
LocalizationScope.SetUICulture(myGrid, myNewCulture);
LocalizationManager.UpdateValues();
For more information, see the demo project. I'll cover this in a new article when I have enough time.
Multithreading
Updating values in code-behind from threads other than the UI thread is supported. Multiple UI threads are supported.
For more information, see the demo project. I'll cover this in a new article when I have enough time.
History
- 2011-09-09
- 2011-09-10
- Bug fix: Fixed a bug in the demo project: "
LocalizationManager.UpdateValues()
" does not have to be called when setting properties in code-behind.
- Bug fix: "
[MyResourceKey]
" is not displayed when localizing the "ContentControl.Content
" property (Label
, Button
, etc.) and the resource is not found.
- Added "
CallbackParameter
" when a callback is used for localization in code-behind.
- Added support for localization of bindings.