Search code examples
localizationxamarin.iosxamarin.androidwindows-phone-8mvvmcross

Localising text in MvvmCross ViewModels


I'd like to get all my localised text from the ViewModels (as it's often dynamic), and I was wondering how you'd use a converter to get the text from the json files used for localisation. For instance, in the code below, I'd like LocalisedString to use the converter which I currently use in my Views in the bindings for static text -

public string MyText // used in the binding in the View
{
    get
    {
        string exclamation;

        if (MyValue <= 3.3)
        {
            exclamation = LocalisedString("Rubbish!");
        }
        else if (OverallScore > 3.3 && OverallScore <= 6.6)
        {
            exclamation = LocalisedString("Good!");
        }
        else
        {
            exclamation = LocalisedString("Excellent!");
        }

        return exclamation;
    }
}

Currently using version 1 of MvvmCross.

Any help much appreciated.


Solution

  • Note: this answer is about vNext - it should be fairly easy to port back to master... The differences in the area aren't that big.


    There is a text localisation mechanism built into MvvmCross.

    The only public sample that uses it is the Conference Sample.


    This sample includes shared and ViewModel specific Json files - see

    These Json files each contain simple key-value pairs like:

    {
    "Title":"SQLBits X",
    "Welcome":"Welcome",
    "Sessions":"Sessions",
    "Sponsors":"Sponsors",
    "Tweets":"Tweets",
    "Favorites":"Favorites"
    }
    

    They are linked in to Droid, Touch and WP as content or assets... all accessed by the platform using the ResourceLoader plugin.


    To use these JSON files at runtime, the core project loads them up in the TextProviderBuilder:

        protected override IDictionary<string, string> ResourceFiles
        {
            get
            {
                var dictionary = this.GetType()
                    .Assembly
                    .GetTypes()
                    .Where(t => t.Name.EndsWith("ViewModel"))
                    .Where(t => !t.Name.StartsWith("Base"))
                    .ToDictionary(t => t.Name, t => t.Name);
    
                dictionary[Constants.Shared] = Constants.Shared;
                return dictionary;
            }
        }
    

    You could obviously easily load other JSON files here if you wanted to. It's not uncommon for some of my apps to have:

    • a file for errors
    • a file for general shared statements
    • a file for specific components
    • a file per ViewModel

    While others have:

    • just one big file!

    Internationalisation - when it is done - is done by loading a different set of JSON files. Typically what is done is you load the default set first, then you load incremental overrides - so you might load English as default, Cat as an override and Cat-Lol as a further refinement.

    For some discussions on this see:


    Assuming you have one shared file and one file per ViewModel, then to provide runtime access to the text values from the JSON, the BaseViewModel presents 2 properties:

        public IMvxLanguageBinder TextSource
        {
            get { return new MvxLanguageBinder(Constants.GeneralNamespace, GetType().Name); }
        }
    
        public IMvxLanguageBinder SharedTextSource
        {
            get { return new MvxLanguageBinder(Constants.GeneralNamespace, Constants.Shared); }
        }
    

    These properties are used in data binding using:

    • Path specifying whether to use SharedTextSource or TextSource
    • MvxLanguageBinderConverter as the Converter
    • the text key as the ConverterParameter

    For example, in Droid this is :

    <TextView
      style="@style/AboutPageBodyText"
      local:MvxBind="{'Text':{'Path':'TextSource','Converter':'Language','ConverterParameter':'Title'}}"
      />
    

    Although in modern 'Swiss' binding this would be written as:

    <TextView
      style="@style/AboutPageBodyText"
      local:MvxBind="Text TextSource, Converter=Language, ConverterParameter='Title'"
      />
    

    Any code which wishes to use the Text can also do it - see for example how the TimeAgo text is created from resource strings in TimeAgoConverter.cs which uses resource strings like:

    {
    "TimeAgo.JustNow":"just now",
    "TimeAgo.SecondsAgo":"{0}s ago",
    "TimeAgo.MinutesAgo":"{0}m ago",
    "TimeAgo.HoursAgo":"{0}h ago",
    "TimeAgo.DaysAgo":"{0}d ago",
    "TimeAgo.Never":"never"
    }
    

    The code for this is effectively:

     var valueToFormat = 42;
     var whichFormat = "TimeAgo.DaysAgo";
    
     var textProvider = this.GetService<IMvxTextProvider>();
     var format = textProvider.GetText(Constants.GeneralNamespace, Constants.Shared, whichFormat);
    
     return string.Format(format, valueToFormat)
    

    The language Binder and the ValueConverter are really very simple code

    So feel free to build something more sophisticated for your app if you need it.


    Other cross-platform text localisation techniques are available - I myself would particularly like to try Vernacular one day - https://github.com/rdio/vernacular