Skip to content

Localization System

Introduction

The localization system consists of two parts:

  1. Localization: ET framework's basic localization component, providing runtime multi-language support
  2. AnyLocalization: Enhanced localization tool, providing complete multi-language solutions, including editor support

System Architecture

1. Directory Structure

Assets/
├── Scripts/
│   └── ETCore/                         
│       └── Localization/               
│           └── LocalizationSingleton.cs      # Localization Component

├── Res/
│   └── AnyLocalization/           
│       ├── Editor/                # Editor Tools
│       └── Dictionaries/          # Language Configuration Files

2. Core Code

LocalizationSingleton.cs
csharp
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using TMPro;
using UnityEngine;
using UnityEngine.Networking;


namespace MH
{
    public class LocalizationSingleton : LogicSingleton<LocalizationSingleton>, ISingletonAwake
    {
        public Dictionary<string, string> StrKeyValuePairs;

        public Language DefaultLanguage;

        public string StreamPath;

        public Language Language;

        public string XmlStreamPath;

        public XmlDocument XmlDocument;
        public void Awake()
        {
            XmlDocument = new XmlDocument();
            StrKeyValuePairs = new Dictionary<string, string>();
            DefaultLanguage = Language.ChineseSimplified;
            StreamPath = XMLStreamingAssetsPath;



            Language = (Language)PlayerPrefs.GetInt("Setting.Language", 0);
            if (Language == Language.Unspecified)
            {
                Language = DefaultLanguage;
                Debug.LogWarning("Language Unspecified! Using Default Language!");
            }
            LoadXmlStream().Coroutine();
            SetLanguage(Language);
        }
        private readonly HashSet<AutoLocalization> autoLocalizationCache = new HashSet<AutoLocalization>();

        private async ETTask LoadXmlStream()
        {
            XmlStreamPath = $"{Application.streamingAssetsPath}/{StreamPath}/{Language}.xml";
            Debug.Log($"XML Stream Path: {XmlStreamPath}");
            Debug.Log("Loading Languages XML Stream....");

            if (Utility.IsWebGL())
            {
                await LoadXmlStreamAsync();
                return;
            }
            else if (Utility.IsAndroid())
            {
                using var request = UnityWebRequest.Get(StreamPath);
                try 
                {
                    await request.SendWebRequest();
                    if (request.result == UnityWebRequest.Result.Success)
                    {
                        XmlDocument.LoadXml(Utility.RemoveXMLBom(request.downloadHandler.text));
                    }
                    else
                    {
                        Debug.LogError($"Download failed: {request.error}");
                    }
                }
                catch (Exception ex)
                {
                    Debug.LogError($"Request exception: {ex.Message}");
                }
            }
            else
            {
                if (File.Exists(XmlStreamPath)) XmlDocument.Load(XmlStreamPath);
            }

            CreateDictionary();
        }

        private async ETTask LoadXmlStreamAsync()
        {
            var request = UnityWebRequest.Get(XmlStreamPath);
            await request.SendWebRequest().GetAwaiter();

            if (request.isDone)
            {
                Debug.Log($"Request is done!");
                XmlDocument.LoadXml(Utility.RemoveXMLBom(request.downloadHandler.text));
                CreateDictionary();
                RefreshText();
            }
            else
            {
                Debug.LogError($"Request failure!{request.error}");
            }
        }

        private void CreateDictionary()
        {
            XmlNodeList xmlNodeList = null;

            try
            {
                xmlNodeList = XmlDocument.SelectSingleNode("Dictionaries/Dictionary").ChildNodes;
            }
            catch
            {
                throw new System.Exception($"Non-existent Language:{Language}");
            }

            StrKeyValuePairs = new Dictionary<string, string>();

            foreach (XmlNode item in xmlNodeList)
            {
                string key = item.Attributes["Key"].Value;
                string value = item.Attributes["Value"].Value;
                StrKeyValuePairs.Add(key, value);
            }
            Debug.Log("Load Simple Languages XML Succeed.");
        }
        public void SetLanguage(Language language){
            SetLanguageAsync(language).Coroutine();
        }
        private async ETTask SetLanguageAsync(Language language)
        {
            Language = language;
            PlayerPrefs.SetInt("Setting.Language", (int)language);
            await LoadXmlStream();
            RefreshText();
        }

        public void RegisterAutoLocalization(AutoLocalization autoLocalization)
        {
            if (autoLocalization == null)
                return;
            autoLocalizationCache.Add(autoLocalization);
        }

