24 July 2011

MVVMLight based language selection for Windows Phone 7

Updated 25-09-2011 with a new implementation of LanguageSettingsViewModel to make it compatible with SilverlightSerializer. It appeared that every 2nd tombstoning of this viewmodel it lost it’s CurrentLanguage property Updated 01-02-2011 with bug fix in SetLanguageFromCurrentLocale

With the advent of the ‘Mango’ release of Windows Phone 7 with 19 more countries getting a Marketplace, supporting more than your own native language or English becomes all the more important. I can tell from personal experience that for instance supporting German gives a tremendous boost to your downloads. Tremendous as in: I get almost as much downloads from Germany as from the whole USA. Apparently Windows Phone 7 is doing pretty well in Germany. So take note of this ‘Geheimtipp’ ;-)

I implemented the following solution, based on MVVMLight 4, which upon first startup automatically tries to select a language based upon the current UI locale, and if that fails, tries to select one from the same group (for instance, if I only have en-UK it tries to select the first language that starts with “en”, for instance en-US). And of course you give the possibility of selecting another language manually.

It works like this. First, I define a very simple class that describes the base properties of a language, i.e. the locale (for instance en-US) and the way you want this language described:

using System;
using GalaSoft.MvvmLight;

namespace Wp7nl.Globalization
{
  /// <summary>
  /// Supported languages
  /// </summary>
  public class Language : ViewModelBase, IEquatable<Language>
  {
    private string locale;
    public string Locale
    {
      get { return locale; }
      set
      {
        if (locale != value)
        {
          locale = value;
          RaisePropertyChanged(() => Locale);
        }
      }
    }

    private string description;
    public string Description
    {
      get { return description; }
      set
      {
        if (description != value)
        {
          description = value;
          RaisePropertyChanged(() => Description);
        }
      }
    }

    public override string ToString()
    {
      return Description;
    }

    public bool Equals(Language other)
    {
      return other != null && other.Locale.Equals(Locale);
    }

    public override bool Equals(object obj)
    {
      return Equals(obj as Language);
    }

    public override int GetHashCode()
    {
      return Locale.GetHashCode();
    }
  }
}

I may look like a lot but it’s only two properties and an implementation of Equals. I implemented it as a full view model, which is actually a bit overkill, but I tend to do this since I have a couple of code snippets that make this pretty easy anyway. The second class does actually all the work:

using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Threading;
using GalaSoft.MvvmLight;

namespace Wp7nl.Globalization
{
  /// <summary>
  /// A ViewModel class to handle language settings. 
  /// Override this class and add languages in the constructor
  /// </summary>
  public class LanguageSettingsViewModel : ViewModelBase
  {
    public LanguageSettingsViewModel()
    {
      AddLanguages(new Language { Description = "English", Locale = "en-US" });
    }

    private readonly ObservableCollection<Language> supportedLanguages =
       new ObservableCollection<Language>();

    /// <summary>
    /// Gets the supported languages.
    /// </summary>
    public ObservableCollection<Language> SupportedLanguages
    {
      get { return supportedLanguages; }
    }

    /// <summary>
    /// Determine current language
    /// </summary>
    /// <returns></returns>
    private Language GetDefaultLanguage()
    {
      // Try to select from current UI thread on full name
      var language = SupportedLanguages.Where(
        p => p.Locale == Thread.CurrentThread.CurrentUICulture.Name).FirstOrDefault();
      if (language == null)
      {
        // Try to select from current UI thread on 2 letter ISO code
        language =
          SupportedLanguages.Where(
            p => p.Locale.StartsWith(
              Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName)).FirstOrDefault();
      }
      if (language == null)
      {
        // Still no language: take the first one that starts with English
        language = SupportedLanguages.Where(p => p.Locale.Contains("en")).First();
      }

      return language;
    }

    private Language currentLanguage;
    /// <summary>
    /// Gets or sets the current language.
    /// </summary>
    /// <value>
    /// The current language.
    /// </value>
    public Language CurrentLanguage
    {
      get
      {
        return currentLanguage;
      }
      set
      {
        if (currentLanguage != value)
        {
          currentLanguage = value;
          RaisePropertyChanged(() => CurrentLanguage);
        }
      }
    }


    /// <summary>
    /// Sets the language from current locale.
    /// </summary>
    public void SetLanguageFromCurrentLocale()
    {
      if (CurrentLanguage == null)
      {
        CurrentLanguage = GetDefaultLanguage();
      }
      Thread.CurrentThread.CurrentUICulture = new CultureInfo(CurrentLanguage.Locale);
      Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture;
    }

    /// <summary>
    /// Adds the languages.
    /// </summary>
    /// <param name="languages">The languages.</param>
    public void AddLanguages(params Language[] languages)
    {
      if (languages != null && languages.Count() > 0)
      {
        foreach (var l in languages)
        {
          if (!supportedLanguages.Contains(l))
          {
            supportedLanguages.Add(l);
          }
        }
      }
    }
  }
}

Out of the box this thing only supports English-USA. To add more languages, you subclass this model and add your own languages:

using Wp7nl.Globalization;

namespace YourApp.ViewModels
{
  public class MyLanguagesViewModel : LanguageSettingsViewModel
  {
    public LanguageViewModel()
    {
       AddLanguages(new Language { Description = "Nederlands", Locale = "nl-NL" });
       AddLanguages(new Language { Description = "Deutsch", Locale = "de-DE" });
    }
  }
}

screenshot_7-24-2011_13.44.6.244Directly after you have created MyLanguagesViewModel, or retrieved it from tombstoning using my extension methods based upon SilverlightSerializer, you simply call the SetLanguageFromCurrentLocale and that either restores the selection last made by the user in the application itself, or tries to find the language that best fits what the user has selected as locale on his phone. To give the user an option to select a language you can, for instance, bind the models’ SupportedLanguages property to the ItemsSource property of a ListPicker and the CurrentLanguage to its SelectedItems property.

In my as of yet unreleased Mango version of Map Mania this looks like as displayed on the right:

This leaves, of course, still two things to do:

  1. Defining resources files with with the actual text
  2. Implement a class that makes these resource files bindable

This procedure is shown in the Windows Phone 7 Globalization Sample provided on MSDN so I won’t repeat them here.

4 comments:

Mark Monster said...

Good work Joost!

This is almost the exact solution I used for Buienradar.nl. Except for taking a different default language ;-) (Dutch of course).

Also a nice emulator skin! Where did you find it?

Keep up the good work.

Joost van Schaik said...

Thanks Mark. Sorry for stealing your thunder ;-). Anyway it's not an emulator skin, it a Windows Screenshot tool skin. See http://www.innovativetechguy.com/?p=13#awp::?p=13

Mark Serilia said...

Wow! great code! However I am confused how to make your own language mode, I am from the Philippines, we need to make an app that wil translat Filipino(tagalog) language to a native dialect in the philippines, is Filipino available already or should we create a language model for it? I WONDER HOW TO CREATE ONE ?? please help me, any suggestions,???? plsssss..
thanks :)

Joost van Schaik said...

@mark I don't think Filipino is supported by the Phone as a display language, nor do I think there's a language code for it (like en-US)