Click here to Skip to main content
16,016,394 members
Articles / Desktop Programming / WPF

Advanced WPF Localization

Rate me:
Please Sign up or sign in to vote.
4.85/5 (27 votes)
1 Oct 2017Public Domain6 min read 299.2K   7.1K   74   138
Provides a solution for localization of WPF application both in XAML and in code-behind

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:
XML
<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:

XML
<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:

XML
<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:

C#
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:

XML
<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.

XML
<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:
    XML
    <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.

XML
<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.
XML
<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>
XML
<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:

XML
<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.

C#
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...

XML
<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.

XML
<Grid Name="myGrid" LocalizationScope.UICulture="English (United States)">
...
</Grid>
C#
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 A Public Domain dedication


Written By
Software Developer (Senior)
Bulgaria Bulgaria
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionTranslation of a binding Pin
Member 80080974-Feb-13 21:15
Member 80080974-Feb-13 21:15 
QuestionConverter ConvertBack Method doesn't call. Pin
bahman aminipour27-Nov-12 8:19
bahman aminipour27-Nov-12 8:19 
AnswerRe: Converter ConvertBack Method doesn't call. Pin
Jecho Jekov29-Nov-12 12:15
Jecho Jekov29-Nov-12 12:15 
GeneralRe: Converter ConvertBack Method doesn't call. Pin
bahman aminipour29-Nov-12 23:13
bahman aminipour29-Nov-12 23:13 
QuestionArchive file is damaged Pin
bahman aminipour21-Nov-12 9:09
bahman aminipour21-Nov-12 9:09 
AnswerRe: Archive file is damaged Pin
Jecho Jekov21-Nov-12 9:14
Jecho Jekov21-Nov-12 9:14 
GeneralRe: Archive file is damaged Pin
bahman aminipour22-Nov-12 0:12
bahman aminipour22-Nov-12 0:12 
QuestionUsing the library within plugins/addins Pin
Patrick Blackman26-Oct-12 19:11
professionalPatrick Blackman26-Oct-12 19:11 
I have a plugin framework where I am dynamically loading assemblies from a specific folder, can this library localize the plugins and be loaded dynamically? By this I mean would it require me to put the resource files in the main application?
AnswerRe: Using the library within plugins/addins Pin
Jecho Jekov28-Oct-12 5:03
Jecho Jekov28-Oct-12 5:03 
GeneralRe: Using the library within plugins/addins Pin
Patrick Blackman29-Oct-12 4:10
professionalPatrick Blackman29-Oct-12 4:10 
AnswerRe: Using the library within plugins/addins Pin
Jecho Jekov1-Nov-12 1:37
Jecho Jekov1-Nov-12 1:37 
GeneralRe: Using the library within plugins/addins Pin
Patrick Blackman1-Nov-12 7:53
professionalPatrick Blackman1-Nov-12 7:53 
GeneralRe: Using the library within plugins/addins Pin
Jecho Jekov1-Nov-12 10:54
Jecho Jekov1-Nov-12 10:54 
QuestionHeader localization in menu Pin
vitaliybg18-Sep-12 1:36
vitaliybg18-Sep-12 1:36 
AnswerRe: Header localization in menu Pin
Jecho Jekov18-Sep-12 1:41
Jecho Jekov18-Sep-12 1:41 
GeneralRe: Header localization in menu Pin
vitaliybg18-Sep-12 2:38
vitaliybg18-Sep-12 2:38 
GeneralRe: Header localization in menu Pin
Jecho Jekov18-Sep-12 4:17
Jecho Jekov18-Sep-12 4:17 
GeneralRe: Header localization in menu Pin
vitaliybg18-Sep-12 8:44
vitaliybg18-Sep-12 8:44 
GeneralRe: Header localization in menu Pin
vitaliybg18-Sep-12 8:55
vitaliybg18-Sep-12 8:55 
QuestionLocalization in multiple UI threads Pin
jgomila4-Sep-12 6:36
jgomila4-Sep-12 6:36 
AnswerRe: Localization in multiple UI threads Pin
Jecho Jekov4-Sep-12 6:43
Jecho Jekov4-Sep-12 6:43 
GeneralRe: Localization in multiple UI threads Pin
jgomila4-Sep-12 11:07
jgomila4-Sep-12 11:07 
GeneralRe: Localization in multiple UI threads Pin
Jecho Jekov4-Sep-12 11:13
Jecho Jekov4-Sep-12 11:13 
GeneralRe: Localization in multiple UI threads Pin
jgomila4-Sep-12 23:09
jgomila4-Sep-12 23:09 
GeneralRe: Localization in multiple UI threads Pin
Jecho Jekov4-Sep-12 23:15
Jecho Jekov4-Sep-12 23:15 

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

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