        public void UnRegisterAutoLocalization(AutoLocalization autoLocalization)
        {
            if (autoLocalization == null)
                return;
            autoLocalizationCache.Remove(autoLocalization);
        }

        private void RefreshText()
        {
            foreach (var obj in autoLocalizationCache)
            {
                if (obj != null && obj.gameObject != null)
                {
                    obj.ShowText();
                }
            }
        }
        public const string XMLStreamingAssetsPath = "AnyLocalization/XML";

        public string GetString(string key)
        {
            if (StrKeyValuePairs.TryGetValue(key, out string value)) return System.Text.RegularExpressions.Regex.Unescape(value);
            Debug.LogWarning("Non-existent Key:" + key);
            return $"[No Key]{key}";
        }
        public string GetString(string key, object arg0)
        {
            string value = GetString(key);

            try
            {
                return Utility.Format(value, arg0);
            }
            catch (Exception exception)
            {
                return Utility.Format("[Error]{0},{1},{2},{3}", key, value, arg0, exception.ToString());
            }
        }

        public string GetString(string key, object arg0, object arg1)
        {
            string value = GetString(key);

            try
            {
                return Utility.Format(value, arg0, arg1);
            }
            catch (Exception exception)
            {
                return Utility.Format("[Error]{0},{1},{2},{3},{4}", key, value, arg0, arg1, exception.ToString());
            }
        }

        public string GetString(string key, object arg0, object arg1, object arg2)
        {
            string value = GetString(key);

            try
            {
                return Utility.Format(value, arg0, arg1, arg2);
            }
            catch (Exception exception)
            {
                return Utility.Format("[Error]{0},{1},{2},{3},{4},{5}", key, value, arg0, arg1, arg2, exception.ToString());
            }
        }

        public string GetString(string key, params object[] args)
        {
            string value = GetString(key);

            try
            {
                return Utility.Format(value, args);
            }
            catch (Exception exception)
            {
                string errorString = Utility.Format("[Error]{0},{1}", key, value);
                if (args != null)
                {
                    foreach (object arg in args)
                    {
                        errorString += "," + arg.ToString();
                    }
                }

                errorString += "," + exception.ToString();
                return errorString;
            }
        }

        public TMP_FontAsset GetFont(Scene root)
        {
            var config = ConfigsSingleton.Instance.Tables.TbLanguageConfig.GetLanguageConfigByLanguageType(Language);
            var resourcesComponent = root.GetComponent<ResourcesComponent>();
            var loadAssetSync = resourcesComponent.LoadAssetSync<TMP_FontAsset>(config.LanguageFonts);
            return loadAssetSync;
        }
        public TMP_FontAsset GetFontByName(Scene root, string name)
        {
            var resourcesComponent = root.GetComponent<ResourcesComponent>();
            var loadAssetSync = resourcesComponent.LoadAssetSync<TMP_FontAsset>(name);
            return loadAssetSync;
        }
    }
}

Features

1. Language Management

Supported Languages

Currently supported language enums:

Localization
Enum
csharp
namespace MH
{
    /// <summary>
    /// Localization language.
    /// </summary>
    public enum Language
    {
        /// <summary>
        /// Unspecified.
        /// </summary>
        Unspecified = 0,

        /// <summary>
        /// Afrikaans.
        /// </summary>
        Afrikaans,

        /// <summary>
        /// Albanian.
        /// </summary>
        Albanian,

        /// <summary>
        /// Arabic.
        /// </summary>
        Arabic,

        /// <summary>
        /// Basque.
        /// </summary>
        Basque,

        /// <summary>
        /// Belarusian.
        /// </summary>
        Belarusian,

        /// <summary>
        /// Bulgarian.
        /// </summary>
        Bulgarian,

        /// <summary>
        /// Catalan.
        /// </summary>
        Catalan,

        /// <summary>
        /// Chinese Simplified.
        /// </summary>
        ChineseSimplified,

        /// <summary>
        /// Chinese Traditional.
        /// </summary>
        ChineseTraditional,

        /// <summary>
        /// Croatian.
        /// </summary>
        Croatian,

        /// <summary>
        /// Czech.
        /// </summary>
        Czech,

        /// <summary>
        /// Danish.
        /// </summary>
        Danish,

        /// <summary>
        /// Dutch.
        /// </summary>
        Dutch,

        /// <summary>
        /// English.
        /// </summary>
        English,

        /// <summary>
        /// Estonian.
        /// </summary>
        Estonian,

