VISUAL STUDIO   Windows Mac

Localizing Xamarin.Forms Apps with RESX Resource Files

PDF for offline use
Sample Code:
Related Links:

Let us know how you feel about this

Translation Quality


0/250

last updated: 2016-09

Xamarin.Forms apps can be localized using .NET resources files.

Overview

The built-in mechanism for localizing .NET applications uses RESX files and the classes in the System.Resources and System.Globalization namespaces. The RESX files containing translated strings are embedded in the Xamarin.Forms assembly, along with a compiler-generated class that provides strongly-typed access to the translations. The translation text can then be retrieved in code.

This document contains the following sections:

Globalizing Xamarin.Forms Code

  • Adding and using string resources in a Xamarin.Forms PCL app.
  • Enabling language detection in each of the native apps.

Localizing XAML

  • Localizing XAML using an IMarkupExtension.
  • Enabling the markup extension in the native apps.

Localizing Platform-Specific Elements

  • Localizing images and the app name in the native apps.

Sample Code

There are two samples associated with this document:

  • UsingResxLocalization is a very simple demonstration of the concepts explained. The code snippets shown below are all from this sample.
  • TodoLocalized is a basic working app that uses these localization techniques.

Shared Projects are not recommended

The TodoLocalized sample includes a Shared Project demo however due to limitations of the build system the resource files do not get a .designer.cs file generated which breaks the ability to access translated strings strongly-typed in code.

The remainder of this document relates to projects using the Xamarin.Forms PCL template.

Globalizing Xamarin.Forms Code

Globalizing an application is the process of making it "world ready." This means writing code that is capable of displaying different languages.

One of the key parts of globalizing an application is building the user-interface so that there is no hard-coded text. Instead, anything displayed to the user should be retrieved from a set of strings that have been translated into their chosen language.

In this document we'll examine how to use RESX files to store those strings and retrieve them for display depending on the user's preference.

The samples target English, French, Spanish, German, Chinese, Japanese, Russian, and Brazilian Portuguese languages. Applications can be translated into as few or as many languages as required.

Adding Resources

The first step in globalizing a Xamarin.Forms PCL application is adding the RESX resource files that will be used to store all the text used in the app. We need to add a RESX file that contains the default text, and then add additional RESX files for each language we wish to support.

Base Language Resource

The base resources (RESX) file will contain the default language strings (the samples assume English is the default language). Add the file to the Xamarin.Forms common code project by right-clicking on the project and choosing Add > New File....

Choose a meaningful name such as AppResources and press OK.

Add Resource File

Two files will be added to the project:

  • AppResources.resx file where translatable strings are stored in an XML format.
  • AppResources.designer.cs file that declares a partial class to contain references to all the elements created in the RESX XML file.

The solution tree will show the files as related. The RESX file should be edited to add new translatable strings; the .designer.cs file should not be edited.

String Visibility

By default when strongly-typed references to strings are generated, they will be internal to the assembly. This is because the default build tool for RESX files generates the .designer.cs file with internal properties.

Select the AppResources.resx file and show the Properties pad to see where this build tool is configure. The screenshot below shows the Custom Tool: ResXFileCodeGenerator.

To make the strongly-typed string properties public, you must manually change the configuration to Custom Tool: PublicResXFileCodeGenerator, as shown in the screenshot below:

This change is optional, and is only required if you wish to reference localized strings across different assemblies (for example, if you put the RESX files in a different assembly to your code). The sample for this topic leaves the strings internal because they are defined in the same Xamarin.Forms PCL assembly where they are used.

You only need to set the custom tool on the base RESX file as shown above; you do not need to set any build tool on the language-specific RESX files discussed in the following sections.

Editing the RESX file

Unfortunately there is no built-in RESX editor in Visual Studio for Mac. Adding new translatable strings requires the addition of a new XML data element for each string. Each data element can contain the following:

  • name attribute (required) is the key for this translatable string. It must be a valid C# property name - so no spaces or special characters are allowed.
  • value element (required), which is the actual string that is displayed in the application.
  • comment element (optional) can contain instructions for the translator that explains how this string is used.
  • xml:space attribute (optional) to control how spacing in the string is preserved.

Some example data elements are shown here:

<data name="NotesLabel" xml:space="preserve">
    <value>Notes:</value>
    <comment>label for input field</comment>
</data>
<data name="NotesPlaceholder" xml:space="preserve">
    <value>eg. buy milk</value>
    <comment>example input for notes field</comment>
