Options for localizing Silverlight applications

As a very small group of people might have noticed, my blog has been more or less dead for the last couple of weeks…or months to be honest… The reason for this is that I have had a bit to do as I have packed up my life in New Zealand and moved back to Sweden. This is a little bit more time consuming that you’d expect, so I have had to move my focus away from certain things. And unfortunately, my blog was one of the things I had to axe.

The move back to Sweden did however make me think about localization, as building apps in Sweden often means adding at least 2 languages, Swedish and English. The topic has been in the back of my mind for a long time, but I never really settled on a good way to handle the whole thing. However, at this point in time, I felt that I really had to come up with some options. And with the introduction of a specific feature in Silverlight 5 called markup extensions, I felt that it might be time to have a look at it.

There are ways to do localization using built in functionality in Silverlight, but for different reasons, I have just never liked it very much. And to be honest, I find that localization very rarely fits in nicely anywhere. It is almost always as if it has been something that have come up at the end and just forced into place, instead of something that has been well thought through and integrated nicely.

So far, the simplest and easiest way of handling localization that I have come across is the way it is handled in EPiServer, a Swedish CRM built on .NET. They have sidestepped the default ASP.NET localization methods, and built their own based on XML. So with this in my mind, I decided to build something similar, although VERY much simpler.

The first thing I wanted was to add an abstraction on top of the language stuff. So I created a simple interface that looks like this

public interface ILanguageManager
{
string GetTranslatedString(string identifier);
string GetTranslatedString(string identifier, CultureInfo culture);
}

As you can see, it is a very simple interface. So far, it only supports localized strings, but could obviously be extended to support any form of localized resource. This interface will be used by “all” my localization options, as I like having the actual functionality abstracted away.

This interface could obviously be hiding the ins and outs of the System.Resources.ResourceManager if that was the preferred way to access localized resources. In this case however, I have implemented my own version, based on the EPiServer one as mentioned before.

The string resources in this case will all be stored in an XML file. The XML file has a root element called languages, which has one child element called language for each supported language. Each one of these language elements has a culture attribute, except the default language. Inside these language elements, the developer is allowed to create any hierarchy of elements that he or she feels necessary/user-friendly. So it ends up something like this

<?xml version="1.0" encoding="utf-8" ?>
<languages>
<language>
<homepage>
<text>Default value</text>
</homepage>
</language>
<language culture="en">
<homepage>
<text>Hello World</text>
</homepage>
</language>
<language culture="en-US">
<homepage>
<text>Whazzup yall!</text>
</homepage>
</language>
<language culture="sv-SE">
<homepage>
<text>Hej Världen!</text>
</homepage>
</language>
</languages>

As you can see, I have supported both English and Swedish, and 2 different versions of English as well. And I have also decided to group the translated strings based on what page they are on. This might not be such a brilliant idea, but it works for this simple sample…

The actual implementation isn’t that complicated, and to be honest has very little to do with this post, as my main focus is the stuff that uses the manager. But I will walk through it quickly anyway…

First of all, the class that implements the ILanguageManager interface, also implements the interface called IApplicationService. This makes it possible to place the language manager in the applications lifetime objects list, and get notified when it starts and stops. It uses this information to load and unload the XML. As the application starts up, it loads the XML into an XDocument, and when it shuts down, it removes the reference to that document.

The path to the actual XML file is set as a property on the class when the class is registered in the App.xaml file like this

<Application ...>
...
<Application.ApplicationLifetimeObjects>
<languages:LanguageManager XmlDocumentPath="Resources/TranslatedStrings.xml" />
</Application.ApplicationLifetimeObjects>
</Application>

The implementation of the GetTranslatedString() method is not very complicated. If a CultureInfo object is not supplied, it defaults to CulturInfo.CurrentCulture, and calls the overloaded version that takes a CulturInfo object. This method in turn looks at the CulturInfo object and tries to find a suitable language element in the XDocument. It starts by looking for one where the culture attribute corresponds to the CulturInfo object’s name. If this is unsuccessful, it looks for one with out the region. And finally, if that does not succeed, it looks for one without a culture attribute all together.

