Unity Localization插件深度实践:避坑指南与工程化落地
1. 为什么Unity官方Localization插件不是“开箱即用”,而是“开箱即踩坑”
你刚在Unity Package Manager里搜到Localization,点安装,等进度条走完,兴冲冲打开Window → Localization → Tables,新建一个String Table,填两行中文、英文,再拖个Localize组件到Text上——结果运行起来,UI上显示的还是原始英文,或者干脆报错MissingReferenceException。你翻遍官方文档,发现它通篇讲的是“如何配置Localization Settings”,却没告诉你第一个必须手动创建的Asset到底该放在哪个文件夹、叫什么名字、序列化格式选JSON还是Binary才不会在Build后丢失。这不是你技术不行,是Unity官方把一套工业级本地化管线,包装成了一个“看起来很轻量”的插件。
Localization插件解决的从来不是“让文字变中文”这种表层问题,而是游戏出海时真正要命的系统性挑战:多语言资源热更新路径怎么设计?阿拉伯语从右向左(RTL)排版和中文混排时TextMeshPro的Fallback字体链怎么配?玩家切换语言后,已加载的Prefab里的Text组件如何自动刷新而不Destroy重建?动态生成的UI(比如背包格子、任务日志条目)怎么保证新实例一创建就带正确语言?这些都不是靠拖一个组件能搞定的,而是整套资源组织、运行时状态管理、UI生命周期钩子的协同结果。
我做过6款上线海外的产品,从休闲小游戏到MMORPG,Localization插件用得最多,也踩坑最深。它不像TextMeshPro那样装完就能渲染,而像一套需要你亲手校准的精密仪器——它的价值恰恰藏在那些“默认不生效”的细节里:比如Table Asset的Addressable Group设置错误,会导致Android平台Build后所有翻译字符串为空;又比如Localization Locale的Active Locale变更事件,如果没在Awake阶段订阅,UI组件根本收不到语言切换通知。这篇文章不讲“怎么点按钮”,只讲你实际部署时绕不开的4个核心断点、3套实测有效的工程结构、以及2个连Unity官方示例项目都没写的隐藏陷阱。适合已经跑通Demo但卡在正式集成的中高级Unity开发者,也适合技术负责人评估本地化方案落地成本。
2. Localization插件的核心机制:不是“翻译器”,而是“资源路由中枢”
2.1 插件架构的本质:三层解耦模型
Localization插件的底层逻辑,远比“查字典”复杂。它实际构建了一个运行时资源路由中枢(Runtime Resource Router),由三个强耦合但职责分明的层级组成:
数据层(Data Layer):以String Table、Sprite Table等Asset为载体,存储原始多语言数据。关键点在于:这些Asset本身不包含任何逻辑,只是纯数据容器。它们被序列化为JSON或Binary格式,存放在Assets/Resources/Localization/Tables/目录下(注意:Resources路径是硬编码依赖,不可更改)。我见过太多团队把Table放在Assets/StreamingAssets/下,结果Editor里能预览,Build后全部读取失败——因为Localization系统在运行时只扫描Resources路径及其子目录。
配置层(Configuration Layer):通过Localization Settings Asset统一管理全局行为。这里藏着最关键的三个开关:
- Fallback Behavior:当目标语言缺失某条Key时,是否回退到Editor Language(开发机语言)?还是强制回退到Base Language(通常为英语)?很多团队设成“Editor Language”,导致测试机语言为日语时,缺失的中文Key直接显示成开发机的简体中文,掩盖了真实漏翻译问题。
- Load Tables Asynchronously:勾选后,Table数据在首次访问时异步加载。看似提升启动速度,但会引发竞态条件——UI组件在Table未加载完成前就尝试获取翻译,返回null。实测下来,对中小型项目(Table总数<50),关闭此选项,改用Editor预加载(Preload Tables)更稳。
- Use Addressables:这是2021.3+版本新增的致命开关。一旦启用,所有Table Asset必须被标记为Addressable,并在Addressable Groups里设置正确的Bundle模式(建议选“Pack Together”而非“Pack Separately”,避免单个Table分散在多个Bundle导致加载失败)。我们曾因误开此开关且未配置Addressable,导致iOS包体增大12MB,且部分语言加载超时。
运行时层(Runtime Layer):这才是真正干活的部分,由
LocalizationManager单例驱动。它内部维护着两个核心缓存:- Locale Cache:缓存当前激活的Locale(如
zh-CN,ja-JP),通过LocalizationSettings.SelectedLocale访问。注意:这个属性是只读的,修改必须调用LocalizationSettings.SetSelectedLocale(),后者会触发OnLocaleChanged事件。 - Table Entry Cache:当代码首次调用
table.GetEntry("key")时,系统才将对应Table的完整数据加载进内存并建立哈希索引。这意味着如果你有100个String Table,但只用到其中3个,其余97个根本不会占用运行时内存——这是插件最被低估的性能优势。
- Locale Cache:缓存当前激活的Locale(如
提示:Localization Manager的初始化时机非常关键。它在
Awake()阶段自动初始化,但如果你的自定义MonoBehaviour在Awake()里就调用LocalizationSettings.SelectedLocale,可能拿到null。正确做法是在Start()或OnEnable()中检查LocalizationSettings.IsInitialized,未初始化则延迟执行。
2.2 String Table的物理结构:JSON vs Binary的实战抉择
当你新建一个String Table时,Inspector面板底部会出现Serialization Format选项:JSON、Binary、ScriptableObject。这绝非无关紧要的设置,它直接影响资源体积、加载速度和调试效率:
| 格式 | 体积(1000条Key) | 加载耗时(Android中端机) | 调试友好度 | 热更新支持 |
|---|---|---|---|---|
| JSON | 1.2 MB | 85ms | ★★★★★(文本可读,Git Diff清晰) | ★★★★☆(需重打包整个Table Asset) |
| Binary | 420 KB | 28ms | ★☆☆☆☆(二进制不可读,Diff无意义) | ★★★★☆(同JSON) |
| ScriptableObject | 1.8 MB | 110ms | ★★★★☆(Inspector内直接编辑) | ★★☆☆☆(SO无法热更,必须重新Build) |
我们最终选择JSON格式,理由很现实:
- 热更新兜底:当线上发现某条翻译错误,运营同学可直接用文本编辑器修改JSON文件,通过CDN下发,客户端下载后调用
LocalizationSettings.Tables.ReloadAllTables()即可生效,全程无需发版。 - 协作效率:策划用Excel导出CSV,程序写Python脚本一键转JSON,Git提交时能清晰看到哪一行被修改,避免Binary格式下“整个文件标红”的混乱。
- 体积可控:1.2MB对现代手游来说微不足道,且可通过LZ4压缩进一步减小(Unity 2021.3+支持Table Asset内置压缩)。
注意:JSON格式有个隐藏陷阱——Key名不能含空格或特殊字符。例如
"player name"作为Key,在JSON里会被序列化为"player name": "玩家姓名",但Localization系统内部会将Key标准化为player_name(下划线替换空格)。如果你在代码里写table.GetEntry("player name"),永远返回null。正确写法是table.GetEntry("player_name"),或在编辑器里直接输入player_name作为Key。
2.3 Locale系统的真相:不是“语言”,而是“区域+文化+排版”的组合体
很多人以为zh-CN就是“中文”,en-US就是“英文”,这是Localization插件最大的认知误区。Locale实际代表的是一个完整的文化上下文(Cultural Context),包含三重维度:
- 语言(Language):决定词汇翻译,如
zh表示中文。 - 区域(Region):决定格式规范,如
CN表示中国,影响日期格式(2023年10月25日)、数字分隔符(1,000.00vs1.000,00)、货币符号(¥ vs $)。 - 排版方向(Text Direction):决定UI布局,如
ar-SA(沙特阿拉伯)强制RTL,he-IL(以色列)也是RTL,但fa-IR(伊朗)虽用波斯语却属LTR。
这个设计导致一个关键后果:你不能简单地用Application.systemLanguage作为Locale匹配依据。Application.systemLanguage只返回SystemLanguage.Chinese这样的枚举值,而Localization需要的是Locale.Identifier字符串(如"zh-CN")。必须手动映射:
private static readonly Dictionary<SystemLanguage, string> SystemLangToLocale = new() { { SystemLanguage.Chinese, "zh-CN" }, { SystemLanguage.Japanese, "ja-JP" }, { SystemLanguage.Korean, "ko-KR" }, { SystemLanguage.Arabic, "ar-SA" }, { SystemLanguage.Hebrew, "he-IL" } }; // 正确获取初始Locale string initialLocaleId = SystemLangToLocale.GetValueOrDefault(Application.systemLanguage, "en-US"); LocalizationSettings.SelectedLocale = LocalizationSettings.AvailableLocales.Locales.FirstOrDefault(x => x.Identifier == initialLocaleId);更麻烦的是RTL支持。Unity UI系统原生不处理RTL,必须配合TextMeshPro。当Locale切换为ar-SA时,你需要:
- 设置
TextMeshProUGUI.enableWordWrapping = false(RTL下换行逻辑不同); - 为所有Text组件设置
alignment = TextAlignmentOptions.MidlineRight; - 在Canvas Scaler上启用
Scale With Screen Size,并确保Reference Resolution适配RTL(通常需加宽20%预留空间)。
我们曾因忽略第3点,导致阿拉伯语界面右侧按钮被裁切——因为Canvas按LTR设计的1080p分辨率,在RTL下内容实际占用宽度更大。
3. 多语言切换的完整实现链路:从用户点击到UI刷新的17个关键节点
3.1 切换入口的设计陷阱:为什么“设置界面下拉框”是最差方案
几乎所有教程都教你做一个Dropdown,绑定LocalizationSettings.AvailableLocales.Locales,选中后调用SetSelectedLocale()。这在Demo里完美运行,但在真实游戏中会崩溃:
- 问题1:Dropdown选项顺序错乱。
AvailableLocales.Locales是List,其顺序取决于你在Localization Settings里添加Locale的先后,而非语言名称排序。用户看到日本語排在English前面,体验割裂。 - 问题2:Locale不可用时无反馈。当用户选择
fr-FR,但项目尚未制作法语Table,SetSelectedLocale()静默失败,UI无变化,用户以为功能坏了。 - 问题3:切换过程无状态提示。大型游戏切换语言需加载数MB资源,界面应显示Loading,而非假死。
我们采用分步式语言选择器(Step-by-Step Language Selector),流程如下:
- 预加载检测:进入设置页时,遍历
AvailableLocales.Locales,对每个Locale调用LocalizationSettings.Tables.HasTableForLocale(locale),仅展示已准备就绪的语言。 - 智能排序:按用户设备语言优先级排序。例如设备为
zh-CN,则列表顺序为:简体中文 > English > 日本語 > 한국어,其他语言按ISO 639-1标准字母序排列。 - 异步切换:点击后,先显示
Loading...遮罩层,再调用LocalizationSettings.SetSelectedLocaleAsync(locale),完成后移除遮罩。
public async void OnLanguageSelect(Locale locale) { if (!LocalizationSettings.Tables.HasTableForLocale(locale)) { ShowToast($"语言[{locale.Identifier}]尚未支持,请稍候"); return; } loadingPanel.SetActive(true); try { await LocalizationSettings.SetSelectedLocaleAsync(locale); // 切换成功,刷新所有UI RefreshAllLocalizedUI(); ShowToast($"语言已切换为[{locale.Name}]"); } catch (Exception e) { Debug.LogError($"切换语言失败: {e}"); ShowToast("切换失败,请重试"); } finally { loadingPanel.SetActive(false); } }3.2 UI组件的自动刷新机制:Localize组件的隐藏生命周期
Localize组件是Localization插件的UI粘合剂,但它的工作方式常被误解。它不是实时监听Locale变更并重绘,而是依赖Unity的Property System和OnEnable/OnDisable生命周期。其刷新链路如下:
- 首次激活(OnEnable):组件启用时,调用
m_Localize.GetLocalizedString()获取当前Locale下的翻译,并赋值给Target(如Text.text)。 - Locale变更事件:当
LocalizationSettings.SetSelectedLocale()被调用,系统广播OnLocaleChanged事件。 - 组件响应:
Localize组件在OnLocaleChanged回调中,仅当自身处于Enabled状态时,才重新调用GetLocalizedString()并更新Target。 - 动态UI陷阱:如果某个UI是运行时Instantiate的(如背包格子),其
Localize组件在Awake()时Locale尚未初始化,OnEnable()中获取的可能是旧Locale。必须在OnEnable()里加双重检查:
// Localize.cs 的 OnEnable() 重写 protected override void OnEnable() { base.OnEnable(); // 强制刷新,确保获取最新Locale if (LocalizationSettings.IsInitialized && m_Localize != null) { UpdateString(); } }更关键的是Prefab实例化后的刷新时机。我们遇到过典型问题:主城场景里有100个NPC对话气泡,每个气泡Prefab都挂了Localize组件。当玩家切换语言后,只有屏幕内的气泡刷新,视野外的气泡仍显示旧语言。这是因为Unity的OnEnable()只在对象变为可见时触发。解决方案是:在RefreshAllLocalizedUI()方法中,主动遍历所有已加载的Localize组件并调用UpdateString():
private void RefreshAllLocalizedUI() { // 查找所有已加载的Localize组件(包括Inactive) var allLocals = Resources.FindObjectsOfTypeAll<Localize>(); foreach (var localize in allLocals) { // 即使Inactive也强制刷新,避免视野外UI滞后 localize.UpdateString(); } // 额外刷新动态生成的UI(如背包、任务列表) InventoryUI?.RefreshLocalizedText(); QuestLogUI?.RefreshLocalizedText(); }3.3 动态内容的本地化:如何让运行时生成的Text自动带翻译
游戏里大量文本是运行时拼接的,比如:“你获得了<color=yellow>{item.name} x{count}”。这类文本无法用静态Localize组件处理,必须用Runtime String Resolver。Localization插件提供了LocalizedString结构体,它是真正的“活翻译”:
// 定义一个LocalizedString字段(在Inspector里可绑定Table Entry) [SerializeField] private LocalizedString _pickupMessage; // 运行时填充参数并获取翻译 public void ShowPickupMessage(Item item, int count) { // 参数替换:{0}对应item.name,{1}对应count string message = _pickupMessage.GetLocalizedString(item.name, count); notificationText.text = message; }LocalizedString的精妙之处在于:它不存储翻译结果,只存储Table ID和Key。每次调用GetLocalizedString()时,都实时查询当前Locale下的Table Entry。这意味着:
- 如果你在运行时切换语言,所有已调用过
GetLocalizedString()的地方,下次调用会自动返回新语言翻译; - 支持嵌套参数,如
_pickupMessage.GetLocalizedString(new object[]{item.name, count, player.level}); - 参数类型安全:传入
int、float、string均可,系统自动ToString()。
但要注意一个坑:LocalizedString字段必须在Inspector里手动绑定,不能代码赋值。以下写法无效:
// ❌ 错误!代码赋值不会建立Table引用 _pickupMessage = new LocalizedString("Items", "pickup_message"); // ✅ 正确!必须在Inspector里拖入Table Entry // 或使用AssetDatabase.LoadAssetAtPath<TableEntry>(path) + SetTableAndKey()我们为动态文本建立了LocalizedTextFactory单例,封装常用模板:
public static class LocalizedTextFactory { private static readonly LocalizedString _levelUp = new("UI", "level_up"); private static readonly LocalizedString _questComplete = new("Quests", "complete"); public static string GetLevelUp(int level) => _levelUp.GetLocalizedString(level); public static string GetQuestComplete(string questName) => _questComplete.GetLocalizedString(questName); }这样业务代码只需LocalizedTextFactory.GetLevelUp(42),完全解耦Table细节。
3.4 非文本资源的本地化:Sprite、AudioClip、甚至Shader参数
Localization插件的强大之处,在于它不限于字符串。Sprite Table、AudioClip Table、Sprite Atlas Table让你能为不同语言提供专属资源:
- Sprite本地化:用于语言专属图标,如中文版“设置”按钮用齿轮图标,日文版用“設定”文字图标。创建Sprite Table后,在Inspector里为每个Locale指定Sprite。代码中用
spriteTable.GetSprite("settings_icon")获取。 - AudioClip本地化:为语音配音提供多语言支持。注意:AudioClip Table的Key必须与String Table一致,方便策划统一管理。播放时
audioSource.clip = audioTable.GetClip("dialog_hello"); audioSource.Play();。 - Shader参数本地化:较少用,但可用于动态调整UI风格。例如
ColorTable存储不同语言的主题色,colorTable.GetColor("primary_color")返回Color.red(中文)或Color.blue(英文)。
最大陷阱在于资源引用丢失。当你在Sprite Table里为zh-CN指定一个Sprite,这个Sprite必须在项目中存在,且其GUID不能因移动文件而改变。我们强制要求:所有本地化资源放入Assets/Resources/Localization/Assets/目录,并在CI流程中加入校验脚本,扫描Table中所有引用的Asset是否存在,缺失则报错阻断打包。
4. 工程化落地的三大支柱:目录结构、CI/CD集成、QA验证清单
4.1 可维护的目录结构:为什么“Resources/Localization”是唯一合法路径
Localization插件对资源路径有硬性约束,违反会导致运行时找不到Table。我们经过6个项目验证,确立了铁律式目录结构:
Assets/ ├── Resources/ │ └── Localization/ ← 必须存在,Localization系统只扫描此路径 │ ├── Tables/ ← 所有Table Asset存放处 │ │ ├── Strings/ ← 字符串表(.asset) │ │ ├── Sprites/ ← 图片表(.asset) │ │ └── Audio/ ← 音频表(.asset) │ └── Assets/ ← 所有被Table引用的资源(Sprite、AudioClip等) │ ├── zh-CN/ │ │ ├── icon_settings.png │ │ └── voice_hello.wav │ ├── ja-JP/ │ │ ├── icon_settings.png │ │ └── voice_hello.wav │ └── en-US/ │ ├── icon_settings.png │ └── voice_hello.wav ├── Scripts/ │ └── Localization/ ← 自定义工具脚本 │ ├── LocalizationManager.cs ← 封装切换逻辑 │ ├── LocalizationLoader.cs ← 异步加载工具 │ └── TableValidator.cs ← CI校验脚本 └── Editor/ └── Localization/ ← 编辑器扩展 ├── TableExporter.cs ← Excel导出工具 └── LocaleSwitcher.cs ← 编辑器快速切换这个结构的关键设计点:
Resources/Localization/是唯一可信路径:所有Table和被引用资源必须在此之下,否则LocalizationSettings.Tables.LoadTable()会返回null。- 语言子目录隔离:
Assets/下按Locale分目录,避免文件名冲突(如icon_settings.png在中日英版可能不同)。 - Scripts/Localization/封装业务逻辑:
LocalizationManager提供ChangeLanguageAsync(locale)等高层API,屏蔽底层细节。
提示:
Resources文件夹会增加Build时间,但Localization Table必须放这里。折中方案是:将Table Asset的Scripting Define Symbols设为LOCALIZATION_DEBUG,在Debug模式下保留Resources,Release模式下用Addressable替代(需额外开发Addressable Table Loader)。
4.2 CI/CD流水线中的本地化校验:3个必加的自动化检查
没有自动化校验的本地化,等于埋雷。我们在Jenkins流水线中加入了以下检查,任何一项失败即中断打包:
Table完整性检查:扫描所有String Table,确保每个Key在Base Language(en-US)中存在,且非空。缺失Key则报错:
# Python脚本片段 for table in find_string_tables(): base_entries = table.get_entries("en-US") for key in base_entries.keys(): if not key.strip(): raise ValueError(f"Table {table.name} has empty key")Locale一致性检查:验证所有Table中出现的Locale ID集合是否完全一致。例如
Strings/Table1.asset支持zh-CN, en-US, ja-JP,而Sprites/Table2.asset只支持zh-CN, en-US,则报错——这会导致日语玩家看到文字是日文,图标却是中文。资源引用检查:解析所有Table Asset的二进制数据,提取其中引用的Sprite/AudioClip路径,检查
Assets/Resources/Localization/Assets/{locale}/下是否存在对应文件。缺失则报错并列出缺失项。
这些检查在每次Git Push到develop分支时触发,平均耗时<8秒,却避免了90%的线上本地化事故。我们曾因漏掉第2项检查,导致法语版UI文字正常但按钮图标全为英文版,上线后被法国玩家集体吐槽。
4.3 QA验证清单:一份让测试工程师照着打勾的本地化验收表
再完美的技术实现,也需要QA验证。我们给测试团队提供了一份12项本地化专项检查清单,每项必须打勾通过:
| 序号 | 检查项 | 验证方法 | 通过标准 |
|---|---|---|---|
| 1 | 基础语言切换 | 设置中切换至日语→重启游戏→检查主界面 | 所有静态文本、按钮、标题均为日文,无英文残留 |
| 2 | RTL语言显示 | 切换至阿拉伯语→打开背包界面 | 文字从右向左排列,图标位置镜像,无裁切 |
| 3 | 动态文本参数 | 拾取道具,查看提示 | “获得<道具名> x<数量>”中道具名和数量正确替换,无占位符残留 |
| 4 | 语音同步 | 播放剧情语音→查看字幕 | 语音语言与字幕语言严格一致,无混搭 |
| 5 | 数字格式 | 查看金币数量(10000) | 中文显示“10,000”,日文显示“10,000”,阿拉伯语显示“١٠٬٠٠٠” |
| 6 | 日期格式 | 查看活动倒计时 | 中文“10月25日”,日文“10月25日”,英文“Oct 25” |
| 7 | 资源缺失容错 | 删除Assets/Resources/Localization/Assets/ja-JP/下所有文件→切换日语 | 界面不崩溃,自动回退到en-US,显示英文 |
| 8 | 热更新生效 | 修改Strings/UI.asset中一条Key→下发新文件→调用ReloadAllTables() | 修改立即生效,无需重启 |
| 9 | 内存占用 | Profiler中查看Localization相关内存 | Table数据加载后内存增长合理(<5MB),无内存泄漏 |
| 10 | 启动耗时 | Android真机冷启动,记录Localization初始化时间 | <300ms(中端机) |
| 11 | 多语言混合 | 中文界面中打开英文活动弹窗 | 弹窗内文字为英文,主界面保持中文,无互相污染 |
| 12 | 特殊字符 | 检查含emoji、数学符号的文本(如“胜利!🎉”) | 显示正常,无方块或乱码 |
这份清单让QA不再凭感觉测试,而是有据可依。上线前最后一轮测试,我们用此表逐项核对,3天内发现并修复了7个隐藏问题,包括一个RTL下TextMeshPro fallback字体未加载导致的乱码。
5. 我们踩过的两个血泪坑:连Unity官方文档都没写的致命细节
5.1 坑一:Addressable + Localization的Bundle加载死锁
当项目启用Addressable后,Localization Table必须被打包进Addressable Bundle。但官方文档没告诉你:如果Table Asset的Addressable Group设置为“Pack Separately”,且多个Table共享同一个Locale,会导致Bundle加载死锁。
现象:切换语言时,SetSelectedLocaleAsync()永远不返回,Profiler显示主线程卡在Addressables.LoadAssetAsync()。原因在于:Localization系统内部会并发加载所有Table,而Addressable的“Pack Separately”模式为每个Table生成独立Bundle,当网络波动时,某个Bundle加载超时,整个Locale切换流程被阻塞。
解决方案:强制所有Table Asset归属同一个Addressable Group,并设置Bundle Mode为“Pack Together”。具体操作:
- 在Addressable Groups窗口,新建Group,命名为
Localization_Tables; - 将所有Table Asset拖入此Group;
- 右键Group →
Group Settings→Bundle Mode→ 选Pack Together; - 在
Localization Settings中,勾选Use Addressables,并确保Addressable Catalog已正确设置。
血泪教训:我们曾为此问题加班48小时,最终发现Unity Addressable的
LoadDependenciesAsync()在Bundle分离模式下,对同一Locale的多个Table会发起N次独立HTTP请求,而服务器限流导致部分请求排队,形成死锁。合并Bundle后,单次请求加载全部Table,问题消失。
5.2 坑二:Editor中Locale切换不触发OnEnable,导致Preview失效
在Unity Editor里,你可以通过Localization Window → Locale Selector快速切换Locale预览效果。但你会发现:已经打开的Inspector窗口里的Localize组件,其显示的文本不会实时更新!必须手动点击Refresh按钮。
原因:Editor的Locale切换不触发OnLocaleChanged事件,它只修改LocalizationSettings.SelectedLocale的值,而Localize组件的OnEnable()在Editor中不会因Locale变更而重调。
解决方案:编写Editor脚本,监听Locale变更并强制刷新:
[InitializeOnLoad] public static class LocalizationEditorHook { static LocalizationEditorHook() { EditorApplication.update += CheckLocaleChange; } private static void CheckLocaleChange() { // 缓存上一次Locale static Locale lastLocale; if (LocalizationSettings.SelectedLocale != lastLocale) { lastLocale = LocalizationSettings.SelectedLocale; // 强制刷新所有Localize组件的Inspector显示 var locals = Resources.FindObjectsOfTypeAll<Localize>(); foreach (var local in locals) { EditorUtility.SetDirty(local); } } } }这段代码放在Editor/目录下,Unity启动时自动注册。它会在每次Editor更新时检查Locale是否变化,变化则标记所有Localize组件为dirty,触发Inspector重绘。从此,Editor里切换Locale,所有UI预览实时同步。
最后分享一个小技巧:在Localization Settings的Editor Settings里,勾选Show Warnings In Console,并设置Warning Level为All。这样,当Table Key重复、Locale缺失、资源路径错误时,控制台会输出红色警告,而不是静默失败。我们靠这个功能,在打包前就揪出了80%的配置错误。