</data>
<data name="AddButton" xml:space="preserve">
    <value>Add new item</value>
</data>

As the application is written, every piece of text displayed to the user should be added to the base RESX resources file in a new data element. It is recommended that you include comments as much as possible to ensure a high-quality translation.

Visual Studio (including the free Community edition) contains a basic RESX editor. If you have access to a Windows computer, that can be a convenient way to add and edit strings in RESX files.

Language-Specific Resources

Typically, the actual translation of the default text strings won't happen until large chunks of the application have been written (in which case the default RESX file will contain lots of strings). It is still a good idea to add the language-specific resources early in the development cycle, optionally populating with machine-translated text to help test the localization code.

One additional RESX file is added for each language we wish to support. Language-specific resource files must follow a specific naming convention: use the same filename as the base resources file (eg. AppResources) followed by a period (.) and then the language code. Simple examples include:

  • 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.

The general pattern is to use two-letter language codes, but there are some examples (such as Chinese) where a different format is used, and other examples (such as Brazilian Portuguese) where a four character locale identifier is required.

These language-specific resources files do not require a .designer.cs partial class so they can be added as regular XML files, with the Build Action: EmbeddedResource set. This screenshot shows a solution containing language-specific resource files:

As an application is developed and the base RESX file has text added, you should send it out to translators who will translate each data element and return a language-specific resource file (using the naming convention shown) to be included in the app. Some 'machine translated' examples are shown below:

AppResources.es.resx (Spanish)

<data name="AddButton" xml:space="preserve">
    <value>Escribir un artículo</value>
    <comment>this string appears on a button to add a new item to the list</comment>
</data>

AppResources.ja.resx (Japanese)

<data name="AddButton" xml:space="preserve">
    <value>新しい項目を追加</value>
    <comment>this string appears on a button to add a new item to the list</comment>
</data>

AppResources.pt-BR.resx (Brazilian Portuguese)

<data name="AddButton" xml:space="preserve">
    <value>adicionar novo item</value>
    <comment>this string appears on a button to add a new item to the list</comment>
</data>

Only the value element needs to be updated by the translator - the comment is not intended to be translated. Remember: when editing XML files to escape reserved characters like <, >, & with &lt;, &gt;, and &amp; if they appear in the value or comment.

Using Resources in Code

Strings in the RESX resource files will be available to use in your user interface code using the AppResources class. The name assigned to each string in the RESX file becomes a property on that class which can be referenced in Xamarin.Forms code as shown below:

var myLabel = new Label ();
var myEntry = new Entry ();
var myButton = new Button ();
// populate UI with translated text values from resources
myLabel.Text = AppResources.NotesLabel;
myEntry.Placeholder = AppResources.NotesPlaceholder;
myButton.Text = AppResources.AddButton;

The user interface on iOS, Android, and the Windows platforms renders as you'd expect, except now it's possible to translate the app into multiple languages because the text is being loaded from a resource rather than being hard-coded. Here is a screenshot showing the UI on each platform prior to translation:

Troubleshooting

Testing a Specific Language

It can be tricky to switch the simulator or a device to different languages, particulary during development when you want to test different cultures quickly.

You can force a specific language to be loaded by setting the Culture as shown in this code snippet:

// force a specific culture, useful for quick testing 
AppResources.Culture =  new CultureInfo("fr-FR");

