25 March 2012

Instant language update in a Windows Phone 7 application using MVVM

With more and more Marketplaces being added to the Windows Phone ecosystem, globalization of your app becomes all the more more important. I am happy to see a lot of apps these days support multiple languages. I usually employ a solution described in my article MVVMLight based language selection for Windows Phone 7 – this automatically picks up the language from the phone and defaults to English/US if the default language is not supported by the app. But I also like to give the user the chance to override the automatically selected language. I, for instance, run the phone OS in English/US, but I’d like specific Dutch apps to run in Dutch, thank you. If you follow the globalization example as described by MSDN using my code, that unfortunately requires the app to be restarted after applying the language change. Well, no more!

The sample as provided by MSDN provides a localized string helper that looks more or less like this:

namespace InstantLanguage.Resources
{
  public class LocalizedStrings
  {
    public LocalizedStrings()
    {
    }
  
    private static AppResources localizedResources = new AppResources();

    public AppResources LocalizedResources 
    { 
      get { return localizedResources; } 
    }
  }
}

I employed it since my first localized app, and I usually put it in the same directory as my resource files. You declare it in your App.xaml like this:

<Application.Resources>
    <!-- More resources -->
    <Resources:LocalizedStrings x:Key="LocalizedStrings"/>
</Application.Resources>
Of course you need to declare the namespace for LocalizedResources first, so in the top you will add something like
xmlns:Resources="clr-namespace:InstantLanguage.Resources"

And now you can use it to show localized strings by binding to it like this:

<TextBlock x:Name="ApplicationTitle" 
  Text="{Binding LocalizedResources.AppTitle, Source={StaticResource LocalizedStrings}}" 
  Style="{StaticResource PhoneTextNormalStyle}"/>

But unfortunately, as I said, if you change the language, for instance by calling this code, nothing happens.

Thread.CurrentThread.CurrentUICulture = new CultureInfo("de-DE");
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture;

The reason for this is pretty simple: the app has no way of informing the GUI, as LocalizedStrings has no support for INotifyPropertyChanged. But that can be easily fixed, by taking MVVMLight and implement LocalizedStrings as a child class of ViewModelBase:

using System.Windows;

namespace InstantLanguage.Resources
{
  using GalaSoft.MvvmLight;

  public class LocalizedStrings : ViewModelBase
  {
    private static AppResources localizedresources = new AppResources();

    public AppResources LocalizedResources
    {
      get { return localizedresources; }
    }

    public void UpdateLanguage()
    {
      localizedresources = new AppResources();
      RaisePropertyChanged(() => LocalizedResources);
    }

    public static LocalizedStrings LocalizedStringsResource
    {
      get
      {
        return Application.Current.Resources["LocalizedStrings"]
            as LocalizedStrings;
      }
    }
  }
}

and then there is another matter: the app needs to have a way of kicking off the RaisePropertyChanged event on a class used as a StaticResource. That’s what UpdateLanguage and the static LocalizedStringsResource are for. Note that for this to work, you will need to have defined the resource with the key LocalizedStrings:

<Resources:LocalizedStrings x:Key="LocalizedStrings"/>

The keys that need to match are marked in red in both pieces of code. Anyway, after you changed the language, call:

LocalizedStrings.LocalizedStringsResource.UpdateLanguage();

And boom – all your texts coming from the resource file will update instantaneously.

InstantLanuage

Here you can find a sample solution demonstrating this principle using the newest release of my #wp7nl library on codeplex and the technique I mentioned earlier. There’s a simple viewmodel subclassing LanguageSettingsViewModel, and you can see the call to UpdateLanguage directly after the language setting has been changed:

using System.ComponentModel;
using InstantLanguage.Resources;
using Wp7nl.Globalization;

namespace InstantLanguage.ViewModel
{
  /// <summary>
  /// Main view model. By subclassing LanguageSettingsViewModel we get properties 
  /// "SupportedLanguages" and "CurrentLanguage" for free. 
  /// </summary>
  public class MainViewModel : LanguageSettingsViewModel
  {

    public MainViewModel()
    {
      // Note: en-US is added by default
      AddLanguages(new Language 
	    { Description = "Deutsch", Locale = "de-DE" });
      AddLanguages(new Language 
	   { Description = "Nederlands", Locale = "nl-NL" });
      PropertyChanged += MainViewModelPropertyChanged;
    }

    void MainViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
      if (e.PropertyName == "CurrentLanguage")
      {
        SetLanguageFromCurrentLocale();
        LocalizedStrings.LocalizedStringsResource.UpdateLanguage();
      }
    }
  }
}

In the end, it’s always pretty simple.

Note that for this globalization and the code to work properly, you will need to take into account the following:

  • You will need to change the “Custom Tool” property for each resource file to “PublicResXFileCodeGenerator” (default is ResXFileCodeGenerator)
  • You will need to have a .resx file for every language you support. For the default language I call mine usually AppResources.resx, for German I then have to define AppResources.de-DE.resx, etc.
  • You will also need to add supported extra languages in the SupportedCultures tag in your main project. You need to do manually, by opening the project file in for instance notepad. Why this can’t be done from Visual Studio – don’t ask me, I’m just the messenger here ;-). To support Dutch and German for instance you change the tag’s contents to:
<SupportedCultures>nl-NL;de-DE</SupportedCultures>

Note: I got the idea from a gentleman I met at the 2012 edition of the Microsoft Techdays in The Hague while while I was manning at the Ask The Expert stand. He showed me it could be done, but did only briefly showed me some of code and certainly not all of it. Once I knew it could be done, I more or less pieced this together in the moments I was not being bombarded by questions from developers dropping by. Unfortunately I don’t remember the gentleman’s name, or else I would have added some credits.

Have fun! I hope this helps you storm the new Marketplaces! ;-)

4 comments:

Mark Monster said...

This is exactly how I implemented it in the iBood app I made for WP7. Good work, another article that I don't have to write anymore :-).

Joost van Schaik said...

@Mark oops. Stole your thunder again ;-). Sorry ;-)

Unknown said...

First thanks for the posting this!

However my question is, this seems only working for xaml. Do you know a generic way to update resources defined in code-behind?

Joost van Schaik said...

@Justin, as I said on twitter ;-) - nope. If you want to update stuff that's already on the screen, you have to update it manually. That's why I use MVVM. I don't like doing things manually that can be automated ;-)