        /// <summary>
        /// Faroese.
        /// </summary>
        Faroese,

        /// <summary>
        /// Finnish.
        /// </summary>
        Finnish,

        /// <summary>
        /// French.
        /// </summary>
        French,

        /// <summary>
        /// Georgian.
        /// </summary>
        Georgian,

        /// <summary>
        /// German.
        /// </summary>
        German,

        /// <summary>
        /// Greek.
        /// </summary>
        Greek,

        /// <summary>
        /// Hebrew.
        /// </summary>
        Hebrew,

        /// <summary>
        /// Hungarian.
        /// </summary>
        Hungarian,

        /// <summary>
        /// Icelandic.
        /// </summary>
        Icelandic,

        /// <summary>
        /// Indonesian.
        /// </summary>
        Indonesian,

        /// <summary>
        /// Italian.
        /// </summary>
        Italian,

        /// <summary>
        /// Japanese.
        /// </summary>
        Japanese,

        /// <summary>
        /// Korean.
        /// </summary>
        Korean,

        /// <summary>
        /// Latvian.
        /// </summary>
        Latvian,

        /// <summary>
        /// Lithuanian.
        /// </summary>
        Lithuanian,

        /// <summary>
        /// Macedonian.
        /// </summary>
        Macedonian,

        /// <summary>
        /// Malayalam.
        /// </summary>
        Malayalam,

        /// <summary>
        /// Norwegian.
        /// </summary>
        Norwegian,

        /// <summary>
        /// Persian.
        /// </summary>
        Persian,

        /// <summary>
        /// Polish.
        /// </summary>
        Polish,

        /// <summary>
        /// Brazilian Portuguese.
        /// </summary>
        PortugueseBrazil,

        /// <summary>
        /// Portuguese.
        /// </summary>
        PortuguesePortugal,

        /// <summary>
        /// Romanian.
        /// </summary>
        Romanian,

        /// <summary>
        /// Russian.
        /// </summary>
        Russian,

        /// <summary>
        /// Serbo-Croatian.
        /// </summary>
        SerboCroatian,

        /// <summary>
        /// Serbian Cyrillic.
        /// </summary>
        SerbianCyrillic,

        /// <summary>
        /// Serbian Latin.
        /// </summary>
        SerbianLatin,

        /// <summary>
        /// Slovak.
        /// </summary>
        Slovak,

        /// <summary>
        /// Slovenian.
        /// </summary>
        Slovenian,

        /// <summary>
        /// Spanish.
        /// </summary>
        Spanish,

        /// <summary>
        /// Swedish.
        /// </summary>
        Swedish,

        /// <summary>
        /// Thai.
        /// </summary>
        Thai,

        /// <summary>
        /// Turkish.
        /// </summary>
        Turkish,

        /// <summary>
        /// Ukrainian.
        /// </summary>
        Ukrainian,

        /// <summary>
        /// Vietnamese.
        /// </summary>
        Vietnamese
    }
}

Note that you still need to configure the corresponding languages manually

Language Switching

csharp
// Set language
LocalizationSingleton.Instance.SetLanguage(Language.Chinese);

// Get current language
var language = LocalizationSingleton.Instance.Language;

2. Localized Text

Localized XML texts are stored in the Assets/StreamingAssets/AnyLocalization/XML/ folder

Text Retrieval

  1. By selecting the Key in advance for the corresponding script on the prefab

Usage

  1. Add AutoLocalization component to UI text component
  2. Set the corresponding localization key
  3. Text will automatically update at runtime and respond to language switching Localization
  1. Through code for dynamic assignment
csharp
// Direct text retrieval
var str = LocalizationSingleton.Instance.GetString("Settings");

There are also methods with placeholders, see LocalizationSingleton for details

Editor Tools

1. Localization Editor

Feature List

  • Visual XML file editing
  • Support for multi-language parallel editing
  • Import/Export Excel

Usage Steps

  1. Open Editor: Window/AnyLocalizationLocalization
  2. Select Directory: Assets/Res/AnyLocalization/DictionariesLocalization
  3. Edit Content Localization
  4. Save and Generate XML Files

Important Notes

WARNING

  1. Keep all language files synchronized
  2. Regularly backup language files
  3. Be aware of file path differences across platforms

Technical Support

Get Help

  • 💬 Join QQ Group for discussion (ET Framework Group): 474643097
  • ⭐ Follow the project on GitHub for latest updates

Released under the MIT License.