This approach – setting the culture directly on the AppResources class – can also be used to implement a language-selector inside your app (rather than using the device's locale).

Loading Embedded Resources

The following code snippet is useful when trying to debug issues with embedded resources (such as RESX files). Add this code to your app (early in the app lifecycle) and it will list all the resources embedded in the assembly, showing the full resource identifier:

using System.Reflection;
// ...
// NOTE: use for debugging, not in released app code!
var assembly = typeof(EmbeddedImages).GetTypeInfo().Assembly; // "EmbeddedImages" should be a class in your app
foreach (var res in assembly.GetManifestResourceNames())
{
    System.Diagnostics.Debug.WriteLine("found resource: " + res);
}

In the AppResources.Designer.cs file (around line 33), the full resource manager name is specified (eg. "UsingResxLocalization.Resx.AppResources") similar to the code below:

System.Resources.ResourceManager temp =
        new System.Resources.ResourceManager(
                "UsingResxLocalization.Resx.AppResources",
                typeof(AppResources).GetTypeInfo().Assembly);

Check the Application Output for the results of the debug code shown above, to confirm the correct resources are listed (ie. "UsingResxLocalization.Resx.AppResources").

If not, the AppResources class will be unable to load its resources. Check the following to resolve issues where the resources cannot be found:

  • The default namespace for the project matches the root namespace in the AppResources.Designer.cs file.
  • If the AppResources.resx file is located in a subdirectory, the subdirectory name should be part of the namespace and part of the resource identifier.
  • The AppResources.resx file has Build Action: EmbeddedResource.
  • The Project Options > Source Code > .NET Naming Policies > Use Visual Studio-style resources names is ticked. You can untick this if you prefer, however the namespaces used when referencing your RESX resources will need to updated throughout the app.

Doesn't work in DEBUG mode (Android only)

If the translated strings are working in your RELEASE Android builds but not while debugging, right-click on the Android Project and select Options > Build > Android Build and ensure that the Fast assembly deployment is NOT ticked. This option causes problems with loading resources and should not be used if you are testing localized apps.

Displaying the Correct Language

So far we've examined how to write code so that translations can be provided, but not how to actually make them appear. Xamarin.Forms code can take advantage of .NET's resources to load the correct language translations, but we need to query the operating system on each platform to determine which language the user has selected.

Because some platform-specific code is required to obtain the user's language preference, use a dependency service to expose that information in the Xamarin.Forms app and implement it for each platform.

First, define an interface to expose the user's preferred culture, similar to the code below:

public interface ILocalize
{
    CultureInfo GetCurrentCultureInfo ();
  void SetLocale (CultureInfo ci);
}

Second, use the DependencyService in the Xamarin.Forms App class to call the interface and set our RESX resources culture to the correct value. Notice that we don't need to manually set this value for Windows Phone and the Universal Windows Platform, since the resources framework automatically recognizes the selected language on those platforms.

if (Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.Android)
{
    var ci = DependencyService.Get<ILocalize>().GetCurrentCultureInfo();
    Resx.AppResources.Culture = ci; // set the RESX for resource localization
    DependencyService.Get<ILocalize>().SetLocale(ci); // set the Thread for locale-aware methods
}

The resource Culture needs to be set when the application first loads so that the correct language strings are used. You may optionally update this value according to platform-specific events that may be raised on iOS or Android if the user updates their language preferences while the app is running.

The implementations for the ILocalize interface are shown in the Platform-specific Code section below. These implementations take advantage of this PlatformCulture helper class:

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;
    }
}

Platform-Specific Code

The code to detect which language to display must be platform-specific because iOS, Android, and the Windows platforms all expose this information in slightly different ways. The code for the ILocalize dependency service is provided below for each platform, along with additional platform-specific requirements to ensure localized text is rendered correctly.

The platform-specific code must also handle cases where the operating system allows the user to configure a locale identifier that is not supported by .NET's CultureInfo class. In these cases custom code must be written to detect unsupported locales and substitute the best .NET-compatible locale.

iOS Application Project

iOS users select their preferred language separately from date and time formatting culture. To load the correct resources to localize a Xamarin.Forms app we just need to query the NSLocale.PreferredLanguages array for the first element.

The following implementation of the ILocalize dependency service should be placed in the iOS application project. Because iOS uses underscores instead of dashes (which is the .NET standard representation) the code replaces the underscore before instantiating the CultureInfo class:

[assembly:Dependency(typeof(UsingResxLocalization.iOS.Localize))]

namespace UsingResxLocalization.iOS
{
public class Localize : UsingResxLocalization.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;
        }
    }
}
The try/catch blocks in the GetCurrentCultureInfo method mimic the fallback behavior typically used with locale specifiers – if the exact match is not found, look for a close match based just on the langauge (first block of characters in the locale).
In the case of Xamarin.Forms, some locales are valid in iOS but do not correspond to a valid CultureInfo in .NET, and the code above attempts to handle this.
For example, the iOS Settings > General Language & Region screen allows you to set your phone Language to English but the Region to Spain, which results in a locale string of "en-ES". When the CultureInfo creation fails, the code falls back to using just the first two letters to select the display language.
Developers should modify the iOSToDotnetLanguage and ToDotnetFallbackLanguage methods to handle specific cases required for their supported languages.

There are some system-defined user-interface elements that are automatically translated by iOS, such as the Done button on the Picker control. To force iOS to translate these elements we need to indicate which languages we support in the Info.plist file. You can add these values via Info.plist > Source as shown here:

