Dave Dave - 6 months ago 76
Android Question

Xamarin iOS localization using .NET

I'm trying to use .NET localization in a portable class library for a Xamarin iOS/Android project. I've followed the steps at:

How to use localization in C#

And have code which looks as follows:

string sText = strings.enter_movie_name;
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("fr");
sText = strings.enter_movie_name;
lblEnterMovieName.Text = sText;


I've also tried:

ResourceManager resman = new ResourceManager(typeof(MyApplication.strings));
string sText = resman.GetString("enter_movie_name", new CultureInfo("fr"));


I've created strings.resx with enter_movie_name equal to "Enter movie name:" and strings.fr.resx equal to "Entre la movie name:"

However, sText always ends up as "Enter movie name:". I can't seem to get the "Entre la movie name:" version out.

I also saw the post at Xamarin cross platform localization but couldn't work it out. I also don't want to use the iOS specific version at Localization on Xamarin.iOS as I'd like to be able to get at my strings from a platform independent library.

Can anyone point out where I'm going wrong?

Answer

I've created a bit of an ugly solution, but it works. What I've done is made a folder called 'strings' in my Portable Class Library (PCL) and inside that created files called:

  • strings.resx
  • strings_fr.resx
  • strings_ja_JP.resx

I've set all of these as Embedded Resource with custom tool as ResXFileCodeGenerator. This means I have a separate resource DLL for each language.

In iOS I can then get the locale by calling:

string sLocale = NSLocale.CurrentLocale.LocaleIdentifier;

I would guess there's an Android equivalent using Locale but I don't know what it is.

This gives me a string like "ja_JP" or "fr_FR" or "en_GB" (note they're underscores, not dashes). I then use this with the following static class I created to retrieve an appropriate resource manager and get the string from it.

If given a locale aa_BB it first looks for strings_aa_BB, then for strings_aa, then for strings.

public static class Localise
{
    private const string STRINGS_ROOT = "MyPCL.strings.strings";

    public static string GetString(string sID, string sLocale)
    {
        string sResource = STRINGS_ROOT + "_" + sLocale;
        Type type = Type.GetType(sResource);
        if (type == null)
        {
            if (sLocale.Length > 2) {
                sResource = STRINGS_ROOT + "_" + sLocale.Substring(0, 2); // Use first two letters of region code
                type = Type.GetType(sResource);
            }
        }
        if (type == null) {
            sResource = STRINGS_ROOT;
            type = Type.GetType(sResource);
            if (type == null)
            {
                System.Diagnostics.Debug.WriteLine("No strings resource file when looking for " + sID + " in " + sLocale);
                return null; // This shouldn't ever happen in theory
            }
        }
        ResourceManager resman = new ResourceManager(type);
        return resman.GetString(sID);
    }
}

An example of how this would be used (referring to the above code) would be:

string sText = Localise.GetString("enter_movie_name", sLocale);
lblEnterMovieName.Text = sText;

A significant downside of this is that all views will need to have their text set programatically, but does have the upside that the translations can be done once and then reused on many platforms. They also remain separate from the main code in their own DLLs and therefore can be recompiled individually if necessary.