使用Unity Localization插件进行项目本地化实战详解
在使用Unity开发游戏的过程中,本地化是必不可少的。网络上也有很多的本地化工具,本次我介绍的是Unity官方提供的Localization插件,大家可以在Package Manager进行安装
一、语言配置,本地化表创建
在Project Setting中找到Localization,(需要先创建这个Localization Setting文件)点击Locale Generator选择需要本地化的语言。创建好后会得到这些文件,这些文件可以用于切换语言(后面做切换语言界面时会用),先把英语拖入作为默认语言。
打开本地化表工具,创建本地化表
创建UILocalization和ScriptLocalization两个本地化表,分别用作UI和代码本地化。会得到以下文件
可以在表格中添加一些需要本地化的文本(这里只是先试试,先不要添加太多,后面会用Excel进行管理),注意最左侧的Key值,这个key值后续用来确定文本
二、使用Localize String Event进行本地化
给UI上的文本挂载LocalizeStringEvent脚本,点击其中最上面的String Reference搜索之前表格中创建的本地化文本。搜索Key值或者本地化的文本都可以搜得到。
注:如果显示不完整,可以调节右下角的小球
然后将LocalizeStringEvent的Update String调整为Text.text,就是要刷新的脚本。任何的string都可以刷新,Text Mesh Pro也是可以的
运行游戏查看效果,可以临时先使用右上角的下拉框调节本地化语言,界面上的文本会实时刷新。
三、UI本地化
我先将本地化的需求暂时分成两类
1,UI本地化:UI界面上面固定不变的文本
2,Script本地化:代码中实时更改的文本,包括(String.Format+数值),动态弹出的提示语,NPC对话,物品名字等
为了后续教程,我们暂时约定在制作界面时,UI本地化的内容正常命名,Script本地化的对象以$开头命名,例如:
首先,需要知道有哪些文本需要本地化,并给它们自动挂载上LocalizeStringEvent脚本。可以在Editor目录下创建一个扩展脚本实现此功能。
using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; using UnityEngine.Events; using UnityEngine.Localization; using UnityEngine.Localization.Components; using UnityEngine.UI; public class LocalizeTextEditor : EditorWindow { private string outputFilePath = "LocalizedText.txt"; // 指定的txt文件路径 private List<string> localizedTextEntries = new List<string>(); [MenuItem("Custom/Localize Text")] private static void ShowWindow() { GetWindow<LocalizeTextEditor>("Localize Text"); } private void OnGUI() { GUILayout.Label("Localize Text Editor", EditorStyles.boldLabel); if (GUILayout.Button("Localize Selected GameObjects")) { LocalizeSelectedGameObjects(); } if (GUILayout.Button("Save Localized Text to File")) { SaveLocalizedTextToFile(); } if (GUILayout.Button("Print Script Localize Selected GameObjects")) { PrintScriptLocalize(); } } private void LocalizeSelectedGameObjects() { localizedTextEntries.Clear(); GameObject[] selectedObjects = Selection.gameObjects; foreach (GameObject selectedObject in selectedObjects) { Text[] textComponents = selectedObject.GetComponentsInChildren<Text>(true); foreach (Text textComponent in textComponents) { if (!textComponent.name.StartsWith("$")) { // 需要本地化的Text if(textComponent.gameObject.GetComponent<LocalizeStringEvent>() == null) { LocalizeStringEvent localizeEvent = textComponent.gameObject.AddComponent<LocalizeStringEvent>(); // 标记对象为“已修改” EditorUtility.SetDirty(selectedObject); } // 添加到列表 string entry = $"{selectedObject.name}\t{textComponent.name}\t{textComponent.text}"; localizedTextEntries.Add(entry); } } } } private void SaveLocalizedTextToFile() { if(!File.Exists(outputFilePath)) { File.Create(outputFilePath).Dispose(); } using (StreamWriter writer = new StreamWriter(outputFilePath, true)) { foreach (string entry in localizedTextEntries) { writer.WriteLine(entry); } } Debug.Log($"Localized text entries saved to {outputFilePath}"); } private void PrintScriptLocalize() { GameObject[] selectedObjects = Selection.gameObjects; foreach (GameObject selectedObject in selectedObjects) { Text[] textComponents = selectedObject.GetComponentsInChildren<Text>(true); foreach (Text textComponent in textComponents) { if (textComponent.name.StartsWith("$")) { Debug.Log($"{selectedObject.name}\t{textComponent.name}\t{textComponent.text}"); } } } } }
我本来还想自动给LocalizeStringEvent的Update String赋值,但是未能实现。如果哪位高人有办法可以在评论区指出
全选需要本地化的UI预制体,依次点击扩展窗口的第1和第2个按钮,可以给非$开头的Text(你们如果命名规则不一样,请自行修改脚本)自动挂载LocalizeStringEvent脚本
这些Text中的内容会输出到txt中,可以查看(注:txt默认应该在项目根路径)
四、使用Excel表格管理本地化文本
右键点击本地化表的选项卡,选择导出CSV
如果使用Office,导出时选择UTF-8即可。如果和我一样使用WPS,请另存为xlsx格式,不然部分语言会乱码。
2024.2更新:不建议另存为xlsx格式,因为ID超过15位会舍入。导致原本设置好的组件找不到本地化ID
可以使用python的pandas库将数据全部变成string,再写入xlsx
import pandas as pd df = pd.read_csv('UILocalization.csv') df = df.astype(str) df.to_excel('UILocalization.xlsx', index=False, sheet_name="UILocalization") df2 = pd.read_csv('ScriptLocalization.csv') df2 = df.astype(str) df2.to_excel('ScriptLocalization.xlsx', index=False, sheet_name="ScriptLocalization")
将之前txt中的内容复制到表格中,由于我输出的是\t,内容会自动分布到表格的不同列,大家可以把根据预制体名字来给Key起名。
丢给AI或者翻译软件翻译之后,对于没有空格的语言,可以使用alt+回车在适当位置输入换行,例如:中文,日语等。Unity的自适应换行只会在有空格的地方换行
对于WPS用户,我们需要将xlsx转回CSV才能在Unity中读取,这里使用python的pandas库进行处理。
import pandas as pd # 读取 Excel 文件 df = pd.read_excel("UILocalization.xlsx", sheet_name="UILocalization") # 将 DataFrame 写入 CSV 文件 df.to_csv('dataUI.csv', encoding='utf-8', index=False) df2 = pd.read_excel("ScriptLocalization.xlsx", sheet_name="ScriptLocalization") df2.to_csv('dataScript.csv', encoding='utf-8', index=False)
这里还处理了后面的Script本地化文件,大家还没看完后续教程的,可以先注释后两行代码
在本地化表导出CSV的上面还有导入CSV,这里就不截图了,导入之后就可以得到我们刚才在Office或WPS中编辑的表格
五、Script本地化
还是全选所有UI预制体,这次点击第三个按钮,会在控制台中输出所有$开头的Text。这些Text都是在代码中更改内容的,只能对其查找所有引用,然后逐个更改。
那么,如何在代码中获取本地化的内容呢,我们需要创建一个单例的LocalizationManager,提供一个获取本地化内容的函数。
以防止有编程小白,这里介绍一下单例模式,单例模式就是类最多只有一个对象,并且有一个指向该对象的静态指针。在Unity中可以用以下代码实现一个简单的单例模式
using UnityEngine; public class Singleton<T> : MonoBehaviour where T : Singleton<T> { protected static T _instance; public static T Ins { get { return _instance; } } protected virtual void Awake() { _instance = (T)this; } protected virtual void OnDestroy() { if (_instance == this) { _instance = null; } } }
在场景中创建一个(DontDestroyOnLoad)不会销毁的对象,并挂载LocalizationManager脚本
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Localization; using UnityEngine.Localization.Settings; using UnityEngine.Localization.Tables; using UnityEngine.ResourceManagement.AsyncOperations; namespace UI { public class LocalizationManager : Singleton<LocalizationManager> { private StringTable ScriptStringTable; //代码本地化表 void Start() { if (LocalizationSettings.AvailableLocales.Locales.Count > 0) { GetLocalizationTable(); } else { LocalizationSettings.InitializationOperation.Completed += OnLocalizationInitialized; } LocalizationSettings.SelectedLocaleChanged += OnLocaleChanged; } protected override void OnDestroy() { base.OnDestroy(); LocalizationSettings.SelectedLocaleChanged -= OnLocaleChanged; } /// <summary> /// 本地化初始化完成 /// </summary> private void OnLocalizationInitialized(AsyncOperationHandle<LocalizationSettings> handle) { if (handle.Status == AsyncOperationStatus.Succeeded) { Debug.Log("Localization initialized successfully!"); GetLocalizationTable(); } else { Debug.LogError("Localization initialization failed."); } } /// <summary> /// 切换语言 /// </summary> private void OnLocaleChanged(Locale newLocale) { GetLocalizationTable(); } /// <summary> /// 获取本地化表 /// </summary> public void GetLocalizationTable() { ScriptStringTable = LocalizationSettings.StringDatabase.GetTable("ScriptLocalization"); //Debug.LogWarning(ScriptStringTable.GetEntry("CommonTip_NoItem").GetLocalizedString()); } /// <summary> /// 获取本地化文本 /// </summary> public string GetLocalizedString(string key) { return ScriptStringTable.GetEntry(key).GetLocalizedString(); } } }
这里我们注册了两个事件,一个是LocalizationSettings.InitializationOperation.Completed,这个是LocalizationSettings初始化完成时调用。由于本地化插件是异步初始化,代码运行到start时不一定初始化完成,此处通过判断可用语言是否大于0来判断有没有初始化完成。
另一个事件是LocalizationSettings.SelectedLocaleChanged,这个是LocalizationSettings切换语言是调用。这两个事件都会执行获取本地化表的操作。对于之前在代码中更改的Text,可用调用GetLocalizedString来获取本地化文本。
例如:
物品名字要进行本地化,key值是GameItem_ID,每个物品ID不同
和之前使用表格管理UILocalization表类似,我们也使用表格管理ScriptLocalization表。
六、语言切换界面
创建一个这样的UI界面,我设计的是每个按钮都能点。当前使用的语言是绿的。
给每个按钮拖上一个Locale,就是之前最开始创建的用于切换语言的
/// <summary> /// 刷新按钮状态 /// </summary> public void RefreshItemChooseState() { Locale currentLocale = LocalizationSettings.SelectedLocale; foreach (var item in languageItems) { item.SetChooseState(currentLocale == item.locale); } } /// <summary> /// 设置语言 /// </summary> public void SetLanguage(Locale locale) { if(locale == LocalizationSettings.SelectedLocale) { return; } LocalizationSettings.Instance.SetSelectedLocale(locale); Client.Ins.Player.PlayerLanguage = locale.LocaleName; RefreshItemChooseState(); }
获取当前语言:LocalizationSettings.SelectedLocale,获取之后和每个按钮上面拖入的Locale比对,一样的就是当前语言
设置语言:LocalizationSettings.Instance.SetSelectedLocale(locale);把拖入的Locale设置进去,就可以切换语言。
以下为拓展内容:
设置完语言之后,最好能够保存下来,用户下次启动自动使用。这里把locale.LocaleName,也就是语言的名字保存下来。
大家可以用任意方式保存一个string,我这边用的json,这里就涉及到游戏存档设计了,超出了本文的讨论范围。大家可以自行设计游戏存档,反正能够存取当前语言名字(public string PlayerLanguage)就行了
这里仍然使用了之前提到的LocalizationSettings.InitializationOperation.Completed事件,在LocalizationSettings初始化完成之后设置为保存的语言。
/// <summary> /// 本地化初始化完成 /// </summary> private void OnLocalizationInitialized(AsyncOperationHandle<LocalizationSettings> handle) { if (handle.Status == AsyncOperationStatus.Succeeded) { Debug.Log("Localization initialized successfully!"); UseSaveLanguage(); } else { Debug.LogError("Localization initialization failed."); } } /// <summary> /// 使用保存的语言 /// </summary> private void UseSaveLanguage() { var locales = LocalizationSettings.AvailableLocales.Locales; foreach (var locale in locales) { if (locale.LocaleName == PlayerLanguage) { LocalizationSettings.Instance.SetSelectedLocale(locale); break; } } }