App Localization

Today mobile is not only limited to call and messages but also they are widely used for entertainment, shopping, study and much more. Now mobile is used to sell products and services across the globe, if we talk about globe then it simply means many countries with many languages. Translating your app into multiple languages is called App Localization and in this blog, I'm going to explain how to create a multilingual app in Xamarin.Forms (Android and iOS) step by step.

 
Step 1 - Create a blank Xamarin.Forms application named “AppLocalizationDemo”, you can create either Shared or Portable project as per your requirement. 
 
Create a new Application
Creating a Xamarin application creates multiple projects as shown below
1. AppLocalizationDemo (Common Project)
2. AppLocalizationDemo.Droid (Android Project)
3. AppLocalizationDemo.iOS (iOS Project)
 
Step 2 -  Now we will create resources file which will contain all text and messages that will be displayed across the app. First we will create a resource file for default language, in this demo we will use English as default language. Adding a resource file is different in Shared and PCL types.

Only for Shared Application - In PCL project application automatically create library project which can be built individually but not in the shared app, therefore, a If you have created a "Shared Application" then you need to add a new project of type Class Library which will be used for localisation. Right click on solution and click on Add-> New Project and choose class library project and  Add reference of this library into Android and iOS projects.
Add a new library project "Localisation" (Only for Shared Application)
Note - In the XF Shared application all next steps will be performed on this library project not the actual shared project created at the time of application creation.

So now we have following projects in application
1. AppLocalizationDemo 
2. AppLocalizationDemo.Droid
3. AppLocalizationDemo.iOS 
// Only for shared application
4. Localisation  (Common Project for XF Shared Application)

Add Resource Files - 
Step 3 - Right click on a common project on and click on Add -> New Item and add a resource file "LocalResource.resx".
 
Add a resource file.
 
Step 4 - Right click on resx file and change the Custom Tool option to "ResXFileCodeGenerator" to generate resource values as c# property. Now you can add text and messages which need to be displayed in multiple languages in key value format.
 
Change custom tool setting
 
LocalResource.resx
 
Step 5 - By repeating the step 3 and 4 you will create another resource file with same name followed by dot (.) and language code. For example if you want to support chinese language then you can add a resource file "LocalResource.zh-Hant.resx" where "zh-Hant" is language code for Chinese. In the same way you can add resource file for other languages too.
  • AppResources.fr.resx - French language translations.
  • AppResources.es.resx - Spanish language translations.
  • AppResources.de.resx - German language translations.
  • AppResources.ja.resx - Japanese language translations.
  • AppResources.zh-Hans.resx - Chinese (Simplified) language translations.
  • AppResources.zh-Hant.resx - Chinese (Traditional) language translations.
  • AppResources.pt.resx - Portuguese language translations.
  • AppResources.pt-BR.resx - Brazilian Portuguese language translations.
 
Now the App will use the resource file based on current culture. 
 
Change Current Culture - 
 
Step 6 - Create an ILocalize interface and Platform culture class in common project.
 
public interface ILocalize
{
    CultureInfo GetCurrentCultureInfo ();
    void SetLocale (CultureInfo ci);
}
public class PlatformCulture
{
    public PlatformCulture (string platformCultureString)
    {
        if (String.IsNullOrEmpty(platformCultureString))
    {
            throw new ArgumentException("Expected culture identifier", "platformCultureString"); // in C# 6 use nameof(platformCultureString)
        }
    PlatformString = platformCultureString.Replace("_", "-"); // .NET expects dash, not underscore
        var dashIndex = PlatformString.IndexOf("-", StringComparison.Ordinal);
        if (dashIndex > 0)
        {
            var parts = PlatformString.Split('-');
            LanguageCode = parts[0];
            LocaleCode = parts[1];
        }
        else
        {
            LanguageCode = PlatformString;
            LocaleCode = "";
        }
    }
    public string PlatformString { get; private set; }
    public string LanguageCode { get; private set; }
    public string LocaleCode { get; private set; }
    public override string ToString()
    {
        return PlatformString;
    }
}
 