Localization keys in Info.plist

Alternatively, open the Info.plist file in an XML editor and edit the values directly:

<key>CFBundleLocalizations</key>
<array>
    <string>de</string>
    <string>es</string>
    <string>fr</string>
    <string>ja</string>
    <string>pt</string> <!-- Brazil -->
    <string>pt-PT</string> <!-- Portugal -->
    <string>ru</string>
    <string>zh-Hans</string>
    <string>zh-Hant</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>

Once you've implemented the dependency service and updated Info.plist, the iOS app will be able to display localized text.

Note that Apple treats Portuguese slightly differently than you'd expect. From their docs: "use pt as the language ID for Portuguese as it is used in Brazil and pt-PT as the language ID for Portuguese as it is used in Portugal". This means when Portuguese language is chosen in a non-standard locale, the fallback language will be Brazilian Portuguese on iOS, unless code is written to change this behavior (such as the ToDotnetFallbackLanguage above).

Android Application Project

Android exposes the currently selected locale via Java.Util.Locale.Default, and also uses an underscore separator instead of a dash (which is replaced by the following code). Add this dependency service implementation to the Android application project:

[assembly:Dependency(typeof(UsingResxLocalization.Android.Localize))]

namespace UsingResxLocalization.Android
{
    public class Localize : UsingResxLocalization.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;
        }
    }
}
The try/catch blocks in the GetCurrentCultureInfo method mimic the fallback behavior typically used with locale specifiers – if the exact match is not found, look for a close match based just on the langauge (first block of characters in the locale).
In the case of Xamarin.Forms, some locales are valid in Android but do not correspond to a valid CultureInfo in .NET, and the code above attempts to handle this.
Developers should modify the iOSToDotnetLanguage and ToDotnetFallbackLanguage methods to handle specific cases required for their supported languages.

Once this code has been added to the Android application project, it will be able to automatically display translated strings.

WARNING: If the translated strings are working in your RELEASE Android builds but not while debugging, right-click on the Android Project and select Options > Build > Android Build and ensure that the Fast assembly deployment is NOT ticked. This option causes problems with loading resources and should not be used if you are testing localized apps.

Windows Application Projects

Windows 8.1 and Universal Windows Platform (UWP) projects do not require the dependency service – these platforms automatically set the resource's culture correctly.

Implementing the XAML markup extension described later in this document may require the ILocalize implementation shown below for Windows Phone.

Windows Phone 8.0

Although not used in the App class, here is the Windows Phone implementation for the ILocalize dependency service. Add this class to the Windows Phone app project; it will be required if implementing the XAML markup extension described later:

[assembly: Dependency(typeof(UsingResxLocalization.WinPhone.Localize))]

namespace UsingResxLocalization.WinPhone
{
    public class Localize : UsingResxLocalization.ILocalize
    {
        public void SetLocale (CultureInfo ci) { }
        public System.Globalization.CultureInfo GetCurrentCultureInfo ()
        {
            return System.Threading.Thread.CurrentThread.CurrentUICulture;
        }
    }
}

Windows Phone 8.0 projects must be properly configured for localized text to be displayed. Supported languages must be selected in the Project Options and the WMAppManifest.xml files. If these settings are not updated the localized RESX resources will not be loaded.

Project Options

Right-click on the Windows Phone project and select Properties. In the Application tab tick the Supported Cultures that the application supports:

WMAppManifest.xml

Expand the Properties node in the Windows Phone project and double-click on the WMAppManifest.xml file. Click on the Packaging tab and tick all the languages supported by the application.

AssemblyInfo.cs

Expand the Properties node in the Portable Class Library (PCL) project and double-click on the AssemblyInfo.cs file. Add the following line to the file to set the neutral resources assembly language to English:

[assembly: NeutralResourcesLanguage("en")]

This informs the resource manager of the app's default culture, therefore ensuring that the strings defined in the language neutral RESX file (AppResources.resx) will be displayed when the app is running in one the English locales.

Example

After updating the platform-specific projects as shown above and recompiling the app with translated RESX files, updated translations will be available in each app. Here is a screenshot from the sample code translated into Simplified Chinese:

Localizing XAML

When building a Xamarin.Forms user interface in XAML the markup would look similar to this, with strings embedded directly in the XML:

<Label Text="Notes:" />
<Entry Placeholder="eg. buy milk" />
<Button Text="Add to list" />

