Skip to content

多语言系统

简介

多语言系统由两部分组成:

  1. Localization: ET框架的基础本地化组件,提供运行时多语言支持
  2. AnyLocalization: 增强版本地化工具,提供完整的多语言解决方案,包括编辑器支持

系统架构

1. 目录结构

Assets/
├── Scripts/
│   └── ETCore/                         
│       └── Localization/               
│           └── LocalizationSingleton.cs      # 本地化组件

├── Res/
│   └── AnyLocalization/           
│       ├── Editor/                # 编辑器工具
│       └── Dictionaries/          # 语言配置文件

2. 核心代码

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($"下载失败: {request.error}");
                    }
                }
                catch (Exception ex)
                {
                    Debug.LogError($"请求异常: {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;
        }
    }
}

功能特性

1. 语言管理

支持的语言类型

目前的语言枚举都有:

多语言
枚举
csharp
namespace MH
{
    /// <summary>
    /// 本地化语言。
    /// </summary>
    public enum Language
    {
        /// <summary>
        /// 未指定。
        /// </summary>
        Unspecified = 0,

        /// <summary>
        /// 南非荷兰语。
        /// </summary>
        Afrikaans,

        /// <summary>
        /// 阿尔巴尼亚语。
        /// </summary>
        Albanian,

        /// <summary>
        /// 阿拉伯语。
        /// </summary>
        Arabic,

        /// <summary>
        /// 巴斯克语。
        /// </summary>
        Basque,

        /// <summary>
        /// 白俄罗斯语。
        /// </summary>
        Belarusian,

        /// <summary>
        /// 保加利亚语。
        /// </summary>
        Bulgarian,

        /// <summary>
        /// 加泰罗尼亚语。
        /// </summary>
        Catalan,

        /// <summary>
        /// 简体中文。
        /// </summary>
        ChineseSimplified,

        /// <summary>
        /// 繁体中文。
        /// </summary>
        ChineseTraditional,

        /// <summary>
        /// 克罗地亚语。
        /// </summary>
        Croatian,

        /// <summary>
        /// 捷克语。
        /// </summary>
        Czech,

        /// <summary>
        /// 丹麦语。
        /// </summary>
        Danish,

        /// <summary>
        /// 荷兰语。
        /// </summary>
        Dutch,

        /// <summary>
        /// 英语。
        /// </summary>
        English,

        /// <summary>
        /// 爱沙尼亚语。
        /// </summary>
        Estonian,

        /// <summary>
        /// 法罗语。
        /// </summary>
        Faroese,

        /// <summary>
        /// 芬兰语。
        /// </summary>
        Finnish,

        /// <summary>
        /// 法语。
        /// </summary>
        French,

        /// <summary>
        /// 格鲁吉亚语。
        /// </summary>
        Georgian,

        /// <summary>
        /// 德语。
        /// </summary>
        German,

        /// <summary>
        /// 希腊语。
        /// </summary>
        Greek,

        /// <summary>
        /// 希伯来语。
        /// </summary>
        Hebrew,

        /// <summary>
        /// 匈牙利语。
        /// </summary>
        Hungarian,

        /// <summary>
        /// 冰岛语。
        /// </summary>
        Icelandic,

        /// <summary>
        /// 印尼语。
        /// </summary>
        Indonesian,

        /// <summary>
        /// 意大利语。
        /// </summary>
        Italian,

        /// <summary>
        /// 日语。
        /// </summary>
        Japanese,

        /// <summary>
        /// 韩语。
        /// </summary>
        Korean,

        /// <summary>
        /// 拉脱维亚语。
        /// </summary>
        Latvian,

        /// <summary>
        /// 立陶宛语。
        /// </summary>
        Lithuanian,

        /// <summary>
        /// 马其顿语。
        /// </summary>
        Macedonian,

        /// <summary>
        /// 马拉雅拉姆语。
        /// </summary>
        Malayalam,

        /// <summary>
        /// 挪威语。
        /// </summary>
        Norwegian,

        /// <summary>
        /// 波斯语。
        /// </summary>
        Persian,

        /// <summary>
        /// 波兰语。
        /// </summary>
        Polish,

        /// <summary>
        /// 巴西葡萄牙语。
        /// </summary>
        PortugueseBrazil,

        /// <summary>
        /// 葡萄牙语。
        /// </summary>
        PortuguesePortugal,

        /// <summary>
        /// 罗马尼亚语。
        /// </summary>
        Romanian,

        /// <summary>
        /// 俄语。
        /// </summary>
        Russian,

        /// <summary>
        /// 塞尔维亚克罗地亚语。
        /// </summary>
        SerboCroatian,

        /// <summary>
        /// 塞尔维亚西里尔语。
        /// </summary>
        SerbianCyrillic,

        /// <summary>
        /// 塞尔维亚拉丁语。
        /// </summary>
        SerbianLatin,

        /// <summary>
        /// 斯洛伐克语。
        /// </summary>
        Slovak,

        /// <summary>
        /// 斯洛文尼亚语。
        /// </summary>
        Slovenian,

        /// <summary>
        /// 西班牙语。
        /// </summary>
        Spanish,

        /// <summary>
        /// 瑞典语。
        /// </summary>
        Swedish,

        /// <summary>
        /// 泰语。
        /// </summary>
        Thai,

        /// <summary>
        /// 土耳其语。
        /// </summary>
        Turkish,

        /// <summary>
        /// 乌克兰语。
        /// </summary>
        Ukrainian,

        /// <summary>
        /// 越南语。
        /// </summary>
        Vietnamese
    }
}

不过还是需要自行配置对应的多语言才行

语言切换

csharp
//设置语言
LocalizationSingleton.Instance.SetLanguage(Language.Chinese);

// 获取当前语言
var language = LocalizationSingleton.Instance.Language;

2. 本地化文本

本地化的XML文本存放在Assets/StreamingAssets/AnyLocalization/XML/文件夹下

文本获取

  1. 通过给预制体上对应的脚本提前选择好Key即可

使用方式

  1. 在UI文本组件上添加 AutoLocalization 组件
  2. 设置对应的本地化键值
  3. 运行时会自动更新文本,并响应语言切换 多语言
  1. 通过代码来进行动态赋值
csharp
// 直接获取文本
var str = LocalizationSingleton.Instance.GetString("Settings");

还有部分是带着占位符的获取方法,详细请看LocalizationSingleton

编辑器工具

1. 本地化编辑器

功能列表

  • 可视化编辑XML文件
  • 支持多语言对照编辑
  • 导入/导出Excel

使用步骤

  1. 打开编辑器: Window/AnyLocalization多语言
  2. 选择文件夹目录:Assets/Res/AnyLocalization/Dictionaries多语言
  3. 编辑内容 多语言
  4. 保存生成XML文件

注意事项

WARNING

  1. 保持所有语言文件的同步更新
  2. 定期备份语言文件
  3. 注意多平台文件路径差异

技术支持

获取帮助

  • 💬 加入QQ群讨论交流(ET框架群) : 474643097
  • ⭐ 在GitHub上关注项目获取最新更新

Released under the MIT License.