Mittwoch, 1. Mai 2013

Using resx-files for localization in MvvmCross

With the release of Xamarin.Android 4.6.3 there was a small not in the release notes saying:
5037: Support satellite assemblies. 
This is actually awesome. (At least I think so XD). Up to now we had to use the json-localization-plugin included in MvvmCross. As I do not like to edit json and I guess any translator does not like it as well, I prefer the resx-stuff. 

In this blog-post I do want to describe how to use resx-Files all around your Xamarin.Android/Xamarin.iOS and Windows Phone 8 Solution (do not target WinRT and WPF, but they should actually be a nobrainer as they use resx by default :-)).

First of all, the fix mentioned before did not really fix the satellite assemblies-problem. You have to fix an additonal file by yourself. So start with this:
Filename: Xamarin.Android.Common.targets

At this point, thanks to Jonathan Pryor  for the awesome support on this!

This fixed, we can start by creating all the resx-stuff we need :-)

Additional Note:  I'm working in a solution already containing an android/touch/WP8 project. All of them ar up and running using the latest NuGet-Stuff provided by Stuart :-)

Step 1: Create PCL-Project for the resources
First I called this projects Test.Resources. Never do that unless you do not want to support Windows Phone.. ! Read why here:

This is why I renamed this project to Test.Localization :-)

Add a reference from each project where you need the localization to this new pcl-project.

Step 2: Create Resources Files
Add a single resources-file. I called it "Strings.resx". Add a second Resource-File called ""

Make sure your first resources-file has the visibility-modifier set to public. Otherwise we will not be able to see the resources from any other assembly. All the additional translations do not need to generate the code. This means you can set them to "No code generation"

Step 3: Add a new class "ResxTextProvider" to your Core-Project.

using System.Diagnostics;
using System.Globalization;
using System.Resources;
using System.Threading;
using Cirrious.MvvmCross.Localization;
namespace Test.Core
    public class ResxTextProvider : IMvxTextProvider
        private readonly ResourceManager _resourceManager;
        public ResxTextProvider(ResourceManager resourceManager)
            _resourceManager = resourceManager;
            CurrentLanguage = Thread.CurrentThread.CurrentUICulture;
        public CultureInfo CurrentLanguage { get; set; }
        public string GetText(string namespaceKey, string typeKey, string name)
            string resolvedKey = name;
            if (!string.IsNullOrEmpty(typeKey))
                resolvedKey = string.Format("{0}.{1}", typeKey, resolvedKey);
            if (!string.IsNullOrEmpty(namespaceKey))
                resolvedKey = string.Format("{0}.{1}", namespaceKey, resolvedKey);
            return _resourceManager.GetString(resolvedKey, CurrentLanguage);
        public string GetText(string namespaceKey, string typeKey, string name, params object[] formatArgs)
            string baseText = GetText(namespaceKey, typeKey, name);
            if (string.IsNullOrEmpty(baseText))
                return baseText;
            return string.Format(baseText, formatArgs);

Step 4: Initialize your ResxTextProvider in your App-Class:

public class App : MvxApplication
    public override void Initialize()
        Mvx.RegisterSingleton(new ResxTextProvider(Strings.ResourceManager));

Here is the point where we pass the ResourceManager from our generated String-class to the ResxTextProvider.

Step 5: Extend your ViewModel with a TextSource:

public IMvxLanguageBinder TextSource
    get { return new MvxLanguageBinder("", GetType().Name); }

In this example I do not pass a Namespace to the MvxLanguageBinder. This means that the GetText-Method of our ResxTextProvider is always called with an empty first parameter. The second parameter will be the current Type. As we are in the MainViewModel, this well be "MainViewModel". 

Step 6: Register Language Converter
protected override void FillValueConverters(IMvxValueConverterRegistry registry)
    registry.AddOrOverwrite("Language", new MvxLanguageConverter());
Add this line in your Setup.cs in your android app. Not sure whether this could be improved by the MvvmCross-Framework. I think it should *hint* *hint* :-)

Step 7: Start using MvxBind in your android XML-Code.
      local:MvxLang="Text test1" />
Android should now work fine, let's move to iOS:
Her is not that much to do. Simply use the MvxLanguageConverter to bind the TextSource to the Label (or whatever) and set the resx-key as CommandParameter.
You know an easier way? Leave a comment :-)
var bindingSet = this.CreateBindingSet();
    bindingSet.Bind(Label1).To(ViewModel => ViewModel.TextSource)
           .WithConversion(new MvxLanguageConverter(),"test1")
   bindingSet.Bind(Label2).To(ViewModel => ViewModel.TextSource)
    .WithConversion(new MvxLanguageConverter(),"test2")

What has to be said, when building the generated resx-stuff on Mac it fails as it can not find any of this annotation. Simply delete all of them, we do not really need them :-)

All that done, move ahead to Windows Phone. 
As resx-files is the common idea of doing the localization in WindowsPhone, I do not want to explain much on this. Just give a short compressed version of this great tutorial:

Steps on Windows Phone:

  1.  Add ressources to App.xaml (you could also add this to any other xaml file.. but when adding here we do have access to the localized stuff from every screen...)
  2. Make sure your app does support all the cultures you have translations for (right click project -> properties)
  3. Use the resoruces with a binding and StaticResource:
<TextBlock Text="{Binding Path=Strings.MainViewModel_test1, Source={StaticResource Strings}}" />

All my code is available on GitHub:

Hope I did not miss anything, in case of a not working or any improvement, please leave a comment :-)