Ideally, we could translate user interface controls directly in the XAML, which we can do by creating a markup extension. The code for a markup extension that exposes the RESX resources to XAML is shown below. This class should be added to the Xamarin.Forms common code (along with the XAML pages):

using System;
using System.Globalization;
using System.Reflection;
using System.Resources;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace UsingResxLocalization
{
    // You exclude the 'Extension' suffix when using in Xaml markup
    [ContentProperty ("Text")]
    public class TranslateExtension : IMarkupExtension
    {
        readonly CultureInfo ci;
        const string ResourceId = "UsingResxLocalization.Resx.AppResources";

        private static readonly Lazy<ResourceManager> ResMgr = new Lazy<ResourceManager>(()=> new ResourceManager(ResourceId
                                                                                                                  , typeof(TranslateExtension).GetTypeInfo().Assembly));

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

        public string Text { get; set; }

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

            var translation = ResMgr.Value.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; // returns the key, which GETS DISPLAYED TO THE USER
                #endif
            }
            return translation;
        }
    }
}

The following bullets explain the important elements in the code above:

  • The class is named TranslateExtension, but by convention we can refer to is as Translate in our markup.
  • The class implements IMarkupExtension, which is required by Xamarin.Forms for it to work.
  • "UsingResxLocalization.Resx.AppResources" is the resource identifier for our RESX resources. It is comprised of our default namespace, the folder where the resource files are located and the default RESX filename.
  • The ResourceManager class is created using typeof(TranslateExtension) to determine the current assembly to load resources from.
  • ci uses the dependency service to get the user's chosen language from the native operating system.
  • GetString is the method that retrieves the actual translated string from the resources files. On Windows Phone 8.1 and the Universal Windows Platform, ci will be null because the ILocalize interface isn't implemented on those platforms. This is equivalent to calling the GetString method with only the first parameter. Instead, the resources framework will automatically recognize the locale and will retrieve the translated string from the appropriate RESX file.
  • Error handling has been included to help debug missing resources by throwing an exception (in DEBUG mode only).

The following XAML snippet shows how to use the markup extension. There are two steps to make it work:

  1. Declare the custom xmlns:i18n namespace in the root node. The namespace and assembly must match the project settings exactly - in this example they are identical but could be different in your project.
  2. Use {Binding} syntax on attributes that would normally contain text to call the Translate markup extension. The resource key is the only required parameter.
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        x:Class="UsingResxLocalization.FirstPageXaml"
        xmlns:i18n="clr-namespace:UsingResxLocalization;assembly=UsingResxLocalization">
    <StackLayout Padding="0, 20, 0, 0">
        <Label Text="{i18n:Translate NotesLabel}" />
        <Entry Placeholder="{i18n:Translate NotesPlaceholder}" />
        <Button Text="{i18n:Translate AddButton}" />
    </StackLayout>
</ContentPage>

The following more verbose syntax is also valid for the markup extension:

<Button Text="{i18n:TranslateExtension Text=AddButton}" />

Localizing Platform-Specific Elements

Although we can handle the translation of the user interface in Xamarin.Forms code, there are some elements that must be done in each platform-specific project. This section will cover how to localize:

  • Application Name
  • Images

The sample project includes a localized image called flag.png, which is referenced in C# as follows:

var flag = new Image();
switch (Device.RuntimePlatform)
{
    case Device.iOS:
    case Device.Android:
        flag.Source = ImageSource.FromFile("flag.png");
        break;
    case Device.UWP:
        flag.Source = ImageSource.FromFile("Assets/Images/flag.png");
        break;
}

The flag image is also referenced in the XAML like this:

<Image>
  <Image.Source>
    <OnPlatform x:TypeArguments="ImageSource">
        <On Platform="iOS, Android" Value="flag.png" />
        <On Platform="UWP" Value="Assets/Images/flag.png" />
    </OnPlatform>
  </Image.Source>
</Image>

All platforms will automatically resolve image references like these to localized versions of the images, as long as the project structures explained below are implemented.

iOS Application Project

iOS uses a naming standard called Localization Projects or .lproj directories to contain image and string resources. These directories can contain localized versions of images used in the app, and also the InfoPlist.strings file that can be used to localize the app name.

Images

This screenshot shows the iOS sample app with language-specific .lproj directories. The Spanish directory called es.lproj, contains localized versions of the default image, as well as flag.png:

Each language directory contains a copy of flag.png, localized for that language. If no image is supplied, the operating system will default to the image in the default language directory. For full Retina support, you should supply @2x and @3x copies of each image.