Once it finds the correct element, it then uses XPath to locate the translated string based on the supplied identifier. However, it replaces “.” characters with “/” before creating the XPath expression. This makes it possible to use identifiers that use a dotted syntax like you would in code, instead of using slashes.

The code for this implementation is available in the download, so I won’t show it here…

Anyhow…once I have this implementation, it is registered in the ApplicationLifetimeObjects in App.xaml. So now it is available to anyone who wants to use it… And that brings us to the core of this post. The 3 different ways that we can consume this service and display the translated strings in the UI.

But before I look at these 3 ways, I want to mention that none of them is based on using a view model. I personally do NOT think this is the responsibility of the VM. I love using MVVM, but translating strings is a UI thing that should be possible to solve without adding a million and one properties in the VM and then use databinding to get it into the view. It makes the VM bloated and ugly according to me…

So, the first option I had was to use resources. Resources is the good old way to solve a lot of things, and I have seen a lot of suggestions to use a resource class to wrap the .NET globalization features. This works fine, and isn’t that hard to build…

This is how I did it. I added a new class called TranslatedStrings, and declared a property for each one of the strings I had translated. In this case, I have one…so the class looks like this

public class TranslatedStrings
{
public string Homepage_Text
{
get
{
EnsureLanguageManager();
return CurrentLanguageManager.GetTranslatedString("homepage.text");
}
}
}

Piece of cake… But yes, there is a bit more… The EnsureLanguageManager is there for a reason. It makes sure that the class has access to an instance of ILanguageManager.

The implementation of that method is quite simple. It checks if a property called CurrentLanguageManager is set, otherwise it sets it before returning. However, it actually sets it to one of 2 instances. If the code executes while in design time, it creates an instance of a class called DesignTimeLanguageManager. This class implements ILanguageManager by simply returning "Translated Text" in all situations. And if it isn’t in a designer, it iterates through the ApplicationLifetimeObjects list looking for an instance of ILanguageManager.

Note: Returning "Translated Text" degrades the design time experience and should be solved. But I am currently looking at the best way to solve it, and it isn’t as simple as you would have expected…

So here is the code for that

public class TranslatedStrings
{
public string Homepage_Text { ... }

private void EnsureLanguageManager()
{
if (CurrentLanguageManager == null)
{
if (DesignerProperties.IsInDesignTool)
{
CurrentLanguageManager = new DesignTimeLanguageManager();
return;
}

CurrentLanguageManager = GetLanguageManager();
}
}

private static ILanguageManager GetLanguageManager()
{
foreach (var item in Application.Current.ApplicationLifetimeObjects)
{
if (item is ILanguageManager)
return (ILanguageManager)item;
}
throw new InvalidOperationException("Cannot get the current language manager when none has been specified");
}

public static ILanguageManager CurrentLanguageManager { get; private set; }

private class DesignTimeLanguageManager : ILanguageManager
{
public string GetTranslatedString(string identifier)
{
return GetTranslatedString(identifier, CultureInfo.CurrentCulture);
}
public string GetTranslatedString(string identifier, System.Globalization.CultureInfo culture)
{
return "Translated Text";
}
}
}

Using this solution is not hard or complicated at all. All that is needed is to place an instance of the TranslatedStrings class as a resource either in the usercontrol/page or in the application resources, and then use a binding to use it

<TextBlock Text="{Binding Homepage_Text, Source={StaticResource TranslatedStrings}}" />

The upside to this way of handling translated information is that we get good tooling support for setting up the databinding. The downside is that we have to create a new property for each one of the translated strings…

The 2nd option I have looked at is using attached properties, a feature that very few people seem to use anymore now that we have actions and behaviors and things. It is still very useful though. And in this case, the syntax becomes nice and short compared to using behaviors or actions.

