March 2011

Volume 26 Number 03

Silverlight Localization - Tips and Tricks for Loading Silverlight Locale Resources

By Matthew Delisle | March 2011

Silverlight is a great framework for creating rich Internet applications (RIAs), but it doesn’t yet provide the robust support for localization that you enjoy in other components of the Microsoft .NET Framework. Silverlight does have .resx files, a simple ResourceManager class and an element in the project file. But after that you’re on your own. There are no custom markup extensions, and no support for the DynamicResource class.

In this article I’ll show you how to remedy all of these issues. I’ll present a solution that will allow a developer to load resource sets at run time, use any format for storing resources, change resources without recompiling and demonstrate lazy loading of resources.

This article is divided into three parts. First, I’ll develop a simple application using the localization process detailed by Microsoft. Next, I’ll present another localization solution that has some benefits over the standard process. Finally, I’ll round off the solution with a discussion of the back-end components needed to complete the solution.

The Standard Localization Process

I’ll start by building a Silverlight application that uses the localization process outlined by Microsoft. A detailed description of the process is available at msdn.microsoft.com/library/cc838238(VS.95).

The UI contains a TextBlock and an Image, as shown in Figure 1.

image: The App

Figure 1 The App

The localization process described by Microsoft uses .resx files to store the resource data. The .resx files are embedded in the main assembly or a satellite assembly and loaded only once, at application startup. You can build applications targeted at certain languages by modifying the SupportedCultures element in the project file. This sample application will be localized for two languages, English and French. After adding the two resource files and two images representing flags, the project structure looks like Figure 2.

image: Project Structure After Adding .resx Files

Figure 2 Project Structure After Adding .resx Files

I changed the build action for the images to content so I can reference the images using a less verbose syntax. I’ll add two entries to each file. The TextBlock is referenced via a property called Welcome, and the Image control is referenced via a property called FlagImage.

When resource files are created in a Silverlight app, the default modifier for the generated resource class is internal. Unfortunately, XAML can’t read internal members, even if they’re located in the same assembly. To remedy this situation, the generated class modifiers need to be changed to public. This can be accomplished in the design view of the resource file. The Access Modifier dropdown menu lets you designate the scope of the generated class.

Once resource files are ready, you need to bind the resources in XAML. To do this you create a wrapper class with a static field referencing an instance of the resource class. The class is as simple as this:

public class StringResources {
  private static readonly strings strings = new strings();
  public strings Strings {  get { return strings; } }
}

To make the class accessible from XAML, you need to create an instance. In this case, I’ll create the instance in the App class so that it’s accessible throughout the project:

<Application.Resources>
  <local:StringResources x:Key="LocalizedStrings"/>
</Application.Resources>

Data-binding in XAML is now possible. The XAML for the TextBlock and the Image looks like this:

<StackPanel Grid.ColumnSpan="2" Orientation="Horizontal" 
  HorizontalAlignment="Center">
  <TextBlock Text="{Binding Strings.Welcome, Source={StaticResource LocalizedStrings}}" 
    FontSize="24"/>
</StackPanel>
<Image Grid.Row="1" Grid.ColumnSpan="2" 
  HorizontalAlignment="Center"
  Source="{Binding Strings.FlagImage, Source={StaticResource LocalizedStrings}}"/>

The path is the String property followed by the key of the resource entry. The source is the instance of the StringResources wrapper from the App class.

Setting the Culture

There are three application settings that must be configured for the application to pick up the browser’s culture setting and display the appropriate culture.

The first setting is the SupportedCultures element in the .csproj file. Currently there’s no dialog box in Visual Studio to edit the setting, so the project file must be edited manually. You can edit a project file either by opening it outside of Visual Studio, or by unloading the project and selecting edit from the context menu within Visual Studio.

To enable English and French for this application, the value of the SupportedCultures element looks like this:

<SupportedCultures>fr</SupportedCultures>

The cultures values are separated by commas. You don’t need to specify the neutral culture; it’s compiled into the main DLL.

These next steps are necessary to pick up the browser language setting. A parameter must be added to the embedded Silverlight object in the Web page. The parameters value is the current UI culture, taken from the server side. This requires the Web page to be an .aspx file. The parameter syntax is:

<param name="uiculture" 
  value="<%=Thread.CurrentThread.CurrentCulture.Name %>" />