Step 7 - Implement ILocalize interface in Android and iOS project.

Android - 
[assembly:Dependency(typeof(AppLocalizationDemo.Android.Localize))]
namespace AppLocalizationDemo.Android
{
    public class Localize : ILocalize    {
        public void SetLocale(CultureInfo ci)
        {
            Thread.CurrentThread.CurrentCulture = ci;
            Thread.CurrentThread.CurrentUICulture = ci;
        }
        public CultureInfo GetCurrentCultureInfo()
        {
            var netLanguage = "en";
            var androidLocale = Java.Util.Locale.Default;
            netLanguage = AndroidToDotnetLanguage(androidLocale.ToString().Replace("_", "-"));
            // this gets called a lot - try/catch can be expensive so consider caching or something
            System.Globalization.CultureInfo ci = null;
            try
            {
                ci = new System.Globalization.CultureInfo(netLanguage);
            }
            catch (CultureNotFoundException e1)
            {
                // iOS locale not valid .NET culture (eg. "en-ES" : English in Spain)
                // fallback to first characters, in this case "en"
                try
                {
                    var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage));
                    ci = new System.Globalization.CultureInfo(fallback);
                }
                catch (CultureNotFoundException e2)
                {
                    // iOS language not valid .NET culture, falling back to English
                    ci = new System.Globalization.CultureInfo("en");
                }
            }
            return ci;
        }
        string AndroidToDotnetLanguage(string androidLanguage)
        {
            var netLanguage = androidLanguage;
            //certain languages need to be converted to CultureInfo equivalent
            switch (androidLanguage)
            {
                case "ms-BN":   // "Malaysian (Brunei)" not supported .NET culture
                case "ms-MY":   // "Malaysian (Malaysia)" not supported .NET culture
                case "ms-SG":   // "Malaysian (Singapore)" not supported .NET culture
                    netLanguage = "ms"; // closest supported
                    break;
                case "in-ID":  // "Indonesian (Indonesia)" has different code in  .NET
                    netLanguage = "id-ID"; // correct code for .NET
                    break;
                case "gsw-CH":  // "Schwiizertüütsch (Swiss German)" not supported .NET culture
                    netLanguage = "de-CH"; // closest supported
                    break;
                    // add more application-specific cases here (if required)
                    // ONLY use cultures that have been tested and known to work
            }
            return netLanguage;
        }
        string ToDotnetFallbackLanguage(PlatformCulture platCulture)
        {
            var netLanguage = platCulture.LanguageCode; // use the first part of the identifier (two chars, usually);
            switch (platCulture.LanguageCode)
            {
                case "gsw":
                    netLanguage = "de-CH"; // equivalent to German (Switzerland) for this app
                    break;
                    // add more application-specific cases here (if required)
                    // ONLY use cultures that have been tested and known to work
            }
            return netLanguage;
        }
    }
}

iOS -
[assembly:Dependency(typeof(AppLocalizationDemo.iOS.Localize))]