An attached property is declared like any other DependencyProperty, but using the RegisterAttached method instead of the Register. In this case, I have created a static class called Globalization that exposes a single attached property called TextIdentifier of type string.

In the static constructor, it does pretty much the same as the previous option did in the EnsureLanguageManager method, which means sets up an ILanguageManager instance depending on whether or not the code runs in a designer or not.

public static class Globalization
{
public static DependencyProperty TextIdentifierProperty = DependencyProperty.RegisterAttached("TextIdentifier", typeof(string), typeof(Globalization), new PropertyMetadata(null, OnTextIdentifierChanged));

static Globalization()
{
if (DesignerProperties.IsInDesignTool)
{
CurrentLanguageManager = new DesignTimeLanguageManager();
return;
}

CurrentLanguageManager = GetLanguageManager();
}

public static string GetTextIdentifier(DependencyObject obj)
{
return (string)obj.GetValue(TextIdentifierProperty);
}

public static void SetTextIdentifier(DependencyObject obj, string value)
{
obj.SetValue(TextIdentifierProperty, value);
}

public static void OnTextIdentifierChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
...
}

public static ILanguageManager GetLanguageManager()
{
...
}

public static ILanguageManager CurrentLanguageManager { get; private set; }

private class DesignTimeLanguageManager : ILanguageManager
{
...
}
}

The only thing needed to get it to work is to implement a callback handler for the “Changed event” for the DependencyProperty. This implementation is fairly simple, it pulls out the translated string from the defined ILanguageManager and sets the Text property of the object that had the property set. But as the type of object is not known, the code resorts to using reflection to find a property named Text.

I know that using reflection is slow, but sometimes it is the best, or even only, solution. In this case it won’t be called thousands of times over and over, so the performance hit is ok. If it isn’t, it is always possible to resort to more specialized implementations…

public static void OnTextIdentifierChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var t = sender.GetType();
var propInfo = t.GetProperty("Text");
if (propInfo != null)
{
propInfo.SetValue(sender, CurrentLanguageManager.GetTranslatedString((string)args.NewValue), null);
}
}

That’s it. There is nothing more that is required. Using it however is a little bit different from the previous sample. We do not have to use the resources or set anything up (except the registration of the ILanguageManager in the ApplicationLifeTimeObjects).

<TextBlock g:Globalization.TextIdentifier="homepage.text" />

The upside to this solution is that we do not need to set the resources, and we do not have to declare a new property for each one of the localized resources. However, it is harder to use as the tools do not support attached properties very well. It is also a bit riskier to declare string identifiers like this in the XAML, but I still like the solution as I don’t have to bloat the resources collection with things like this…

The final option is using a Silverlight 5 feature called markup extensions. It has been around for ages in WPF, but it is new to Silverlight. It means that we can extend the XAML markup with new “curly brace features”. So instead of just having {Binding} and {StaticResource} etc, we can now add our own curly brace defined features. And it is simple to do, which is really nice…

The first step is to create a new class that implements IMarkupExtension<T>, with T set to string in this case. T is the type that this extension returns. This defines a single method called ProvideValue(IServiceProvider serviceProvider). On top of this single method, the developer is allowed to add as many properties as he or she likes. These properties can be defined in the XML just as Path and Source is set when using the {Binding} syntax. We can however not declare a default property like {Binding} does, which makes it possible to omit “Path=” in the {Binding}. In this case, I have declared a string property called Identifier. This will, to no big surprise be used to identify what string to get.

But before we look at the actual implementation of the markup extension, I want to mention that it also includes the same functionality as the rest of the options. In the static constructor, it verifies whether or not the code runs in a designer, and then sets the ILanguageManager based on this. Just as before…so I will omit the code for that…

The interesting part in this case is the ProvideValue(IServiceProvider serviceProvider) method. All we have to do here, is return a string to be used. So in this case, it returns CurrentLanguageManager.GetTranslatedString(Identifier). Very simple…

So with some of the repetitive code removed, it looks like this

