Resource Files in Xamarin Forms

Resource files allow you to define a name and a resource, normally a string, that can change depending upon the language of the device. Each native platform has a resource file structure and setup that can be used. Xamarin.Android and Xamarin.iOS will use resx and UWP uses resw. Interestingly with both of these file extensions, they are interchangable.

Create a Resource File

In your PCL, create a resource file. If you are using .NET Core, you will need the .NET Standard Portable Compatibility package. However because this causes issues in Xamarin.Android, you can remove it. You won’t be able to open the GUI for this resource file anymore but you can still edit it in XML.

resourcefile

Inside this file you can define resources as below, or directly in xml.

editresourcefile

This is the default file and in my case is English. If you want to create a resource file for another language you can create another resource file, include all the same names, but change the values to suit that language. The naming convention for each additional file is based on the two letter language codes. e.g.

  • Resources.fr.resx (for French)
  • Resources.es.resx (for Spanish)

Note: There are exceptions with Chinese (zh-Hans [simplifed] zh-Hant [traditional]) or Brazilian Portuguese (pt-BR).

Copy Files For UWP

UWP likes to be different and uses resw files and uses folder structure instead of naming convention. In my UWP project I create a folder called Strings > en.

uwpresourcefolder

Then put this as your pre-build event, in Properties > Build Event of the UWP project. In the pre-build process, this copies the resx from the PCL and moves it to the UWP project.

xcopy "$(ProjectDir)..\MyMobileProjectFolder\Resource\Resources.resx" "$(ProjectDir)\Strings\en\Resources.resw" /Y

Using The Resource File

ResourceContainer

First we need to create a ResourceContainer that can read from the ResourceManager to correctly retrieve the string depending upon the users CultureInfo.

public interface IResourceContainer
{
    string GetString(string key);
}

public class ResourceContainer : IResourceContainer
{
    public static string ResourceId = "Mobile.Resources"; // The namespace and name of your Resources file
    private CultureInfo _cultureInfo;
    private ResourceManager _resourceManager;

    public ResourceContainer(ResourceManager manager, ILocalize localize) {
        _cultureInfo = localize.GetCurrentCultureInfo();
        _resourceManager = manager;
    }
 
    public string GetString(string key)
    {
        return _resourceManager.GetString(key, _cultureInfo);
    }
}

Next we create an instance of the ResourceContainer that we can add it into our dependency injection framework, or keep a static reference. But before we can do that, we need to create a ResourceManager and Localize first.

ResourceManager

In your App.xaml.cs, or anywhere in your startup sequence you can create and inject a ResourceManager like this.

new ResourceManager(ResourceContainer.ResourceId, typeof(App).GetTypeInfo().Assembly);

On UWP, you need to create a ResourceManager in your native project and pass it through.

new Resource.ResourceManager(typeof(AppCentre.App));

Localize

Localize is a class that obtains the CultureInfo of the user on the device. We have to obtain the CultureInfo on a per platform basis.

UWP

public class Localize: ILocalize
{
    public CultureInfo GetCurrentCultureInfo()
    {
        return CultureInfo.CurrentUICulture;
    }
}

Android

public class Localize : ILocalize
{
    public System.Globalization.CultureInfo GetCurrentCultureInfo()
    {
        var androidLocale = Java.Util.Locale.Default;
        var netLanguage = androidLocale.ToString().Replace("_", "-"); // turns pt_BR into pt-BR
        return new System.Globalization.CultureInfo(netLanguage);
    }
}

iOS

public class Localize : ILocalize
{
    public CultureInfo GetCurrentCultureInfo()
    {
        var netLanguage = "en";
        var prefLanguageOnly = "en";

        if (NSLocale.PreferredLanguages.Length > 0)
        {
            var pref = NSLocale.PreferredLanguages[0];
            prefLanguageOnly = pref.Substring(0, 2);

            if (prefLanguageOnly == "pt")
            {
                if (pref == "pt")
                    pref = "pt-BR"; // Brazilian
                else
                    pref = "pt-PT"; // Portuguese
            }

            netLanguage = pref.Replace("_", "-");
        }

        CultureInfo cultureInfo = null;
        try
        {
            cultureInfo = new CultureInfo(netLanguage);
        }
        catch
        {
            // Fallback to first two characters, e.g. "en"
            cultureInfo = new CultureInfo(prefLanguageOnly);
        }

        return cultureInfo;
     }
 }

Now you can build your resource container. Calling the ResourceContainer in C# will give you the correct string depending upon the users

_resourceContainer.GetString("LoginButtonText");

MarkupExtension

Next we can create a MarkupExtension to use this ResourceContainer in XAML. If you want to use the StaticInitialization as shown in the example below, use Exrin or manually implement StaticInitialize. Otherwise,you can replace the Static Initialization with just a public static property that you assign to.

namespace Mobile.ViewExtension
{
    using Exrin.Abstraction;
    using System;
    using System.Diagnostics;
    using Xamarin.Forms;
    using Xamarin.Forms.Xaml;

    [ContentProperty("Text")]
    public class TranslateExtension : IMarkupExtension, IStaticInitialize
    {
        private static bool _initialized = false;
        private static IResourceContainer _resourceContainer = null;

        [StaticInitialize]
        public TranslateExtension(IResourceContainer resourceContainer)
        {
            _initialized = true;
            _resourceContainer = resourceContainer;
        }

        public TranslateExtension() { }
 
        public string Text { get; set; }

        public object ProvideValue(IServiceProvider serviceProvider)
        {
            if (!_initialized)
                throw new NullReferenceException($"{nameof(TranslateExtension)} can not be called as it was not initialized. You must call Init() first.");

            if (Text == null)
                return "";

            var translation = _resourceContainer.GetString(Text);

            if (translation == null)
            {
                Debug.WriteLine(String.Format("Key '{0}' was not found in resources.", Text)); // I want to know about this during debugging

                translation = Text; // Returns the key, which gets displayed to the user as a last resort effort to display something meaningful
            }

            return translation;
        }
    }
}

In XAML you can now implement the translate extension as shown.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Mobile.View.LoginView"
             xmlns:language="clr-namespace:Mobile.ViewExtension">
    <Button Text="{language:Translate LoginButtonText}" />
</ContentPage>
Microsoft MVP | Xamarin MVP | Xamarin Certified Developer |
Exrin MVVM Framework | Xamarin Forms Developer | Melbourne, Australia

Related Posts

2 Comments

  1. Dave Parker

    Hi Adam,

    I’m doing basically the same as you. My version of localise is optimised to reduce the number of times the try/catch runs. I have a few other odd non .net supported languages handled as well.

    The other tooling I am using is MAT. It is excellent. Need another language, just tick a box and hit done.

Leave A Comment?