Introduction

I was looking for a way to localize my application in a way that would be
simple to handle, both for the developer and for the translator, and since I
couldn't find any, I found this very handy solution that I would like to share with you.

My goals were:

Application localisation.

Dynamic application language change (at run time).

Simple to add/modify a language for the developer or for the user (no compilation or such).

Design time support in Visual Studio / Blend in any language.

And those goals are met...

..but... an important remark: this library, so far, does not work with ClickOnce applications. Look at the "To ClickOnce Users : the folder issue" part of this article for details. I am open to comments to improve this.

In this article I explain how the library works, and how to use it. I also present a tool that allows to handle
easily the localised strings for the developer or the user.

I released a demo in both C# and VB, so no WPF lover is left behind.

Background

My solution uses Resource Dictionaries, which are, as expected, dictionaries: they associate a Key to an Object (for localisation purposes: a String). In WPF,
those
resource dictionaries can be used within several scopes: Application, Window, Control, and even at
FrameWorkElement level, so almost anything can have its own resources.

A feature of the WPF ResourceDictionary is that it can contain other ResourceDictionarys, which allows to split the dictionary into multiple smaller
ones. Those inner dictionaries are called MergedDictionarys.

The same key can be used in several merged RDs. In this case, the value returned for this key is the latest added.

In case the ResourceDictionary is changed, a notification is issued to all
XAML controls using this key as a DynamicResource (controls using StaticResource remains unaffected).

So the idea is simple:

Store all application strings for each language within a ResourceDictionary.

Keep a fallback language RD in the application merged dictionary.

Only use dynamic reference to a resource string within the application.

On language change requested, we add or remove a RD, ensuring that: The
fallback RD is always loaded. - The latest RD is the language requested.

So on language change all the strings will be updated by the standard WPF resource management. (Rq: Step 2 is done to avoid any bad surprise in case you forget to add a key in a foreign RD. In this case, the fallback language string will be shown.)

Using the library

First step: Create the Resource Dictionaries

Create a resource dictionary (a standard WPF item) for each language, using the same
keys. Rq : It is wise to use a suffix to prevent resource names from colliding:
I use 'Str'
as suffix.For example, for English, with three strings having HelloStr,
DemoModeStr, and RealModeStr as keys, the RD looks like :

<ResourceDictionaryxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:s="clr-namespace:System;assembly=mscorlib"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"><s:Stringx:Key="HelloStr"> Hello </s:String><s:Stringx:Key="DemoModeStr"> Currently in demo mode </s:String><s:Stringx:Key="RealModeStr"> Currently in real mode </s:String></ResourceDictionnary>

Same dictionary for French (only localised text changes, use copy paste to create other versions):

Now you must save those RDs in separate files with a naming convention, by
using "_" and the two letter ISO of the language as a suffix.
I used "InterfaceStrings" as a prefix (you can choose yours). So for instance, for
English, French, and Spanish we will have the files: InterfaceStrings_en.xaml,
InterfaceStrings_fr.xaml, InterfaceStrings_es.xaml.

In Visual Studio :

Put all those RDs in the same folder (I used 'Languages').

Set those RD properties 'Build Action' as 'Content', as it should be packed with your application.

Set 'Copy to
Output Directory' to 'Copy always'.

Second step: Include one of this language dictionaries as a resource for your application

Choose your fallback language: this will be the language you want to see in design time, and the language of your application when it launches (if you don't change
language at startup).

Add the fallback dictionary inside your resources in the first
place within the App/Window/Control you want to localize. For application resources, this will look like:

<Application(...)><Application.Resources><ResourceDictionary><ResourceDictionary.MergedDictionaries><!--This is the fallBack language resource
dictionnary. it should always be in First position. --!>
<!--set EZLocalise.FallBackLanguage to the language used here--><ResourceDictionarySource="/Languages/InterfaceStrings_en.xaml"/><!-- ... Some Other Resource Dictionary for my Application ... --></ResourceDictionary.MergedDictionaries></ResourceDictionary></Application.Resources></Application>

If you have other resources (like themes, styles, templates...) put them in another
ResourceDictionary inside the MergedDictionariesafter the fallback RD. Notice that he fallback dictionary will always be kept inside the application RD, so you can rest assured that all that worked at design time will at least display in your
fallback language at run time.
It also allows you to add some keys only in the fallback language during
development, then add the other languages strings later-on.

