Localizing an XBAP application without using LocBaml

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

9 Comments

  1. Al

    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;
    }
    }
    }

  2. Rick

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

  3. 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.)

  4. Tim

    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.

  5. Chad

    When I use Application.Current.FindResource, Blend does not render the UI. The app works, but Blend throws an exception error because it cannot find the resource.

    Suggestions?

  6. Not off the top of my head. It’s been a long while since I’ve worked with this, so my only feeble suggestion is to investigate some of the constructions that Blend has for doing certain things only at design time. The details escape me right now, but I think I saw something on the Expression Blog at Microsoft about this several months (perhaps even a year) ago.

  7. J3nonk

    You said above:
    ….
    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.

    Do you know how to change the app manifest to add all the localized resources file?
    can you share it with us?

    Thanks in advance.

  8. In theory, yes. You would use MageUI or the command-line equivalent to open up the deployment manifest and then generate a new manifest from there after adding the localized resource dlls. If you search my blog, I have something on using MageUI in order to get UAC elevation to work with XBAP deployed apps. The steps would be similar, I believe. But honestly, I felt at the time that it wasn’t worth investigating in depth, because the XAML localization method I’ve outlined fit my needs at the time perfectly. Sorry I can’t be of more help. Thanks for stopping by and asking, though. I appreciate every single visitor and comments even more!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.