The final mandatory step in this process is to edit the web.config file and add a globalization element inside of the system.web element, setting the values of its attributes to auto:

<globalization culture="auto" uiCulture="auto"/>

As mentioned earlier, a Silverlight application has a neutral language setting. The setting is reached by going to the Silverlight tab of the project properties and clicking Assembly Information. The neutral language property is located at the bottom of the dialog, as shown in Figure 3.

image: Setting the Neutral Language

Figure 3 Setting the Neutral Language

I recommend setting the neutral language to one without a region. This setting is a fallback, and it’s more useful if it covers a wide range of potential locales. Setting a neutral language adds an assembly attribute to the assemblyinfo.cs file, like this:

[assembly: NeutralResourcesLanguageAttribute("en")]

After all that, what you end up with is a localized application that reads the browser language setting at startup and loads the appropriate resources.

A Custom Localization Process

The limitations of the standard localization process stem from the use of ResourceManager and .resx files. The ResourceManager class doesn’t change resource sets at run time based on culture changes within the environment. Using .resx files locks the developer into one resource set per language and inflexibility in maintaining the resources.

In response to these limitations, let’s look at an alternative solution that employs dynamic resources.

To make resources dynamic, the resource manager needs to send notification when the active resource set changes. To send notifications in Silverlight, you implement the INotifyPropertyChanged interface. Internally, each resource set will be a dictionary with a key and value type of string.

The Prism framework and the Managed Extensibility Framework (MEF) are popular for Silverlight development, and these frameworks break up the application into multiple .xap files. For localization, each .xap file needs its own instance of the resource manager. To send notifications to every .xap file (every instance of the resource manager), I need to keep track of each instance that gets created and iterate through that list when notifications need to be sent. Figure 4 shows the code for this SmartResourceManager functionality.

Figure 4 SmartResourceManager

public class SmartResourceManager : INotifyPropertyChanged {
  private static readonly List<SmartResourceManager> Instances = 
    new List<SmartResourceManager>();
  private static Dictionary<string, string> resourceSet;
  private static readonly Dictionary<string, 
    Dictionary<string, string>> ResourceSets = 
    new Dictionary<string, Dictionary<string, string>>();
  public Dictionary<string, string> ResourceSet {
    get { return resourceSet; }
    set { resourceSet = value;
    // Notify all instances
    foreach (var obj in Instances) {
      obj.NotifyPropertyChanged("ResourceSet");
    }
  }
}
public SmartResourceManager() {
  Instances.Add(this);
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string property) {
  var evt = PropertyChanged;
  if (evt != null) {
    evt(this, new PropertyChangedEventArgs(property));
  }
}

As you can see, a static list is created to hold all instances of the resource manager. The active resource set is stored in the field resourceSet and every resource that has been loaded is stored in the ResourceSets list. In the constructor, the current instance is stored in the Instances list. The class implements INotifyPropertyChanged in the standard way. When the active resource set is changed, I iterate through the list of instances and fire each one’s PropertyChanged event.

The SmartResourceManager class needs a way to change the culture at run time, and it’s as simple as a method that takes a CultureInfo object:

public void ChangeCulture(CultureInfo culture) {
  if (!ResourceSets.ContainsKey(culture.Name)) {
    // Load the resource set
  }
  else {
    ResourceSet = ResourceSets[culture.Name];
    Thread.CurrentThread.CurrentCulture = 
      Thread.CurrentThread.CurrentUICulture = 
      culture;
  }
}

This method checks to see if the requested culture has been loaded yet. If not, it loads the culture and then sets it as active. If the culture has already been loaded, the method simply sets the corresponding resource set as active. The code to load a resource is omitted for the moment.

For completeness, I’ll also show you the two methods to load a resource programmatically (see Figure 5). The first method takes just a resource key and returns the resource from the active culture. The second method takes a resource and a culture name and returns the resource for that specific culture. 

Figure 5 Loading Resources

public string GetString(string key) {
  if (string.IsNullOrEmpty(key)) return string.Empty;
  if (resourceSet.ContainsKey(key)) {
    return resourceSet[key];
  }
  else {
    return string.Empty;
  }
}
public string GetString(string key, string culture) {
  if (ResourceSets.ContainsKey(culture)) {
    if (ResourceSets[culture].ContainsKey(key)) {
      return ResourceSets[culture][key];
    }
    else {
      return string.Empty;
    }
  }
  else {
    return string.Empty;
  }
}

If you ran the application right now, all localized strings would be empty, because no resource sets have been downloaded. To load the initial resource set, I’m going to create a method named Initialize that takes the neutral language file and culture identifier. This method should be called only once during the application lifetime (see Figure 6).

Figure 6 Initializing the Neutral Language

public SmartResourceManager() {
  if (Instances.Count == 0) {
    ChangeCulture(Thread.CurrentThread.CurrentUICulture);    
  }
  Instances.Add(this);
}
public void Initialize(string neutralLanguageFile, 
  string neutralLanguage) {
  lock (lockObject) {
    if (isInitialized) return;
    isInitialized = true;
  }
  if (string.IsNullOrWhiteSpace(neutralLanguageFile)) {
    // No neutral resources
    ChangeCulture(Thread.CurrentThread.CurrentUICulture);
  }
  else {
    LoadNeutralResources(neutralLanguageFile, neutralLanguage);
  } 
}

Binding to XAML

A custom markup extension would provide the most fluid binding syntax for the localized resources. Unfortunately, custom markup extensions aren’t available in Silverlight. Binding to a dictionary is available in Silverlight 3 and later, and the syntax looks like this:

<StackPanel Grid.ColumnSpan="2" Orientation="Horizontal" 
  HorizontalAlignment="Center">
  <TextBlock Text="{Binding Path=ResourceSet[Welcome], Source={StaticResource 
SmartRM}}" FontSize="24"/>
</StackPanel>
<Image Grid.Row="1" Grid.ColumnSpan="2" 
  HorizontalAlignment="Center" 
  Source="{Binding ResourceSet[FlagImage], Source={StaticResource SmartRM}}"/>