namespace AppLocalizationDemo.iOS
{
public class Localize : ILocalize
    {
        public void SetLocale (CultureInfo ci)
        {
            Thread.CurrentThread.CurrentCulture = ci;
            Thread.CurrentThread.CurrentUICulture = ci;
        }
        public CultureInfo GetCurrentCultureInfo ()
        {
            var netLanguage = "en";
            if (NSLocale.PreferredLanguages.Length > 0)
            {
                var pref = NSLocale.PreferredLanguages [0];
                netLanguage = iOSToDotnetLanguage(pref);
            }
            // this gets called a lot - try/catch can be expensive so consider caching or something
            System.Globalization.CultureInfo ci = null;
            try
            {
                ci = new System.Globalization.CultureInfo(netLanguage);
            }
            catch (CultureNotFoundException e1)
            {
                // iOS locale not valid .NET culture (eg. "en-ES" : English in Spain)
                // fallback to first characters, in this case "en"
                try
                {
                    var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage));
                    ci = new System.Globalization.CultureInfo(fallback);
                }
                catch (CultureNotFoundException e2)
                {
                    // iOS language not valid .NET culture, falling back to English
                    ci = new System.Globalization.CultureInfo("en");
                }
            }
            return ci;
        }
        string iOSToDotnetLanguage(string iOSLanguage)
        {
            var netLanguage = iOSLanguage;
            //certain languages need to be converted to CultureInfo equivalent
            switch (iOSLanguage)
            {
                case "ms-MY":   // "Malaysian (Malaysia)" not supported .NET culture
                case "ms-SG":   // "Malaysian (Singapore)" not supported .NET culture
                    netLanguage = "ms"; // closest supported
                    break;
                case "gsw-CH":  // "Schwiizertüütsch (Swiss German)" not supported .NET culture
                    netLanguage = "de-CH"; // closest supported
                    break;
                // add more application-specific cases here (if required)
                // ONLY use cultures that have been tested and known to work
            }
            return netLanguage;
        }
        string ToDotnetFallbackLanguage (PlatformCulture platCulture)
        {
            var netLanguage = platCulture.LanguageCode; // use the first part of the identifier (two chars, usually);
            switch (platCulture.LanguageCode)
            {
                case "pt":
                    netLanguage = "pt-PT"; // fallback to Portuguese (Portugal)
                    break;
                case "gsw":
                    netLanguage = "de-CH"; // equivalent to German (Switzerland) for this app
                    break;
                // add more application-specific cases here (if required)
                // ONLY use cultures that have been tested and known to work
            }
            return netLanguage;
        }
    }
}
 
Step 8 - If you want to use these resource value in Xaml page then you need to create a TranslateExtension class in common project.
namespace AppLocalizationDemo
{
    [ContentProperty ("Text")]
    public class TranslateExtension : IMarkupExtension
    {
        readonly CultureInfo ci;
        const string ResourceId = "UsingResxLocalization.Resx.AppResources";

        public TranslateExtension()
        {
          if (Device.OS == TargetPlatform.iOS || Device.OS == TargetPlatform.Android)
          {
              ci = DependencyService.Get<ILocalize>().GetCurrentCultureInfo();
          }
        }

        public string Text { get; set; }

        public object ProvideValue (IServiceProvider serviceProvider)
        {
            if (Text == null)
                return "";

            ResourceManager resmgr = new ResourceManager(ResourceId
                                , typeof(TranslateExtension).GetTypeInfo().Assembly);

            var translation = resmgr.GetString (Text, ci);

            if (translation == null)
            {
                #if DEBUG
                throw new ArgumentException (
                    String.Format ("Key '{0}' was not found in resources '{1}' for culture '{2}'.", Text, ResourceId, ci.Name),
                    "Text");
                #else
                translation = Text; // HACK: returns the key, which GETS DISPLAYED TO THE USER
                #endif
            }
            return translation;
        }
    }
}

Now we are ready to consume these resource content in your application.
 
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:AppLocalizationDemo"
             xmlns:res="clr-namespace:AppLocalizationDemo;assembly=AppLocalizationDemo"
             x:Class="AppLocalizationDemo.MainPage">

    <StackLayout>
        <Label x:Name="lblTitle" Text="{res:Translate Title}" 
           VerticalOptions="Center" 
           HorizontalOptions="Center" />

        <Button Text="{res:Translate ButtonText}"></Button>
        
    </StackLayout>

</ContentPage>

If you want to use it at C# end then you can write following code-
 
lblTitle.Text = AppLocalizationDemo.LocalResource.Title;
 
Now we are ready to use AppLocalization in our project. If you want to turn your app in the Chinese language then just execute the below code -
 
DependencyService.Get<ILocalize>().SetLocale (new CultureInfo("zh-Hant"));
You can replace "zh-Hant" by any language code to translate it in that language.

That's it.
You can download a working sample from hereDownload Sample

Please feel free to write us at support@xamarians.com