Microsoft Dot Net Master

Microsoft Dot Net Master
Microsoft Dot Net Master

Monday, July 23, 2012

Localization of a WPF application using a custom MarkupExtension


The Idea

A simple and effective way to localize application resources is to write a custom MarkupExtension that provides a localized value. The extension takes a parameter in the constructor that is the unique resource key. When the DepdendencyProperty asks for the value, the markup extension looks up the value from a generic resource provider. This gives you the flexibility to reuse the resource management and translation process that is already established within your company.
Using a custom markup extension has some advantages
  • Lightweight and flexible solution
  • Dynamic language switch at runtime
  • Works with any kind of resource providers.

Other Implementations

The idea of using a markup extension for localization is not unique by me. There are also other similar implementations out there. Here you find some links:

How to use it

The usage of the markup extension is very simple. Just replace the string you want to localize by {l:Translate resourceKey}.
 
 <TextBlock Text="{l:Translate CustomerForm.FirstName}" />
 
 

Implementation details of the translation infrastructure

The translation infrastructure consists of the folling parts
  • Translation manager
    The translation manager is a static class that manages the current language and notifies all markup extensions, to update their values when the language changes. It also provides access to translated resources. The resources itself are provided by a generic translation provider.
  • Translate markup extension
    The tanslate markup extension knows the resource key and provides the translated value. It listens to the LanguageChanged event of the translation manager and updates its value. This event handler is implemented by the weak event pattern to prevent memory leaks.
  • Translation provider
    The translation provider is a class that provides the translated resources. It has to implement the ITranslationProvider and can access any kind of resources you like. For e.g. ResX, XML or text files.
 
public class TranslateExtension : MarkupExtension
{
    private string _key;
 
    public TranslateExtension(string key)
    {
        _key = key;
    }
 
    [ConstructorArgument("key")]
    public string Key
    {
        get { return _key; }
        set { _key = value;}
    }
 
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var binding = new Binding("Value")
              {
                  Source = new TranslationData(_key)
              };
        return binding.ProvideValue(serviceProvider);
    }
}
 
 
 
public class TranslationData : IWeakEventListener, 
                  INotifyPropertyChanged, IDisposable
{
    private string _key;
 
    public TranslationData( string key)
    {
        _key = key;
        LanguageChangedEventManager.AddListener(
                  TranslationManager.Instance, this);
    }
 
    ~TranslationData()
    {
        Dispose(false); 
    }
 
 
    public void Dispose() 
    { 
        Dispose(true); 
        GC.SuppressFinalize(this); 
    } 
 
    protected virtual void Dispose(bool disposing) 
    { 
        if (disposing) 
        { 
          LanguageChangedEventManager.RemoveListener(
                    TranslationManager.Instance, this); 
        } 
    } 
 
 
    public object Value
    {
        get
        {
            return TranslationManager.Instance.Translate(_key);
        }
    }
 
    public bool ReceiveWeakEvent(Type managerType, 
                            object sender, EventArgs e)
    {
        if (managerType == typeof(LanguageChangedEventManager))
        {
            OnLanguageChanged(sender, e);
            return true;
        }
        return false;
    }
 
    private void OnLanguageChanged(object sender, EventArgs e)
    {
        if( PropertyChanged != null )
        {
            PropertyChanged( this, new PropertyChangedEventArgs("Value"));
        }
    }
 
    public event PropertyChangedEventHandler PropertyChanged;
}
 
 
 
public class TranslationManager
{
    private static TranslationManager _translationManager;
 
    public event EventHandler LanguageChanged;
 
    public CultureInfo CurrentLanguage
    {
        get { return Thread.CurrentThread.CurrentUICulture; }
        set
        {
           if( value != Thread.CurrentThread.CurrentUICulture)
           {
               Thread.CurrentThread.CurrentUICulture = value;
               OnLanguageChanged();
           }
        }
    }
 
   public IEnumerable<CultureInfo> Languages
   {
       get
       {
           if( TranslationProvider != null)
           {
               return TranslationProvider.Languages;
           }
           return Enumerable.Empty<CultureInfo>();
       }
    }
 
    public static TranslationManager Instance
    {
        get
        {
            if (_translationManager == null)
                _translationManager = new TranslationManager();
            return _translationManager;
        }
    }
 
    public ITranslationProvider TranslationProvider { get; set; }
 
   private void OnLanguageChanged()
   {
        if (LanguageChanged != null)
        {
            LanguageChanged(this, EventArgs.Empty);
       }
    }
 
   public object Translate(string key)
   {
       if( TranslationProvider!= null)
       {
            object translatedValue =TranslationProvider.Translate(key);
            if( translatedValue != null)
           {
               return translatedValue;
           }
        }
       return string.Format("!{0}!", key);
    }
}
 
 

No comments:

Post a Comment