Introduction
Localisation is something everyone should really care about. Creating your applications, or websites, in a manner that can be easily localised for different languages and cultures is not only “good practice”, but it may also provide additional opportunities.
Luckily the .NET framework, and WPF, has rich support for localisation, with WPF even having “baked in” support for switching flow direction for right to left languages. On top of that Rick Strahl and Michele Leroux Bustamante compiled an excellent guidance document detailing several technique for localising WPF applications. The documentation, along with sample code, can be found in the WPF Localisation Guidance project on CodePlex.
The document discusses several techniques for localising applications using RESX (resource) files, which is my preferred approach; but each of the techniques has its drawbacks:
- Static Binding. Simple and easy, but lacks support for dynamically switching culture.
- Attached Properties. Look powerful, but lacks support for value convertors and it’s a bit inefficient.
- Markup Extension. A new instance of the helper (complete with event wireup for locale switching) is created for every control, which doesn’t sound ideal and may lead to memory leaks.
All of the techniques are perfectly workable solutions, but with Binding and INotifyPropertyChanged WPF already contains a powerful mechanism for mapping and automatically updating UI elements with data; surely there’s some way we can leverage those? My goal was to attempt to find a way to localise an application with the following criteria:
- Use the standard Binding syntax so we can support value convertors.
- No complicated markup extensions.
- Provide a mechanism for code to “register” resources that can be consumed anywhere in the application, even from other PRISM modules.
- Be able to cleanly switch locales, without restarting the application or closing/reopening screens.
- Support the design experience with a minimum of fall back values when in design mode.
The Result – Localisation Using Binding
If you just want to see the code, there’s a basic implementation, and a PRISM demonstration, at the end of the article. Both implementations use the same basic “moving parts”:
LocalisationHelper
In essence this is our ViewModel. There are two key pieces of code here, first being:
public string this[string Key] { get { if (!validKey(Key)) throw new ArgumentException(@"Key is not in the valid [ManagerName].[ResourceKey] format"); if (DesignHelpers.IsInDesignModeStatic) throw new Exception("Design mode is not supported"); return _resourceManager.GetResourceString(GetManagerKey(Key), GetResourceKey(Key)); } }
This allows us to bind elements to the LocalisationHelper and provide a “key” which will be used to lookup the correct resource manager and resource string. Our binding uses a little known syntax that looks like this (take note of the initial “.”):
{Binding Path=.[MyResourceManager.MyResourceString]}
To provide design support we throw an exception if we detect design mode (using code from Laurent Bugnion’s MVVM Light Toolkit) so the FallbackValue can be used instead. I’m not too keep on throwing an exception, but I couldn’t see a cleaner way to “fail” the binding.
The other important code hooks an event that fires when the locale changes, and fires a NotifyPropertyChangedEvent with an empty property string. This triggers a refresh for all controls that have bindings to the LocalisationHelper.
ResourceManagerService
The ResourceManagerService provides several functions:
- The ability to register ResourceManagers – these are automatically generated by Visual Studio when you create RESX files and are used to load the locale specific strings.
- Retrieve a resource string from a given ResourceManager.
- Get and set the current locale. A locale consists of an IETF language tag (such as en-GB) and a boolean to indicate whether the locale uses a right to left flow direction.
- A event that is fired when the locale changes. This is hooked by the LocalisationHelper, for firing PropertyChanged events, and also by the main Window which uses the right to left flag to set the flow direction.
Give Me The Code Already!
There are two samples attached. The first is a simple application that uses a static ResourceManagerService that shows the basic implementation. The second is a PRISM based application that uses the container/service locator, EventAggregator, weak references, and several different modules to give a more “advanced” example.
Conclusion
I haven’t yet used this solution in anger, but it certainly seems to “tick all the boxes” from my initial requirements. Comments, suggestions, criticisms and flames are welcomed 🙂
It is an interesting approach, and I don’t think I saw this before. In addition to the problems of the static binding and of the markup extension, you can add that these do not work in Silverlight (yet?) so you cannot have one approach for all situations with these two solutions.
Unfortunately, I am not sure that the solution you propose here works in Silverlight either. As far as I can remember, the array syntax is not supported in bindings in Silverlight. I didn’t test the “dot” syntax, but I am pretty sure that it will also fail in SL.
So what’s left if we want an ubiquitous solution? Well, what I do is that I declare some properties on my VM (or on a sub-VM dedicated to localization) and I bind to these properties. On the plus side: It is a fairly easy syntax, and uses the well known mechanisms of ViewModels. If the application needs to change language dynamically, you can use PropertyChanged events, which are also well known. This works in SL and in WPF.
On the down side however, declaring one VM property for every possible localizable string can be a total overkill in large applications.
For WPF applications, I think that the solution you propose is innovative and definitely worth analyzing further.
Finally, as usual when I talk about localization, I want to mention that the firm Alchemy is proposing a localization tool that claims to support WYSIWYG. There is a demo available on their website. I was in contact with them and need to spend some time evaluating the tool when I will have more time.
Cheers,
Laurent
Thanks for the comments Laurent, much appreciated.
I must admit I haven’t considered Silverlight yet, although I probably should have. It’s a shame that Binding is so closed off, it would be nice if we could extend it with something like:
Bindings.RegisterExtension(regEx, (src, ctx, opt) => { return ctx[opt.params[0]]; } );
Probably never going to happen though 🙂
Another Silverlight alternative would be to possibly use T4 to automatically generate INPC classes based on a resx, or even replace the standard resx compiler with something that produces INPC classes. I do like to stick to resx files, partly for the tooling support, and partly because I can just ship the XML files to a translation house to be translated.
For Silverlight (and this would work in WPF too), an interesting approach is posted by Tim Heuer: http://timheuer.com/blog/archive/2009/08/26/silverlight-string-localization.aspx
This approach can be leveraged into a “LocalizableViewModel” base class, like Corrado Cavalli is exposing here: http://groups.google.com/group/wpf-disciples/browse_thread/thread/acf4b88103a9872
I think I just found a new feature to add to the MVVM Light Toolkit…
Cheers,
Laurent
Tim’s approach is great for simple scenarios, but once you have several resource files, split amongst several projects, and want to potentially use resources across modules in a PRISM app then I’m not sure it’s going to be managable. The last point is the reason for maintaining a dictionary of ResourceManager, otherwise the solution is mostly the same 🙂
Yes I think that Tim’s approach is similar to yours except for the ResourceManagerService part. I wanted to mention it anyway because the syntax will be compatible for WPF and SL while unfortunately the array syntax doesn’t work in SL 🙁
My thought right now is to include a LocalizableViewModel in the MVVM Light Toolkit, which would cover simple scenarios, and then your solution with the ResourceManagerService is great for complex and distributed scenarios.
Nice work 🙂
Laurent
Yes, it’s a shame that Silverlight doesn’t support the “dot” or array syntax in bindings, it does come in handy at times – I’ve voted for “full binding support” on user voice 🙂
The ResourceManager does allow us to work with things other than strings too (images etc) but again, I don’t think that works in Silverlight, although I’ve not tried it in SL3.
At least all of these approaches avoid LocBAML and strong references to UIElements/Events 🙂
Steven,
Seems like the prism exemple zip file is invalid. i tried to open it in 2 different computers and I get an error messages. Please check.
Thanks
If you clear your browser cache and retry it should be fine. I’ve finally managed to troubleshoot this with someone and it looks like it was down to apache’s gzip corrupting the zip files in transfer in some scenarios (but working fine 99% of the time). Zips are now excluded from Apache’s compression and that seems to have sorted it!
Hi Steven,
This is really interesting. I reviewed the WPF localization guide on CodePlex and for the most part, I prefer your solution to any of those discussed in the document.
The one thing I like about the x:Static approach is the ability to catch binding errors at compile time. I take it this is impossible to achieve using standard binding? (Sorry, I’m pretty new to WPF so hopefully this isn’t a really dumb question).
Thanks!
Hello There. You’re right, there’s no way to static checking of normal bindings, you will have to use one of the standard binding debugging techniques (output window or trace listener are the ones I use). I’ve used this technique in quite a large project though and, if you stick to conventions for naming the resources, I haven’t seen any issues with it.
Hi guy, It sounds really good!
Hi, Neat post. There is a problem with your site in internet explorer, would test this IE still is the market leader and a large portion of people will miss your magnificent writing due to this problem.