20180119_220424

Localization TextManager for Unity3d

When trying to add localization to my mobile game Level It! for Android I wanted to use the .net resource manager but unfortunately got a weird error message from Visual Studio. Apparently this is not supported with Unity3d. This post describes a simple text manager to do the localization with Unity 3d.

My starting point was this solution from the unity forum which I adapted to my needs. Thanks for the code. Hopefully the improvement in this post is helpful for someone.

The basic idea is to have a language file for each language. A language file consists of keys and contents. The language files need to be stored in the Unity solution (in Unity Editor) in a folder called Resources. To configure the Unity game with a language the script loads the textfile from the resources, parses it and stores the texts and keys in a dictionary. To retrieve a localized text call GetText(key).

 

TextManager Class Definition
using UnityEngine;
using System.IO;
using System.Collections.Generic;
using System;
using Assets.Scripts;

public class TextManager : MonoBehaviour
{
    private static readonly IDictionary<string, string> TextTable = new Dictionary<string, string>();
    private static TextAsset _language;

 

Singleton Constructor

TextManager is a singleton. I wanted to make sure there is only one TextManager in the system and the resource file should only be read once. Note the private constructor and the method instance. DontDestroyOnLoad() makes sure the object is not destroyed when a new scene is loaded.

    private TextManager() { }

    private static TextManager _instance;
    public static TextManager instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = GameObject.FindObjectOfType<TextManager>();

                // Tell unity not to destroy this object when loading a new scene!
                DontDestroyOnLoad(_instance.gameObject);
            }

            return _instance;
        }
    }

 

Awake to Destroy Additional Instances

When during the game you switch from a scene that does not have TextManager in its GameObjects list to a scene with TextManager then a new GameObject is created and the Awake() method is called on it. So if you use DontDestroyOnLoad() you would add another TextManager to the currently instanciated GameObjects. To make sure we only have one TextManager the implementation of Awake() destroys all additional TextManagers.

    void Awake()
    {
        if (_instance == null)
        {
            // If I am the first instance, make me the Singleton
            _instance = this;
            DontDestroyOnLoad(this);
            SetDefaultLanguage();
        }
        else
        {
            // If a Singleton already exists and you find
            // another reference in scene, destroy it!
            if (this != _instance)
                Destroy(this.gameObject);
        }
    }

 

Set the Default Language

Persistence.GetLanguage() returns the name of the language that the player has configured. It just reads a value from the PlayerPrefs. If the code is running for the first time we try to set the language that is set in the operating system. Definitions.GermanName, Definitions.EnglishName and Definitions.DefaultLanguages are constant strings. Persistence.SetLanguage() sets a value in the PlayerPrefs.

public static void SetDefaultLanguage()
    {
        string language = Persistence.GetLanguage();
        if (language == "")
        {
            var osLanguage = Application.systemLanguage;
            if (osLanguage == SystemLanguage.German)
            {
                language = Definitions.GermanName;
            }
            else if (osLanguage == SystemLanguage.English)
            {
                language = Definitions.EnglishName;
            }
            else
            {
                language = Definitions.DefaultLanguage;
            }
            
            Persistence.SetLanguage(language);
        }

        SetLanguage(language);
    }

 

Load the Text File from the Resources

The method SetLanguage() loads the language text file in memory and calls LoadLanguage().


    public static void SetLanguage(string langFilename)
    {
        // Load a asset by its AssetName.
        // The text file must be located within 'Resources' subfolder in Unity ('Assets/Resources' in Visual Studio).
        TextAsset newLanguage = Resources.Load(langFilename) as TextAsset;
        if (newLanguage == null)
        {
            Debug.LogError(String.Format("No valid asset file."));
        }
        else
        {
            LoadLanguage(newLanguage);
            _language = newLanguage;
        }
    }

 

Parse the Text File to Create the Dictionary

LoadLanguage() parses the language text file and stores all entries in a dictionary. For the expected file format see below.

    
    private static void LoadLanguage(TextAsset asset)
    {
        TextTable.Clear();

        var lineNumber = 1;
        using (var reader = new StringReader(asset.text))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                int firstSpaceIndex = line.IndexOf('=');
                if (firstSpaceIndex > 0)
                { 
                    var key = line.Substring(0, firstSpaceIndex);
                    var val = line.Substring(firstSpaceIndex + 1);

                    if (key != null && val != null)
                    {
                        var loweredKey = key.ToLower();
                        if (TextTable.ContainsKey(loweredKey))
                        {
                            Debug.LogWarning(String.Format(
                                     "Duplicate key '{1}' in language file '{0}' detected.", asset.text, key));
                        }
                        TextTable.Add(loweredKey, val);
                    }
                }
                else
                {
                    Debug.LogWarning(String.Format(
                         "Format error in language file '{0}' on line {1}. Each line must be of format: key=value",
                          asset.text, lineNumber));
                }

                lineNumber++;
            }
        }
    }

 

Retrieve the Localized Text

This is the implementation that retrieves the localized text from the dictionary.


    public string GetText(string key)
    {
        string result = "";
        var loweredKey = key.ToLower();

        if (TextTable.ContainsKey(loweredKey))
        {
            result = TextTable[loweredKey];
        }
        else
        {
            Debug.LogWarning(String.Format("Couldn't find key '{0}' in dictionary.", key));
        }

        return result;
    }
}

 

Text File Format

The language files are expected to have this format:
English.txt:

firstkey=first english text
key2=another english text

German.txt:

firstkey=erster deutscher Text
key2=Noch ein deutscher Text

The keys need to be the same in all language files.

German Umlauts
To make sure the german umlauts (äöü) are properly displayed I set the encoding of the text file to UTF-8. For instance use Notepad++ and you find the Encoding in the menu.

 

Unity 3d

Add the TextManager to the list of GameObjects in Unity. You only need to add it to the scene that is loaded first. The way we defined the constructor and the Awake() method the TextManager will always be there and exactly once.

To get localized text use this line of code:

TextManager.instance.GetText("key");

So that’s it. Happy coding! :-)

Leave a Reply

Your email address will not be published. Required fields are marked *