The path contains the name of the dictionary property with the key in square brackets. If you’re using Silverlight 2, there are two options available: creating a ValueConverter class or emitting a strongly typed object using reflection. Creating a strongly typed object using reflection is beyond the scope of this article. The code for a ValueConverter would look like Figure 7.

Figure 7 Custom ValueConverter

public class LocalizeConverter : IValueConverter {
  public object Convert(object value, 
    Type targetType, object parameter, 
    System.Globalization.CultureInfo culture) {
    if (value == null) return string.Empty;
    Dictionary<string, string> resources = 
      value as Dictionary<string, string>;
    if (resources == null) return string.Empty;
    string param = parameter.ToString();
    if (!resources.ContainsKey(param)) return string.Empty;
    return resources[param];
  }
}

The LocalizeConverter class takes the dictionary and parameter passed in and returns the value of that key in the dictionary. After creating an instance of the converter, the binding syntax would look like this:

<StackPanel Grid.ColumnSpan="2" Orientation="Horizontal" 
  HorizontalAlignment="Center">
  <TextBlock Text="{Binding Path=ResourceSet, Source={StaticResource SmartRM}, Converter={StaticResource LocalizeConverter}, Convert-erParameter=Welcome}" FontSize="24"/>
</StackPanel>
<Image Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Center" 
  Source="{Binding ResourceSet, Source={StaticResource SmartRM}, Converter={StaticResource LocalizeConverter}, ConverterParameter=FlagImage}"/>

The syntax is more verbose using the converter, though you have more flexibility. However, for the rest of the article, I’ll continue without the converter, instead using the dictionary binding syntax.

Locale Settings Redux

There are two application settings that must be configured for the culture to be picked up by the Silverlight application. These are the same settings discussed with the standard localization process. The globalization element in the web.config file needs to have culture and uiCulture values of auto:

<globalization culture="auto" uiCulture="auto"></globalization>

Also, the Silverlight object in the .aspx file needs to be passed in the thread current UI culture value as a parameter:

<param name="uiculture" 
  value="<%=Thread.CurrentThread.CurrentCulture.Name %>" />

To showcase the dynamic localization of the application, I’m going to add a couple buttons that facilitate changing culture, as shown in Figure 8. The click event for the English button looks like this:

(App.Current.Resources["SmartRM"] as SmartResourceManager).ChangeCulture(
  new CultureInfo("en"));

image: Culture-Change Buttons

Figure 8 Culture-Change Buttons

With some mocked-up data, the application would run and display the appropriate language. The solution here allows for dynamic localization at run time and is extensible enough to load the resources using custom logic.

The next section focuses on filling in the remaining gap: where the resources are stored and how they’re retrieved.