App Name

The contents of the InfoPlist.strings is just a single key-value to configure the app name:

"CFBundleDisplayName" = "ResxEspañol";

When the application is run, the app name and the image are both localized:

Android Application Project

Android follows a different scheme for storing localized images using different drawable and strings directories with a language code suffix. When a four-letter locale code is required (such as zh-TW or pt-BR), note that Android requires an additional r following the dash/preceding the locale code (eg. zh-rTW or pt-rBR).

Images

This screenshot shows the Android sample with a some localized drawables and strings:

Note that Android does not use zh-Hans and zh-Hant codes for Simplified and Traditional Chinese; instead, it only supports country-specific codes zh-CN and zh-TW.

To support different resolution images for high-density screens, create additional language folders with -*dpi suffixes, such as drawables-es-mdpi, drawables-es-xdpi, drawables-es-xxdpi, etc. See Providing Alternative Android Resources for more information.

App Name

The contents of the strings.xml is just a single key-value to configure the app name:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">ResxEspañol</string>
</resources>

Update the MainActivity.cs in the Android app project so that the Label references the strings XML.

[Activity (Label = "@string/app_name", MainLauncher = true,
        ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]

The app now localizes the app name and image. Here's a screenshot of the result (in Spanish):

Windows Phone 8.0 Application Project

Windows Phone doesn't have a simple built-in way of selecting a specific localized image nor for localizing the app name.

Images

To get around this limitation the sample provides a suggestion for how you might implement localized image loading using a custom renderer for the Image control.

The custom renderer code is shown below - if the source is a FileImageSource then it extracts the filename and builds a path to a localized image using the CurrentUICulture. Some languages require special handling so that fallbacks work as expected; in the example the default is to use only the two-letter language code except in a few special cases:

using System.IO;
using Xamarin.Forms;
using Xamarin.Forms.Platform.WinPhone;

[assembly: ExportRenderer(typeof(Image), typeof(UsingResxLocalization.WinPhone.LocalizedImageRenderer))]
namespace UsingResxLocalization.WinPhone
{
    public class LocalizedImageRenderer : ImageRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement != null)
            {
                var s = e.NewElement.Source as FileImageSource;
                if (s != null)
                {
                    var fileName = s.File;
                    string ci = System.Threading.Thread.CurrentThread.CurrentUICulture.ToString();
                    // you might need some custom logic here to support particular cultures and fallbacks
                    if (ci == "pt-BR") {
                        // use the complete string 'as is'
                    } else if (ci == "zh-CN") {
                         // we could have named the image directories differently,
                         // but this keeps them consisent with RESX file naming
                        ci = "zh-Hans";
                    } else if (ci == "zh-TW" || ci == "zh-HK") {
                        ci = "zh-Hant";
                    } else {
                        // for all others, just use the two-character language code
                        ci = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
                    }
                    e.NewElement.Source = Path.Combine("Assets/" + ci + "/" + fileName);
                }
            }
        }
    }
}

This code works with the localized images in the directory structure shown below. You're encouraged to modify the code to meet your specific localization requirements (such as handling more specific locales, and falling back when images are not available):

Windows Phone now localizes the image. Here's a screenshot of the result (in Spanish and Simplified Chinese):

App Name

Refer to Microsoft's documentation for localizing the Windows Phone 8.0 app title.

Windows Phone 8.1 and Universal Windows Platform Application Projects

Windows Phone 8.1 and the Universal Windows Platform both possess a resource infrastructure that simplifies the localization of images and the app name.

Images

Images can be localized by placing them in a resource-specific folder, as demonstrated in the following screenshot:

At runtime the Windows resource infrastructure will select the appropriate image based on the user's locale.

App Name

Refer to Microsoft's documentation for Windows 8.1 Store apps: Localize the information that describes your app to users and Loading strings from the app manifest.

Summary

Xamarin.Forms applications can be localized using RESX files and .NET globalization classes. Apart from a small amount of platform-specific code to detect what language the user prefers, most of the localization effort is centralized in the common code.

Images are generally handled in a platform-specific way to take advantage of the multi-resolution support provided in both iOS and Android. Windows Phone requires some custom code to localize images in a cross-platform-friendly way; sample code was provided to add this capability.

Xamarin Workbook

If it's not already installed, install the Xamarin Workbooks app first. The workbook file should download automatically, but if it doesn't, just click to start the workbook download manually.