Perspective 0.5 : A light process for WPF localizationPerspective 0.5 : A light process for WPF localization
Principle and implementation
The standard localization process for WPF applications is relatively cumbersome and unsuited to a on the fly localization of a small application under development.
Indeed, it relies on the outsourcing of BAML resources (binary resources corresponding to XAML files) in satellite assemblies. A basic utility program, LocBaml (provided as a sample application with the SDK), will retrieve textual data resource from BAML to put them into a new satellite assembly, once translated. Beforehand, all the elements to translate must have been marked by an attribute x:Uid, ideally automatically created by MsBuild. The result is that XAML code is riddled with x:Uid attributes having automatically generated names, thereby reducing its readability. In addition, BAML resources are duplicated in satellite assemblies, and any amendment of the XAML code requires a regeneration of all the translated satellite assemblies. In other words, the localization is not really possible until the end of the development cycle.
So I developed in Perspective library an alternative by using classical .NET resources string for all text properties to be localized. Thanks to the standard satellite assemblies mechanism, the application can easily be localized during the development phase.
The implementation is very simple :
- Add in the project a .resx resource file for each XAML file, in the base language of the application, and create a resource for each text to be localized. I.e., for a given XAML file MainWindow.xaml, create a MainWindow.resx file in a folder named Strings.

- Add in the project a .resx resource file for each targeted culture, i.e. Strings\MainWindow.fr.resx, and translate the resources.

- In the XAML file, add a ResourceStringDecorator (from Perspective 0.5) as container of all the elements to localize.
- Indicate in the BaseName property the name of the .resx file, without extension, and prefixed with the namespace, i.e. :
<p:ResourceStringDecorator BaseName="PerspectiveDemo.Strings.MainWindow">
...
</p:ResourceStringDecorator>
- For each property to localize, use the ResourceString markup extension (from Perspective 0.5) to reference the corresponding resource in the .resx file.
<TabItem Header="{p:ResourceString Content}" >When executing, the resources corresponding to the current culture are automatically loaded (MainWindow.fr.resx for the French language).

If the satellite assembly is not found for the current culture, default resources are loaded (MainWindow.resx).

Architecture
The 2 concerned classes are in the namespace Perspective.Wpf.ResourceStrings.
ResourceStringDecorator inherits from Decorator, and has a BaseName dependency property. When the value of this one changes, a ResourceManager is instanciated, and referenced by the ResourceManager dependency property.
public string BaseName
{
get { return (string)GetValue(BaseNameProperty); }
set { SetValue(BaseNameProperty, value); }
}
public static readonly DependencyProperty BaseNameProperty =
DependencyProperty.Register(
"BaseName",
typeof(string),
typeof(ResourceStringDecorator),
new FrameworkPropertyMetadata(
"",
OnBaseNameChanged
));
private static void OnBaseNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!DesignerProperties.GetIsInDesignMode(d))
{
SetResourceManager(d, new ResourceManager(
(string)e.NewValue,
Assembly.GetEntryAssembly()));
}
}ResourceManager is an "heritable" property : each child element acquires automatically this property (and its value), as if it were its own property.
public static ResourceManager GetResourceManager(DependencyObject obj)
{
return (ResourceManager)obj.GetValue(ResourceManagerProperty);
}
private static void SetResourceManager(DependencyObject obj, ResourceManager value)
{
obj.SetValue(ResourceManagerProperty, value);
}
public static readonly DependencyProperty ResourceManagerProperty =
DependencyProperty.RegisterAttached(
"ResourceManager",
typeof(ResourceManager),
typeof(ResourceStringDecorator),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.Inherits));ResourceStringExtension inherits from MarkupExtension. It has a Name property, indicating the name of the resource to use. The ProvideValue method is overloaded to return the value of the resource. For this, it gets a reference on the object owner of the property, through the IProvideValueTarget service. Once it has this reference, it uses its ResourceManager property, "inherited" (acquired) from the ResourceStringDecorator object. The GetString() method of the ResourceManager returns then the resource's value.
public override object ProvideValue(IServiceProvider serviceProvider)
{
string value = "";
IProvideValueTarget target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
if (target != null)
{
DependencyObject d = (DependencyObject)target.TargetObject;
if ( d != null)
{
ResourceManager rm = ResourceStringDecorator.GetResourceManager(d);
if (rm != null)
{
value = rm.GetString(_name);
}
}
}
return value;
}About this article
Author: Olivier Dewit.
History:
- January 13, 2008 : For information, the next release of Perspective will make it possible to build multilingual applications (applying resources at runtime when the culture changes).
- January 8, 2008 : 1st publication (Perspective version 0.5).