Server-Side Components

Now let’s create a database to store resources and a Windows Communication Foundation (WCF) service to retrieve those resources. In larger applications, you’d want to create data and business layers, but for this example, I won’t be using any abstractions.

The reason I chose a WCF service is for the ease of creation and robustness offered by WCF. The reason I chose to store the resources in a relational database is for ease of maintenance and administration. An administration application could be created that would allow translators to easily modify the resources.

I’m using SQL Server 2008 Express for this application. The data schema is shown in Figure 9.

image: Schema for Localization Tables in SQL Server 2008 Express

Figure 9 Schema for Localization Tables in SQL Server 2008 Express

A Tag is a named group of resources. A StringResource is the entity representing a resource. The LocaleId column represents the name of the culture that the resource is under. The Comment column is added for compatibility with the .resx format. The CreatedDate and ModifiedDate columns are added for auditing purposes.

A StringResource can be associated with multiple Tags. The advantage of this is that you can create specific groups (for example, the resources for a single screen) and download only those resources. The disadvantage is that you can assign multiple resources with the same LocaleId, Key and Tag. In that case, you may want to write a trigger to manage creating or updating resources or use the ModifiedDate column when retrieving resource sets to determine which is the latest resource.

I’m going to retrieve the data using LINQ to SQL. The first service operation will take in a culture name and return all resources associated with that culture. Here’s the interface:

[ServiceContract]
public interface ILocaleService {
  [OperationContract]
  Dictionary<string, string> GetResources(string cultureName);
}

Here’s the implementation:

public class LocaleService : ILocaleService {
  private acmeDataContext dataContext = new acmeDataContext();
  public Dictionary<string, string> GetResources(string cultureName) {
  return (from r in dataContext.StringResources
          where r.LocaleId == cultureName
          select r).ToDictionary(x => x.Key, x => x.Value);
  }
}

The operation simply finds all resources whose LocaleId is equal to the cultureName parameter. The dataContext field is an instance of the LINQ to SQL class hooked up to the SQL Server database. That’s it! LINQ and WCF make things so simple.

Now, it’s time to link the WCF service to the SmartResourceManager class. After adding a service reference to the Silverlight application, I register to receive the completed event for the GetResources operation in the constructor:

public SmartResourceManager() {
  Instances.Add(this);
  localeClient.GetResourcesCompleted += 
    localeClient_GetResourcesCompleted;
  if (Instances.Count == 0) {
    ChangeCulture(Thread.CurrentThread.CurrentUICulture);
  } 
}

The callback method should add the resource set to the list of resource sets and make the resource set the active set. The code is shown in Figure 10.

Figure 10 Adding Resources

private void localeClient_GetResourcesCompleted(object sender, 
  LocaleService.GetResourcesCompletedEventArgs e) {
  if (e.Error != null) {
    var evt = CultureChangeError;
    if (evt != null)
      evt(this, new CultureChangeErrorEventArgs(
        e.UserState as CultureInfo, e.Error));
  }
  else {
    if (e.Result == null) return;
    CultureInfo culture = e.UserState as CultureInfo;
    if (culture == null) return;
    ResourceSets.Add(culture.Name, e.Result);
    ResourceSet = e.Result;
    Thread.CurrentThread.CurrentCulture = 
      Thread.CurrentThread.CurrentUICulture = culture;
  }
}

The ChangeCulture method needs to be modified to make a call to the WCF operation:

public void ChangeCulture(CultureInfo culture) {
  if (!ResourceSets.ContainsKey(culture.Name)) {
    localeClient.GetResourceSetsAsync(culture.Name, culture);
  }
  else {
    ResourceSet = ResourceSets[culture.Name];
    Thread.CurrentThread.CurrentCulture = 
      Thread.CurrentThread.CurrentUICulture = culture;
  }
}

Loading the Neutral Locale

This application needs a way to recover if the Web services can’t be contacted or are timing out. A resource file containing the neutral language resources should be stored outside of the Web services and loaded at startup. This will serve as a fallback and a performance enhancement over the service call.

I’m going to create another SmartResourceManager constructor with two parameters: a URL pointing to the neutral language resources file and a culture code identifying the culture of the resource file (see Figure 11).

Figure 11 Loading the Neutral Locale

public SmartResourceManager(string neutralLanguageFile,   string neutralLanguage) {
  Instances.Add(this);
  localeClient.GetResourcesCompleted += 
    localeClient_GetResourcesCompleted;
  if (Instances.Count == 1) {
    if (string.IsNullOrWhiteSpace(neutralLanguageFile)) {
      // No neutral resources
      ChangeCulture(Thread.CurrentThread.CurrentUICulture);
    }
    else {
      LoadNeutralResources(neutralLanguageFile, neutralLanguage);
    }
  }
}

If there’s no neutral resource file, the normal process of calling the WCF call is performed. The LoadNeutralResources method uses a WebClient to retrieve the resource file from the server. It then parses the file and converts the XML string into a Dictionary object. I won’t show the code here as it’s a bit long and somewhat trivial, but you can find it in the code download for this article if you’re interested. 

To call the parameterized SmartResourceManager constructor, I need to move the instantiation of the SmartResourceManager into the code-behind of the App class (because Silverlight doesn’t support XAML 2009). I don’t want to hardcode the resource file or the culture code, though, so I’ll have to create a custom ConfigurationManager class, which you can check out in the code download.

After integrating the ConfigurationManager into the App class, the Startup event callback method looks like this:

private void Application_Startup(object sender, StartupEventArgs e) {
  ConfigurationManager.Error += ConfigurationManager_Error;
  ConfigurationManager.Loaded += ConfigurationManager_Loaded;
  ConfigurationManager.LoadSettings();
}

The startup callback method now serves to load the application settings and register for callbacks. If you do choose to make the loading of the configuration settings a background call, be careful of the race conditions that you can run into. Here are the callback methods for the ConfigurationManager events:

private void ConfigurationManager_Error(object sender, EventArgs e) {
  Resources.Add("SmartRM", new SmartResourceManager());
  this.RootVisual = new MainPage();
}
private void ConfigurationManager_Loaded(object sender, EventArgs e) {
  Resources.Add("SmartRM", new SmartResourceManager(
    ConfigurationManager.GetSetting("neutralLanguageFile"), 
    ConfigurationManager.GetSetting("neutralLanguage")));
  this.RootVisual = new MainPage();
}

The Error event callback method loads SmartResourceManager without a neutral language, and the Loaded event callback method loads with a neutral language.

I need to put the resource file in a location where I don’t have to recompile anything if I change it. I’m going to put it in the ClientBin directory of the Web project, and after creating the resource file, I’m going to change its extension to .xml so that it’s publicly accessible and the WebClient class can access it from the Silverlight application. Because it’s publicly accessible, don’t put any sensitive data in the file.

ConfigurationManager also reads from the ClientBin directory. It looks for a file called appSettings.xml, and the file looks like this:

<AppSettings>
  <Add Key="neutralLanguageFile" Value="strings.xml"/>
  <Add Key="neutralLanguage" Value="en-US"/>
</AppSettings>

Once appSettings.xml and strings.xml are in place, ConfigurationManager and SmartResourceManager can work together to load the neutral language. There’s room for improvement in this process, because if the thread’s active culture is different than the neutral language and the Web service is down, the thread’s active culture will be different than the active resource set. I’ll leave that as an exercise for you.

Wrapping Up

What I didn’t go over in this article was normalizing the resources on the server side. Let’s say that the fr-FR resource is missing two keys that the fr resource has. When requesting the fr-FR resources, the Web service should insert the missing keys from the more general fr resource.

Another aspect that’s built into this solution that I didn’t cover is loading resources by culture and resource set instead of just culture. This is useful for loading resources per screen or per .xap file.

The solution I walked you through here does allow you to do a few useful things, however, including loading resource sets at run time, using any format for storing resources, changing resources without recompiling and lazy loading resources.

The solution presented here is general-purpose, and you can hook into it in multiple points and drastically change the implementation. I hope this helps reduce your daily programming load.

For further in-depth reading about internationalization, check out the book “.NET Internationalization: The Developer’s Guide to Building Global Windows and Web Applications” (Addison-Wesley, 2006), by Guy Smith-Ferrier. Smith-Ferrier also has a great video on internationalization on his Web site; the video is called “Internationalizing Silverlight at SLUGUK” (bit.ly/gJGptU).


Matthew Delisle enjoys studying both the software and hardware aspects of computers. His first daughter was born in 2010and he thinks she’s almost ready to begin her programming career.

Thanks to the following technical expert for reviewing this article: John Brodeur