Click here to Skip to main content
15,886,422 members
Articles / Desktop Programming / WPF

Globalization in WPF or Silverlight

Rate me:
Please Sign up or sign in to vote.
4.00/5 (3 votes)
14 Mar 2010CPOL3 min read 29K   352   15   10
Dynamic globalization for WPF and Silverlight apps without breaking design time

Introduction

In this C# demo, I'll show how to add globalization with some sense of error detection to a WPF or Silverlight application. This will enable the developer to create an application where the user can choose in which language to display information as well as other cultural settings rather than having them be based on their Windows settings. For example, on a public or group use computer, the application can allow the user to choose their language and render the application in that language. You might see this in a program providing for employment applications and testing.

The other goal is to make the strings visible at design time in Visual Studio or from Expression Blend. (Note the Common.dll or CommonSL.dll needs to be available for Expression Blend to be able to see the strings.) This allows your artist to come in after the developers are done and review the application as the user would see it.

The Code

We'll create a class called PublicResource which implements both IValueConverter and INotifyPropertyChanged. This class is used to make public the bits of a resx file that aren't normally available. It will also hold a Converter for use in XAML.

To start with, create a child private class that inherits from ResourceManager. Some of this will look very familiar from the regular designer.cs for your resx file. Note how we're adding a way to display a default message if the string isn't found in the two GetString() methods.

C#
public class PublicResource : IValueConverter, INotifyPropertyChanged
{
  private class ResourceManagerWithErrors 
        : global::System.Resources.ResourceManager
  {
    public ResourceManagerWithErrors(Type resourceSource) 
        : base(resourceSource) { }
    public ResourceManagerWithErrors(string baseName, Assembly assembly) 
        : base(baseName, assembly) { }
    public ResourceManagerWithErrors(string baseName, Assembly assembly,
        Type usingResourceSet)
        : base(baseName, assembly, usingResourceSet) { }

    public string NotFoundMessage = "#Missing#";

    public override string GetString(string name)
    {
      return base.GetString(name) ?? NotFoundMessage + name;
    }

    public override string GetString(string name, CultureInfo culture)
    {
      return base.GetString(name, culture) ?? NotFoundMessage + name;
    }
  }

  private static ResourceManagerWithErrors _stringResourceManager;
  private static ResourceManagerWithErrors StringResourceManager
  {
    get
    {
      if (object.ReferenceEquals(_stringResourceManager, null))
      {
        _stringResourceManager = 
            new ResourceManagerWithErrors("WPFApplication1.Strings",
            typeof(PublicResource).Assembly);
        _stringResourceManager.NotFoundMessage = "#StringResourceMissing#";
      }
      return _stringResourceManager;
    }
  }

Be sure to replace WPFApplication1.Strings with the namespace and name of your resx file (without the .resx).

Next we implement IValueConverter so we can use the class in XAML. By moving the logic of finding the string to the Converter, we can dynamically build the globalized text.

There’s a bit of error detection making sure that value is set. We'll get to that in a bit. Note that we lookup the string using the CurrentUICulture and not the culture that is passed in. The culture passed in is the global culture that doesn't change.

C#
public object Convert(object value, Type targetType,
    object parameter, CultureInfo culture)
{
  if ((value == null) || !(value is string))
    return "set Binding Path/Source!";