public class GlobalizedString : IMarkupExtension<string>
{
static GlobalizedString()
{
...
}

public string ProvideValue(IServiceProvider serviceProvider)
{
return CurrentLanguageManager.GetTranslatedString(Identifier);
}

public string Identifier
{
get;
set;
}

private static ILanguageManager GetLanguageManager()
{
...
}
public static ILanguageManager CurrentLanguageManager
{
get;
private set;
}

private class DesignTimeLanguageManager : ILanguageManager
{
...
}
}

Using this markup extension is really simple. It works just like the {Binding} syntax. The only difference is that, as it isn’t a built in extension, you have to remember to add the correct xmlns attribute to the XAML. But once this is done, you can just write something like this

<TextBlock Text="{g:GlobalizedString Identifier=homepage.text}" />

Unfortunately, at the moment, the designer doesn’t evaluate this expression correctly, so it doesn’t show the design time text at design time. But I hope this will be fixed in a future beta or at least when Silverlight 5 is released. But except for that detail, I can’t find any problems with using this option except the lack of tooling. But all in all, I like it as it doesn’t require properties for each of the strings, and it doesn’t use reflection to do its job. Instead it will work in any place a string is required.

So these are the options I am looking at at the moment, and as soon as Silverlight 5 is released, I think the markup extension option will my personal favorite. And to be honest, I think I will use markup extensions for a lot of things… But for now, I will have to choose between the other options, and I can’t really make up my mind. They both have pros and cons… So it is up to you to decide, or come up with something better. And I would love it if you gave some feedback through the comments…

As for downloads, I have one solution available for those who haven’t got Silverlight 5 installed, and one for those of you who do. So far I have had no problems having the Silverlight 5 beta installed at the same time as working with 4. So installing it seems fine, and doesn’t break Silverlight 4 development. Just remember to change targeted framework to 4 when creating a new project, as 5 is the default…

Code for Silverlight 4: DaksideCookie.Ag.GlobalizationDemo - Silverlight 4.zip (25.19 kb)

Code for Silverlight 5: DaksideCookie.Ag.GlobalizationDemo - Silverlight 5.zip (26.43 kb)

That’s all folks! Cheers!

Comments (3) -

If Silverlight 5 would support markup extensions, why not utilize markup extensions in a proper way. I've created a Localizer markup extension by extending the MarkupExtension class. That means I can  use locations in the same way I use Binding or StaticResource.

<Label Content="{Loc 'text', Section='Homepage'}" />
<Label Content="{Loc 'text', section='Homepage', StringFormat='{0:x}'} " />
<Image Source="{Loc 'Flag', Section='Flags', Culture='en-US'}" />

Without the Culture property the localizer uses the UICulture by default.

My XML looks this:
<Localization>
   <Culture type='en-US'>
      <Text key='Text' section='Homepage'>This is the text for my homepage</Text>
      <Text key='Number' section='Homepage'>42</Text>
      <Image key='Flag' section='Flags'>... base64 encoded string ..</Image>
   </Culture>
</Localization>

I use the 'key' and 'sectiom' attributes so I can lowercase both the dictionary keys as the key from the markup extension.

Hi Dave!
Sorry for my slow reply, it seems like my comment notification e-mails aren't being delivered as they should...

I agree that the MarkupExtension should be better than the one I provided. Mine was only supposed to be an example of how you can separate the concerns and build a simple version.

I like your solution, even though I might suggest not putting the section as an attribute and instead make it an element. That way the XML file will be very easy to read

<Localization>
   <Culture type="en-US">
      <Homepage>
         <Text key="Text">This is...</Text>
      </Homepage>
   </Culture>
</Localization>

See where I am going?

My biggest annoyance though is the fact that MarkupExtensions aren't visible in design time, which is "by design", which sucks!

So from that point of view, the attached properties works best for my unfortunately, even if I really wanted a markupextension...

// Chris

Hi,

Override ToString() in your markup extension to get some design time support.

HTH

Comments are closed