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

Advanced WPF Localization

By , 10 Sep 2011
Rate this:
Please Sign up or sign in to vote.

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:

  1. Open the default resource file created by VS in your WPF application project. It is located in "Properties\Resources.resx".
  2. Create entries in the string resources of the file. For example enter "HelloWorld" for name and "Hello World!" for value.
  3. 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:

  1. Make the type public (open the resource file and change the "Access Modifier" from "Internal" to "Public")
  2. 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);
// Update the localized values
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
    • Initial version
  • 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.

License

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

About the Author

Jecho Jekov
Software Developer (Senior)
Bulgaria Bulgaria
No Biography provided

Comments and Discussions

 
GeneralRe: Using the library within plugins/addins PinmemberPatrick Blackman1-Nov-12 7:53 
GeneralRe: Using the library within plugins/addins PinmemberJecho Jekov1-Nov-12 10:54 
QuestionHeader localization in menu Pinmembervitaliybg18-Sep-12 1:36 
AnswerRe: Header localization in menu PinmemberJecho Jekov18-Sep-12 1:41 
GeneralRe: Header localization in menu Pinmembervitaliybg18-Sep-12 2:38 
GeneralRe: Header localization in menu PinmemberJecho Jekov18-Sep-12 4:17 
GeneralRe: Header localization in menu Pinmembervitaliybg18-Sep-12 8:44 
GeneralRe: Header localization in menu Pinmembervitaliybg18-Sep-12 8:55 
I seems I just must put the LocalizationScope BEFORE the heder declaration in that case! Like this:
 
<dg:DataGrid AutoGenerateColumns="False" Width='Auto' Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="3" Name="dgHotfolders" ItemsSource="{Binding ItemsBinding}" Margin="0" SelectionMode="Extended" >
            <dg:DataGrid.Columns >
                <dg:DataGridTemplateColumn LocalizationScope.ResourceManager="{ResourceManager res:Resources}" Header="{Loc DataGridTemplateColumn_Path}">
                    <dg:DataGridTemplateColumn.CellTemplate>
 
It is a surprise, I thought the order does not matter!
QuestionLocalization in multiple UI threads Pinmemberjgomila4-Sep-12 6:36 
AnswerRe: Localization in multiple UI threads PinmemberJecho Jekov4-Sep-12 6:43 
GeneralRe: Localization in multiple UI threads Pinmemberjgomila4-Sep-12 11:07 
GeneralRe: Localization in multiple UI threads PinmemberJecho Jekov4-Sep-12 11:13 
GeneralRe: Localization in multiple UI threads Pinmemberjgomila4-Sep-12 23:09 
GeneralRe: Localization in multiple UI threads PinmemberJecho Jekov4-Sep-12 23:15 
QuestionLocalization of FlowDocument Paragraphs? PinmemberJohnny J.27-Jul-12 2:28 
AnswerRe: Localization of FlowDocument Paragraphs? PinmemberJecho Jekov30-Jul-12 2:03 
QuestionError when Assembly Name != Default Namespace PinmemberJohnny J.25-Jul-12 2:45 
AnswerRe: Error when Assembly Name != Default Namespace PinmemberJecho Jekov25-Jul-12 4:25 
GeneralRe: Error when Assembly Name != Default Namespace PinmemberJohnny J.25-Jul-12 4:30 
QuestionWorking with the translation at design time [modified] PinmemberDron Andrei26-Apr-12 4:45 
AnswerRe: Working with the translation at design time PinmemberJecho Jekov26-Apr-12 4:51 
GeneralRe: Working with the translation at design time PinmemberDron Andrei26-Apr-12 5:04 
GeneralRe: Working with the translation at design time PinmemberJecho Jekov3-May-12 6:44 
GeneralRe: Working with the translation at design time Pinmembermarko4444-Jan-13 1:17 
AnswerRe: Working with the translation at design time Pinmemberjed_codeproject16-May-12 1:11 

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
Web02 | 2.8.140415.2 | Last Updated 10 Sep 2011
Article Copyright 2011 by Jecho Jekov
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid