WPF Localization Using RESX Files

Introduction
For developers used to the integrated, inbuilt localization support for Windows Forms applications, the Microsoft approach to localizing WPF applications using the somewhat primitive Locbaml tool can come as a shock. Some of the issues that have been identified with this approach are:
- The localization process is not integrated into the standard Visual Studio build mechanism (as it is for Windows Forms applications).
- There is no way to view or edit the localized XAML within the Visual Studio designer.
- Locbaml uses CSV files and has issues when the translated text includes commas. The use of CSV files forces translators to work with two separate mechanisms since they still have to work with standard .NET RESX files for programmatically translated strings.
- The Locbaml approach results in the complete binary XAML for the window being replicated in the satellite assemblies for each localized language. This results in much larger footprint for localized applications compared to the Windows Forms approach where only those resources that differ from the invariant culture are compiled into the satellite assemblies.
- There is no way to dynamically change the language of the application at runtime without closing and recreating windows.
The issues with the Locbaml approach have resulted in the development of a multitude of different solutions for localizing WPF applications. The following are just some of the solutions proposed:
- Localizing WPF Applications using Locbaml
- WPF Runtime Localization
- Simple WPF Localization
- WPF Localization - On-the-fly Language Selection
- WPF Localization Extension
At the risk of adding to the confusion, this article outlines another approach which builds on the strengths of some of these earlier solutions.
Background
The article Simple WPF Localization provides an effective, simple solution for localizing text resources in WPF applications. It uses a WPF Extension to get the string resources from the standard project Properties.Resources RESX file. This article takes a similar approach, and defines a RESX extension that allows WPF properties to be pulled from embedded RESX resources. The solution outlined here differs in the following ways:
- Resources can be localized using any embedded RESX file. This allows you to take a similar approach to Windows Forms localization and store the resources associated with each WPF window or control in a separate RESX file (typically named the same as the Window). This is important for projects with a large number of windows where it can become difficult to manage the large number of resources in a single file, particularly with multiple developers. It also means that your resource names only have to be unique within the window.
- Any WPF property (not just strings) can be localized using the RESX Extension. You can use it to localize images, locations, sizes, and other layout properties. The built-in support for images makes defining icons for windows, menus, and toolbars much simpler, and is worth using even if you don't want to localize them.
- The design time display culture can be selected dynamically using a notification icon in the system tray, allowing you to view and edit the localized windows in the Visual Studio designer. This is great for verifying the localized layout for a window without having to run the application.
- The RESX extension allows you to define a default fallback value for properties that is used if the associated resource cannot be loaded. This is particularly important when localizing non-text properties where returning a null value may cause the page to fail to load.
Using the RESX Extension
Once you have downloaded the source code and built it, add a reference to the compiled assembly (Infralution.Localization.Wpf) to your project. If you are using the demo solution, then this is already done for you. You are now ready to use the ResxExtension
in your own XAML.
Markup extensions allow you to define XAML property values that are evaluated by calling custom code defined by the Markup Extension. See the MSDN article Markup Extensions and XAML for more information. The ResxExtension
derives from the base MarkupExtension
class, and evaluates the property by retrieving the data from an embedded RESX resource. For instance, the following markup sets the Text
property of a TextBlock
to the "MyText
" resource from the embedded RESX resource called MyApp.TestWindow
:
<TextBlock Text="{Resx ResxName=MyApp.TestWindow, Key=MyText}"/>
If you haven't yet created the resource (and compiled your project), then this will be displayed in the WPF designer as #MyText, where the # highlights the fact that the resource has not yet been defined. The next step is to create the RESX file and define the resource. If the default namespace for your project is "MyApp
", then you simply create a RESX file called "TestWindow.resx" and set its Build Action to "Embedded Resource". Add a string resource with the name "MyText
" and recompile the project. If you now open the TestWindow
XAML in the designer, you will see the string from the resource file displayed in the TextBlock
.
To add a localization, simply copy the RESX file and rename it. For instance, to create a French localization, copy the RESX file to TestWindow.fr.resx and include it in your project as an embedded resource. Change the "MyText
" resource to the French translation. When you recompile your project, Visual Studio will automatically create the French satellite assembly containing the French resources.
Changing the Design Time Culture
Open TestWindow.xaml in the designer again. You should notice that a new icon appears in your Windows desktop notification tray. This allows you to select the culture used at design time. Click on the icon and select "Other Cultures...". Select one of the French cultures from the dropdown list. The translations displayed in the designer will switch to those from the French RESX file.
Changing the Culture Dynamically at Runtime
The culture of your application can be changed dynamically at runtime simply by setting the CultureManager.UICulture
property. This sets the CurrentThread.UICulture
and automatically updates all active XAML that uses the ResxExtension
.
Images and Icons
The ResxExtension
doesn't just work for text. It also makes it easy to use icons and images from RESX files in your XAML markup. For instance, to define the icon for a window, simply add an icon resource to the RESX file named "Window.Icon", then define the markup as follows:
<Window Icon="{Resx ResxName=MyApp.TestWindow, Key=Window.Icon}"/>
This is so much easier than the standard way of defining WPF window icons, that you will probably will want to use this even if you don't want to localize the icons. You can use the same technique for setting the icons for menus and toolbars.
Localizing Other Property Types
The ResxExtension
can be used to localize properties of any type. For instance, the Margin
property of a TextBlock
could be defined as follows:
<TextBlock
Margin="{Resx ResxName=MyApp.TestWindow, Key=MyMargin, DefaultValue='18,0,0,71'}"/>
In this case, note that we have defined a DefaultValue
attribute. This is the value used if no resource can be found and loaded. If you don't provide a DefaultValue
for non-text properties, then the XAML may fail to load in the designer if the resource has not been defined yet. The resource can be defined as a simple string resource (with value "18, 0, 0, 71"), or you can define it as a fully typed value in the RESX file, e.g.:
<data name="MyMargin" type="System.Windows.Thickness, PresentationFramework">
<value>18, 0, 0, 71</value>
</data>
The latter is somewhat more work - but it does ensure that only valid values can be entered using the resource editor.
UICulture Extension
The project also defines another markup extension - UICulture
. This extension is used to set the Language
property of a WPF window (or other elements) to the language matching the current CultureManager.UICulture
. Like the RESX Extension, the UICulture
extension automatically updates attached elements when the CultureManager.UICulture
changes.
ResourceEnumConverter
For convenience, the project includes the ResourceEnumConverter
class. This provides an easy mechanism for localizing the display text associated with enum
s. It is described in more detail in this article.
Hiding Window RESX Files in the Visual Studio Solution Explorer
One nice feature of Windows Forms localization is that the RESX files associated with a form or control are hidden by default. To see the RESX files, you click on the expand button next to the form or control. This means that as you add more languages, your Solution Explorer pane does not become too cluttered. You can also do this for the RESX files associated with a window XAML file by editing the Visual Studio project file and adding a DependentUpon
XML node for the RESX file. E.g.:
<EmbeddedResource Include="TestWindow.resx">
<DependentUpon>TestWindow.xaml</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
Implementation Notes
This section provides some notes on the internal implementation of the ResxExtension
class. You don't need to read this to use the ResxExtension
, but if you are interested in some of the design choices, then read on.
Object Lifetime Management
Object lifetime management is one of the main issues when designing a MarkupExtension
that will dynamically update the XAML that uses it. The ResxExtension
needs to maintain a reference to the target XAML elements that use it to enable the elements to be updated when the CultureManager.UICulture
is changed. If this reference is a strong reference however, then the WPF elements that use the extension will never be garbage collected. To avoid this situation, the extension instead maintains a weak reference to the WPF target objects. This enables the target objects to be garbage collected. The only problem is that there is still a strong reference to the extension objects themselves (since we need to hold a collection of the extension objects in order to update them). This issue is overcome by periodically calling a cleanup function that removes extensions that no longer have active targets. The cleanup is triggered after a set number of extension objects have been created since the last cleanup. This lifetime management mechanism has been implemented in some base classes to enable it to be used for any MarkupExtension
that requires this behaviour. The ManagedMarkupExtension
provides the base class for the extension, and implements the weak reference to the WPF target objects. The MarkupExtensionManager
class manages a collection of ManagedMarkupExtension
objects, and implements the update and cleanup mechanism. The ResxExtension
and UICultureExtension
both derive from ManagedMarkupExtension
, and use a static
instance of the MarkupExtensionManager
class to handle updating WPF targets when the CultureManager.UICulture
is changed.
ResxExtension Arguments
Ideally, it would be nice you did not have to pass the name of the RESX file to the ResxExtension
and it would default to using the root WPF node x:Class
property to determine this. Unfortunately, at design time, there is no way to navigate from the TargetObject
to the root node that defines the x:Class
. So, to enable use of separate resource files for each window (which is important for large projects), we are stuck with specifying the REX name for each extension. If anyone can see a solution for this, I would be happy to hear about it.
Expression Blend Support
The ResxExtension
will work inside Expression Blend and display the resources from the embedded invariant RESX file (provided that the project has been compiled). Unfortunately, Expression Blend will not load resources from satellite assemblies, and so changing the design time culture using the desktop tray icon has no effect when using Expression Blend.
Globalizer.NET Support
The ResxExtension
was designed to support localizing WPF applications using Infralution's Globalizer.NET tool (although you certainly don't need Globalizer.NET to use the ResxExtension
). The ResxExtension
class includes a static static GetResource
event that allows Globalizer.NET (or any other localization tool) to hook into the resource translation mechanism and dynamically provide translations for resources. This enables Globalizer.NET to display translated previews of windows and controls that use the ResxExtension
without having to first compile the satellite assemblies. Globalizer.NET also includes the ability to scan existing WPF projects for localizable properties and automatically convert them to use the ResxExtension
.
History
- 2009.04.08 - Initial posting
- 2009.04.20 - Fixed bug in
ResxExtension.ConvertValue
- 2009.09.22 - Fixed
ResxExtension.GetDefaultValue
to handle non-dependency types and addedUpdateTarget
methods - 2010.01.05 - Fixed
ManagedMarkupExtension.UpdateTarget
to handle targets which don’t inherit fromFrameworkElement
- 2010.06.30 - Fixed issue with using
ResxExtension
with PRISM - 2011.05.30 - Fixed issue with using
ResxExtension
in templates
Post Comment
wlxAEb Souls in the Waves Superior Morning, I just stopped in to visit your site and thought I would say I experienced myself.
fipYZJ Of course, what a fantastic blog and illuminating posts, I definitely will bookmark your blog.Have an awsome day!
a1h4SU Very informative blog post. Keep writing.
when we do our house renovation, we normally search for new home styles and designs on-line for some wonderful tips.
Bx3IlX You have made some good points there. I looked on the internet to learn more about the issue and found most people will go along with your views on this website.
BzBvSV Really appreciate you sharing this article.Thanks Again. Great.
u2Pq1O thanks to the author for taking his time on this one.
fPpMUt That is a really good tip particularly to those new to the blogosphere. Simple but very accurate info Thanks for sharing this one. A must read article!
RIddGT Very informative post.Much thanks again. Fantastic.
4mM1gJ Thanks again for the blog post.Thanks Again. Awesome.
gzJZsw Thanks again for the article post.Thanks Again. Want more.
vgvzSL Really informative blog post.Really thank you! Cool.
AK6y5F I really liked your blog post.Much thanks again. Really Cool.
7Oa6Bi Thanks again for the blog post.Much thanks again. Will read on...
94WBiR Thank you for your article.Thanks Again. Keep writing.
6J3yeT Awesome article post.Thanks Again.
NPE8R0 Really enjoyed this blog post.Really thank you! Great.
ydp1yT Looking forward to reading more. Great post.Thanks Again. Keep writing.
gcRYoM I really like and appreciate your blog post.Thanks Again. Cool.
1XOk53 Thanks for the article.Really looking forward to read more. Much obliged.
Zfjnwk I truly appreciate this article post. Really Great.
2UBYsX This is one awesome article post.Thanks Again. Much obliged.