Rq 1: In the EZLocalise constructor you set FallBackLanguage. You should always set its value to be the real fallback language, i.e., the language of the first
ResourceDictionary of your
application (in the above example, "en").

Rq2: To use another language at design time, change this first
dictionary within your App resources, and update your EZLocalize constructor's FallBackLanguage.

Third step: Setup the library

Add a reference to EZLocalize.dll to your project. You now have access to
the EZLocalizeNS namespace. Create a new EZLocalize to handle your resources. The arguments are:

The resource to localize

The fallback language

The folder where the application is launched; use null to auto-detect
for non-ClickOnce applications.

Rq4: The library can help you find available language files with getFileBaseNames, which gives you all the file base names within a folder ("InterfaceStrings")
and LanguagesForFileBaseName will give you all languages for a base name (LanguagesForFileBaseName("InterfaceStrings") = { "en", "fr", "es"} ).

Rq5: use naming conventions if you localize many objects, and use Reflection.Expl : if you localize MyUserControl, use "MyUserControl" as file base name.If LanguagesForFileBaseName("MyUserControl") is empty then your control is not localized.

Fourth step: Use the localised resources

Now each time you are using a string in your application :

you must always declare it in the FallBack RD

you
can declare it very localised RD.

you must no longer 'hard-code' your strings : see below how to retrieve a string.

Rq : As mentioned above, a non-existing key in a 'foreign' (non-fallback) language will only result in the application displaying a fallback language string, so you can delay the translation while coding, and test at run time in a foreign language even if not every string is translated in this language.

To use your strings, use standard WPF:

In XAML, use a DynamicResource Markup extension:

<TextBlockText="{DynamicResource HelloStr}"/>

Rq: If you use StaticResource, only the fallback language will get used and
XAML strings will not get updated if you dynamically change the language, unless you change
the language during application
startup (before Windows/controls gets loaded).

In code,use standard WPF also : FindResource/TryFindResource, or SetResourceReference for dynamic use on controls.

!! I used the application resource as an example, but you must use the resource
dictionary that you localized to get your localized strings. For instance, in the code behind of a user control that you localised, use
this.TryFindResource("MyStringName").

!! If you are using both strings defined at application level and strings defined at a lower level, when seeking for a resource the search will start from the lower then walk up the visual tree until it finds a match. So a control won't find the Application strings unless the control is within this visual tree.

If you want to change the text of an element which is not a Framework element (such as the header
of a DataGridView column for instance) you cannot use SetResourceRefrence, you have to update it yourself: in this case handle the
LanguageChanged event.

As mentioned earlier, if you update language before startup, StaticResource will use that language. So if you do not wish to change language dynamically, use StaticResource to
slightly reduce overhead.

If you want to change language at any time, just call ChangeLanguage with the new language string ("en", "fr", ...). And all your strings will update.

