Localizing an XBAP application without using LocBaml

December 12, 2006 4:23 pm

Hi! It looks like you're new here. Welcome! If you like what you see, I encourage you to subscribe to my RSS feed. If you're worried about adding another RSS feed that will add to your information overload, don't! I'm not a prolific poster. Thanks for visiting!

Using the LocBaml sample application that Microsoft provides with the Windows SDK for Windows Presentation Foundation can be very frustrating. By default it seems to pick up a lot of things that clutter the resulting CSV file. If your application contains a lot of image resources, those will get duplicated in the localized resource dlls as well, increasing the space that your application occupies on the hard drive. And finally, getting an XBAP application to be deployable after localization with LocBaml requires opening up the application manifest and adding all the localized resource files to it. Otherwise a deployment error will bite you.

So with all those problems, we decided to take a different approach for our WPF XBAP applications. We put all our strings into a standalone XAML file, producing a ResourceDictionary. This way our application can pick up strings using Text=”{StaticResource strXYZ}” for XAML markup and/or use Application.Current.FindResource(”strXYZ”) for codebehind. We name the file “Strings_en-US.xaml”. We can localize this file nicely, and name the resulting set appropriately: “Strings_de-DE.xaml”, “Strings_fr-FR.xaml” and so on. These files can then be included as resources in the application. They can also be added as “loose” XAML files, so we can add languages or make corrections after compilation.

The string resource file looks something like this:



<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
  <sys:String x:Key="strButtonOK">OK</sys>
  <sys:String x:Key="strButtonCancel">Cancel</sys>
</ResourceDictionary>


At application load time, we look at the CurrentCulture and attempt to load the ResourceDictionary object like this:


ResourceDictionary rd = null;
System.Globalization.CultureInfo ci =
     System.Globalization.CultureInfo.CurrentCulture;
string langCountry = ci.Name;
string languageFile =
     string.Format("/Resources/Strings/Strings_{0}.xaml", langCountry);
try
{
  rd = (ResourceDictionary)Application.LoadComponent(
                              new Uri(languageFile, UriKind.RelativeOrAbsolute)
                                  );
}


You can imagine this with some catch {} fallbacks for when a specific language-country combination doesn’t exist. We’ll fall back to a file that’s only language specific, and if that doesn’t work out, we’ll fall back to en-US.

We’re not using CurrentUICulture, since that seems to be tied to the UI language of Vista, and we want our apps to follow what the user sets in Control Panel. CurrentCulture seems to work for that.

To load the strings into the global resource dictionary, we just do


Application.Current.Resources.MergedDictionaries.Add(rd);

To load loose XAML files that are not part of the application’s resources, we do something like this:


ResourceDictionary rd1 = null;
string languageFileLoose = string.Format(
         "pack://siteoforigin:,,,/Resources/Strings/Strings_{0}.xaml",
         langCountry
      );
Uri uri = new Uri(languageFileLoose, UriKind.Absolute);
System.Windows.Resources.StreamResourceInfo info;
System.Windows.Markup.XamlReader reader;
try {
  info = Application.GetRemoteStream(uri);
  reader = new System.Windows.Markup.XamlReader();
  rd1 = (ResourceDictionary)reader.LoadAsync(info.Stream);
}

Again, we extend this with catch {} blocks to fall back to a file that’s only language specific. We do the loose XAML loading after we’ve loaded embedded resources, so we can rely on a basic set of languages and strings, but can add languages, fix string translation errors or make improvements in the loose XAML after compiling things.

It may work for you as well, but ymmv.

Update: Changed the first Uri to UriKind.RelativeOrAbsolute

4 Responses to “Localizing an XBAP application without using LocBaml”

Al wrote a comment on September 22, 2007

Hi,

I think this way is way better than localization with Locbaml (at least for strings). The problem we have with Locbaml (as far as I tested) is that if a new localizable (e.g. Window) is added to your project AFTER localization process, you’ll need to regenerate the whole .csv file, so what will happen to all already localized content?

I’ve changed your code to contain fallback to default culture when a culture-specific resource is not found. Here it is :

public static ResourceDictionary LoadExternalResourceDictionary(string xamlFilename, CultureInfo culture)
{
string cultureName = string.Empty;

if (culture != null)
cultureName = string.Concat(”.”, culture.Name);

if(Path.HasExtension(xamlFilename))
{
string ext = Path.GetExtension(xamlFilename);
xamlFilename = xamlFilename.Remove(xamlFilename.Length - ext.Length);
}

try
{
string looseFile = string.Format(”pack://siteoforigin:,,,/{0}{1}.xaml”, xamlFilename, culture != null ? cultureName : string.Empty);
Uri uri = new Uri(looseFile, UriKind.Absolute);

System.Windows.Markup.XamlReader reader = new System.Windows.Markup.XamlReader();
System.Windows.Resources.StreamResourceInfo info = System.Windows.Application.GetRemoteStream(uri);

return reader.LoadAsync(info.Stream) as ResourceDictionary;
}
catch
{
if(culture != null)
{
return LoadExternalResourceDictionary(xamlFilename, null);
}
else
{
throw;
}
}
}

Rick wrote a comment on October 3, 2007

Any idea how to load default language so that application won’t loop crapy in design view?

GeekTieGuy wrote a comment on October 3, 2007

Sorry, I haven’t had a need to look into that. So I don’t know.

However, one thing you could try is put default strings into all XAML elements (TextBox, Label, TextBlock, etc.) and then replace them at runtime, for example in the Loaded event (or something that fires earlier.)

Tim wrote a comment on May 13, 2008

Regarding the issue with the design view and the default language - you can define a RD for your default language in your App.xaml and later optionally add another RD in the described way, the new items will hide the items defined for the default language. BTW for review purposes you could also build an “identity” RD on-the-fly mapping the key value to the key value. With such a auxilary construct you can display your forms showing your keys instead of the values.

Care to comment?

Close
E-mail It