вторник, 25 марта 2008 г.

XAML localization

Hi guys!

At last I've decided to start english version of my blog. Honestly, I don't see any reasons to keep the blog in my native language as it seems to bring no use to anyone.

And it is not a coincidence that my first blog entry is dedicated to localization in WPF.

I've come across localization issues when I started developing presentation layer for my project with WPF. And my firm belief was that English and only English was default language of international applications (surprisingly that MS guys apparently think same way too), but it has crashed

So I started looking for solution and found a number of articles at codeproject and msdn:
WPF Globalization and Localization.

All of these entries solved the problem and suggested ready solutions with its pros and cons (in my sight), but it was not enough. Here I give short description of these approaches:

  • Localization via use of resx files and custom code generator for public resource class (this generator is used due to wpf binding engine restriction - you can bind data only from public properties/fields in public classes) (1st article): pros - supports design-time; easy to use (just create ObjectDataProvider, put your resources' class in it and bind your strings to DEPENDENCY properties of objects in xaml). Cons result from pros (pardon me my English) - first of all we are strongly restricted in use, 'cause binding can be applied ONLY to the dependency properties of the DependencyObject descendants; and second disadvantage is that additional VS installation (custom resource generator tool) is required.
  • Localization via LocBaml (simplified variant - it uses ResourceDictionary with localizable strings that are used by merging this dictionary in Application.Resources.MergedDictionaries and referring to them by means of DynamicResource extension)(1st article): it is still not straightforward enough, because Locbaml forces us to compile binaries twice.
  • Localization via LocBaml (official MS tutorial) (articles #1, #5): see above.
  • The approach in the 3rd article is similar to the 1st item. Main difference here is that article author writes wrappers of resources classes manually. But I've got almost 50 projects in my solution. I don't want to write this bunch of classes manually. (Suddenly I thought that Paul Stovell's idea with custom type descriptors could be useful here... or wrapping could become less complicated via file reference in VS... I'll think about it later... Main advantage of described solution is the ability to switch languages in runtime.
  • Localization via xml files, XmlDataProvider, XPath queries in Binding (4th article): I consider this to be the most elegant solution among all listed here. Pros - design-time support and runtime language switching. The only disadvantage I can think of is complexity of files' maintenance.

Well, after collecting different solutions I came to following requirements:

  • ability to localize clr properties of objects created in xaml;
  • easy use;
  • no need to write wrappers and use custom tools;
  • design-time support (the only requirement here is ability of this xaml to be opened in Blend).

I shaked (not stirred) everything and saw the solution - xaml markupextension + resx files.
The idea is straightforward - keeping all resources in resx files results in easy maintenance of localizable data in VS (.Net 2.0 localization rocks!); in xaml you substitute the string with this markup extension. The extension needs a resource Id that you provide, and it returns back your localized string with this Id in parsing time or error message in case of any exceptions.

It looks like this:

<TextBlock Text={me:CultureResource resourceId}/>
, where me: - namespace declaration where the CultureResourceExtension class declared.

The markupextension doesn't suffer from restriction of DependencyObject and allows localizing any clr object in XAML. Disadvantages are - lack of design-time support, or more properly, highly restricted support (Blend is able to open a file without crashing, but instead of expected strings you can see "ResourceStr", - our designer said it was not critical =) ).
Also resource class name should be directly assigned to the extension in case of:

  • assembly name differs from default namespace;
  • resx file is not in projects /Properties subfolder;
  • in both cases;

The extension has a BaseName property, where you can supply your resources class name.
To simplify this case I've written a helper class BaseBinder with attached property Base that inherits in visual tree.

There exists another case where this solution is not effective: text, formatted with WPF document API. I've solved this problem by putting this formatted xaml in resx file and then rendering it with my custom control XamlViewer.

Here is main extension method:

Good luck!

2 комментария:

John "Z-Bo" Zabroski комментирует...

what does vtsvc stand for?

thanks for the english blog! :D

RobertT комментирует...

sorry I've noticed your comment too late. For some reason there was no e-mail notification from blogspot on your comment. Answering your question, vtsvc is a reduction of IProvideValueTarget that has a perfect description in MSDN