  return StringResourceManager.GetString(
          (string)parameter,
          System.Threading.Thread.CurrentThread.CurrentUICulture);
}

public object ConvertBack(object value, Type targetType,
    object parameter, CultureInfo culture)
{
  throw new NotImplementedException("No reason to do this.");
}

Next we need a default string. We'll bind to this and override it with the Converter. If the path fails to be resolved, the converter never runs. We make it a constant so the converter always runs.

C#
public static string AString { get { return "AString"; } }

Next we need some way to tell the application to change the culture. We do this by notifying that the one single property that everyone is going to point to has changed. If you are using generated code for your resx file, set the culture for it as well.

C#
public event PropertyChangedEventHandler PropertyChanged;

public void ChangeCulture(string culture)
{
  // set App culture and the resource file culture
  System.Threading.Thread.CurrentThread.CurrentUICulture =
      new System.Globalization.CultureInfo(culture);
  System.Threading.Thread.CurrentThread.CurrentCulture =
          System.Threading.Thread.CurrentThread.CurrentUICulture;
  Strings.Culture = 
          System.Threading.Thread.CurrentThread.CurrentUICulture;

  // notify that the culture has changed
  PropertyChangedEventHandler handler = PropertyChanged;

  if (handler != null)
    handler(this, new PropertyChangedEventArgs("AString"));
}

Finally, since PublicResource is acting like a static class, but we can't call NotifyCultureChanged without an instance, we'll save a reference to itself in its own constructor.

C#
public static PublicResource Myself;

public PublicResource() 
{ 
  Myself = this; 
}

That rounds out the PublicResource class. Let’s see how it is used. In the XAML, we add a TextBlock and call the converter.

XML
xmlns:res="clr-namespace:WPFApplication1;assembly=Common"

<Window.Resources>
  <res:PublicResource x:Key="Resource"/>
</Window.Resources>

<TextBlock Text="{Binding Path=AString, 
    Source={StaticResource Resource}, Mode=OneWay,
    Converter={StaticResource Resource}, ConverterParameter=HelloWorld}"/>

Be sure to change out WPFApplication1 and Common with the name and assembly name for where to find the PublicResource class.

So, what happens here? The Binding goes and looks for a property in PublicResource named AString. This gets returned as "AString". Once this is done, the converter gets executed. This goes and looks up the parameter HelloWorld in the appropriate resource file and returns whatever it is mapped to based on the current culture. If the string isn't found in the resx file, it will return #StringResourceMissing#HelloWorld. This is pretty easy to spot on the interface rather than a completely missing string.

To dynamically change the culture, we call:

C#
PublicResource.Myself.ChangeCulture("es-ES");

We need to set the full culture name, not just "es".

For Silverlight applications, you also need to edit the .csproj file for the client application. Modify the SupportedCultures tag with a comma delimited list of the resx files. There’s nothing to include for the default one. These codes have to match the codes on the resource files. If you want es-MX and es-ES to be different, then you'll need to support both in both places.

XML
<SupportedCultures>en,de,es</SupportedCultures>

I hope this has helped you out with your Internationalization, Globalization and Localization efforts.

History

  • 14th March, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
SuggestionThanks and small contribution Pin
LauLoInfo23-Oct-12 4:04
LauLoInfo23-Oct-12 4:04 
GeneralRe: Thanks and small contribution Pin
mikewishart23-Oct-12 4:10
mikewishart23-Oct-12 4:10 
GeneralSilverlight 4 + ChildWindow Pin
Mikael Bager25-Aug-10 20:52
Mikael Bager25-Aug-10 20:52 
GeneralRe: Silverlight 4 + ChildWindow Pin
mikewishart26-Aug-10 10:30
mikewishart26-Aug-10 10:30 
GeneralHelp needed Pin
Devasasi7-Apr-10 4:32
Devasasi7-Apr-10 4:32 
GeneralRe: Help needed [modified] Pin
mikewishart8-Apr-10 20:16
mikewishart8-Apr-10 20:16 
GeneralRe: Help needed Pin
Devasasi13-Apr-10 0:57
Devasasi13-Apr-10 0:57 
GeneralRe: Help needed Pin
hanni832-Aug-10 1:27
hanni832-Aug-10 1:27 
GeneralRe: Help needed Pin
mikewishart3-Aug-10 6:00
mikewishart3-Aug-10 6:00 
GeneralNot a bad start, made it public but there are many more ways to do localization in WPF Pin
Sacha Barber14-Mar-10 0:19
Sacha Barber14-Mar-10 0:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.