(C#):

string res = MyEZLocalise.ChangeLanguage("fr");

(VB):

Dim res = MyEZLocalise.ChangeLanguage("fr")

If you want to check if a language was loaded successfully, just test if the returned string is the language requested (res == "fr"). If not,
res is an error message in English (: "file not found" or "xaml file corrupted at line xxx").

To ClickOnce users: Folder issue

If you are either:

Distributing your application by copying the Debug/ or Release/ folder

Using a classical deployment project (generating setup.exe and .msi files)

Using a third party installer

Then there is no concern for you. But, as mentioned earlier, the EZLocalize
class won't work with ClickOnce applications, that you get if you
use 'Publish' to provide your applications. The reason for that is that the ClickOnce applications are
SandBoxed when they are launched,
so there is no way to retrieve the launch folder, hence no way to get the full path for the
XAML files containing the localised
resource dictionaries.You can set the folder where to seek for languages in the library constructor, if you know where to find it .

Details : I found no reliable way to have it work on both XP and Seven,
even within MS documentation or StackOverflow posts... and if you look here: MSDN documentation,
you will see at the end of the "ClickOnce and Windows Installer Comparison Table", that the "Application installation location"' is the "ClickOnce application cache".

Using EZLocalize with larger projects

EZLocalize is very simple and small: Unless someone asks, I won't
detail the code (provided in VB and C#).

If you want to use EZLocalize within a large project, or if you support many languages, it will soon take too much time to add each key in all supported languages.
I developed a small application, StringResourceEditor, that allows to add/remove/modify/export in
CSV/import in CSV all the key value pairs,
which is more convenient and avoids many errors. I added this app in the 'Tools' menu of Visual Studio 2010, so
I can change any string at any time during development. I also gave this tool to my customers so that they can edit the interface strings, which in fact is
useful even for small projects since
most people don't know how to edit an XAML file. So I suggest you:

add StringResourceEditor.exe in your Languages folder.

set its 'Build Action' to 'Content'.

set 'Copy to Output Directory' to
'Copy Always'.

Then, to add it as a tool in VS2010 :

Go into 'Tools' menu, 'External Tools' sub menu,

Choose a title ('string edit'),

Command:
$(ProjectDir)Languages\StringResourceEditor.exe,

Argument: admin,

Initial Directory:
$(ProjectDir).

When called with "admin" as parameter, StringResourceEditor allows to add/remove a key (developer). Otherwise those choices are hidden (customer).

Screenshot for the small demo strings:

The demo

A small demo is provided with this article, in its executable format, as a C# project and also as a VB project. Feel free to play with the controls, you can even add a language file into "Languages" folder, (expl: InterfaceStrings_de.xaml) and this language will be available on the next application startup. Screenshots:

Rq1: To create a new localisation, go into the 'Languages' folder, copy the fallback version, and rename it (expl for
Italian: copy InterfaceStrings_en.xaml
to InterfaceStrings_it.xaml). Once you have the new file, you can use StringResourceEditor.

Rq2: If you edit the strings directly with a text editor, change the content, !not the keys!, And use an UTF16 compliant editor, otherwise you won't be able to save non-English text.

Last words

Rq1: My only concern here was text but a similar scheme might be used for any type of resource (color, image, ...). expl : the flag image for each country.

Rq2: You might want to use localised strings at Window/Control/DLL/... level for more modularity. I found it more relevant at
application level,
since the same words are used and again in all parts of an application.You might even do both (application wide strings + component specific strings).

I hope this library can help you localize your application, tell me if it did!

With my sincere thanks for providing a clear example, I would like to ask your and others' opinion on if this method should be preferred to resource DLLs which were the norm for WinForms applications. I did see examples like yours, but no clear explanations on which is better, or if there can be a contest.

By the way, your update date is 90 years later from now. If WPF is still relevant in 2103, all of us should flock to it

For the update time, 2103 : sorry, i travel so often in time, i sometimes forget the year i'm currently living in.

For dll, this is not an option if you want to let your -non-technical- customer take care of the translation, and do not whish to have overwork each time a new language is used.

Imagine also the case of a free application : motivated users can translate it for you and send you the file back for later release. Not possible with a dll.

An annoying -but only saddening- drawback i did not anticipate is that i did use this lib for an aplication that was translated in a few languages (russian, chinese, italian) but i never actually saw it translated so far... :-/ Just complaining for the pleasure

Thank you for your informative reply that confirmed my view; I, too, am planning to rely on a string resource XAML file like you. About not seeing the derived or translated apps, maybe we could suggest revising CPOL to require sending copies of links by the people making use of the original articles or applications.
Hurol Aslan

Hello again,
I was planning to rely on an external XAML file and present it alongside a WPF user control library. That way, users could modify the resource strings in any way they wanted. However, in case foolish users messed up or lost that file, I would have to provide a backup plan. Your solution may be an alternative; I will look into it deeper.

How would I use this with a plugin framework? Each plugin would have its own screen with localized text, plus the main application so should I load the resource in the merged dictionary of the plugin UI or add the localization to the main application resources? These plugin will be written by independently by different department within the same org.

Now, to localise a modular app, you have basically 3 strategies :
S1 : All strings handled at App. level.
the issue with this solution is the concurent access to a single resource file.
You can handle this with file sharing, versionning system, or by designating an owner for the file.
In case there is an owner, there will be a validation delay for a new string, but the team might still include a local string to perform tests without waiting.
S2 : All strings handled at module level.
This ensures maximal modularity. Here the challenge is for the translator : he must avoid translating many times the same text, and work with the teams to achieve consistency : for example to avoid one component asking 'are you sure ?', and another one 'do you confirm ?'
for a similar meaning. Or one component using 'color' where the other uses 'teint'.
S3 = S1 + S2. Strings handled partly at app level, partly at module level. This is my prefered solution : put C1, C2, C3 at app level, and C4 at module level. So you localize BOTH the app and its plugins. You have some modularity and better consistency.