资讯动态

ET框架多语言方案:从配置表到运行时完整实现

发布时间:2026/10/20 8:59:53 来源:尧图企业网站定制
1. 为什么游戏项目到了中期才想起做多语言往往已经晚了做Unity项目的人大多有过这种经历游戏在国内跑通了发行方突然说想上几个海外地区于是团队火急火燎地开始做多语言。结果一打开工程发现UI预制体里全是硬编码的中文字符串策划配表里文本和逻辑混在一起连个统一的文本入口都没有。这时候再回头做国际化工作量几乎等于把整个项目重写一遍。我自己参与过一个中型项目早期没人考虑多语言等到要出海的时候光是梳理散落在各个Prefab、ScriptableObject和代码里的文本就花了两周。更麻烦的是有些文本是运行时拼接出来的比如获得{0}个{1}这种翻译的时候语序完全对不上。所以多语言这件事越早规划成本越低而Unity生态里能把这套东西做扎实的框架并不多ET框架算是其中一个比较有代表性的选择。这篇内容主要聊的是在ET框架下怎么把多语言这套体系搭起来。ET本身是一套基于C#的双端框架客户端和服务端共用一套逻辑代码它的资源管理和配置体系有自己的设计哲学。多语言在ET里不是一个孤立的模块它跟配置表、资源加载、UI组件都有耦合关系。我会从文本的存储结构、运行时切换机制、配置表集成、资源本地化这几个维度展开把踩过的坑和验证过的方案都摊开讲。不管你是刚接触ET还是已经在用ET做项目但还没碰多语言这篇内容应该都能给你一些可以直接抄的参考。2. 多语言在ET框架里的落点文本该存在哪谁来管2.1 硬编码文本的三种常见藏身之处在动手做多语言之前得先搞清楚项目里的文本到底藏在哪些地方。我梳理过自己经手的几个项目文本来源基本逃不出这三类第一类是UI预制体上的静态文本。Unity的Text或TMP_Text组件上直接填的中文这类文本最容易被发现但也最容易被遗漏因为预制体多了之后没人记得哪个改过哪个没改。第二类是配置表里的文本字段。ET框架有一套自己的配置表体系通常用Excel或类似工具导出成二进制或JSON策划在里面填的各种名称、描述、对话内容都属于这一类。这类文本量最大也最需要统一管理。第三类是代码里动态拼接的文本。比如提示信息、格式化字符串、错误码对应的描述等。这类文本最隐蔽因为它们在代码里以字符串字面量的形式存在不做全局搜索根本找不到。我的经验是在项目初期就约定一条规则任何面向玩家的文本都不允许直接写在Prefab或代码里必须走统一的文本获取接口。这条规则执行得越早后期做多语言的成本就越低。2.2 ET框架下文本管理的核心思路ET框架本身并没有内置一套完整的多语言方案但它提供了足够的基础设施让你去搭。核心思路可以概括为文本与逻辑分离语言与资源解耦。具体来说文本不应该以字面量的形式出现在任何逻辑代码或UI组件里而是通过一个唯一的Key去获取。这个Key对应的文本内容根据当前语言环境从不同的数据源加载。ET的配置表体系天然适合做这件事你可以建一张多语言配置表结构大概是这样的字段名类型说明Idint文本唯一标识Keystring文本键名如login_titleChinesestring中文文本Englishstring英文文本Japanesestring日文文本...string其他语言这张表在ET的配置体系里就是一个普通的配置类导出后可以通过框架的配置管理模块加载。运行时根据当前语言设置从对应的字段取值。但这里有个问题如果语言种类很多这张表会变得非常宽每加一种语言就要改表结构、重新导出。更灵活的做法是把语言维度拆出来做成两张表一张是文本Key表一张是翻译内容表通过Key和Language两个维度来索引。这样加语言只需要加数据不需要改结构。2.3 为什么我不建议用Unity自带的Localization包Unity官方有一个Localization包功能挺全的支持文本、资源、字体等多维度的本地化。但在ET框架的项目里我不太推荐直接用它原因有几个一是双端一致性问题。ET是双端框架服务端也需要处理多语言文本比如给玩家发邮件、返回错误提示等。Unity的Localization包是客户端专属的服务端用不了。如果客户端和服务端各用一套多语言方案维护成本会翻倍。二是配置体系冲突。ET有自己的配置表加载和管理机制如果引入Unity的Localization相当于在项目里跑了两套配置体系资源加载路径、生命周期管理都会变得混乱。三是热更新友好度。ET框架对热更新有比较好的支持配置表可以通过热更包更新。而Unity的Localization包在热更场景下的表现需要额外处理不如直接用ET的配置体系来得直接。所以我的建议是在ET项目里做多语言优先考虑基于ET配置表自建一套轻量方案而不是引入外部包。这套方案的核心组件其实不多一个文本管理类、一张配置表、一个语言切换入口再加上UI组件的自动绑定机制基本就能覆盖大部分场景。3. 从配置表到运行时多语言文本的完整加载链路3.1 配置表的设计与导出先看配置表怎么设计。前面提到了两张表的方案这里展开说一下具体结构。文本Key表TextKeyConfig负责定义所有文本的唯一标识和分组信息字段名类型说明Idint自增主键Keystring文本键名全局唯一Groupstring分组如UI、Item、SkillCommentstring备注给策划看的翻译内容表TextTranslationConfig负责存储每种语言的具体内容字段名类型说明Idint自增主键TextKeyIdint关联TextKey表的IdLanguagestring语言标识如zh、en、jaContentstring翻译内容这种设计的好处是加语言只需要在翻译表里加数据不需要动表结构。导出的时候ET的配置导出工具会把这两张表分别导出成配置类运行时通过框架的ConfigComponent加载。这里有个细节要注意翻译内容表的数据量可能会很大如果每种语言都全量加载到内存对内存的压力不小。我的做法是只加载当前语言的翻译数据切换语言时重新加载。ET的配置加载支持按条件过滤可以在加载时传入当前语言参数只拉取对应语言的数据。3.2 运行时文本管理类的实现配置表准备好之后需要一个运行时管理类来统一处理文本获取。这个类我一般叫它TextManager核心职责有三个加载配置、根据Key和当前语言返回文本、处理文本格式化。public static class TextManager { private static Dictionarystring, string _textDict new Dictionarystring, string(); private static string _currentLanguage zh; public static void Init(string language) { _currentLanguage language; LoadTextConfig(); } private static void LoadTextConfig() { _textDict.Clear(); var configs ConfigComponent.Instance.GetAllTextTranslationConfig(); foreach (var config in configs) { if (config.Language _currentLanguage) { var keyConfig ConfigComponent.Instance.GetTextKeyConfig(config.TextKeyId); if (keyConfig ! null) { _textDict[keyConfig.Key] config.Content; } } } } public static string Get(string key, params object[] args) { if (!_textDict.TryGetValue(key, out var text)) { Log.Warning($Text key not found: {key}); return key; } if (args ! null args.Length 0) { return string.Format(text, args); } return text; } public static void SwitchLanguage(string language) { _currentLanguage language; LoadTextConfig(); EventSystem.Instance.Publish(new LanguageChangedEvent()); } }这段代码有几个关键点值得说明。第一文本字典用Key作为索引查找是O(1)的性能没问题。第二找不到Key的时候返回Key本身而不是空字符串这样在开发阶段能快速定位缺失的翻译。第三切换语言后发了一个事件UI层可以监听这个事件来刷新显示。3.3 语言切换时的UI刷新机制语言切换不是简单地把字典换掉就完事了界面上已经显示出来的文本需要同步更新。ET框架有完善的事件系统这里正好用得上。我的做法是给所有需要响应语言切换的UI组件挂一个统一的脚本这个脚本在OnEnable时注册语言切换事件在OnDisable时反注册。事件触发时重新调用TextManager.Get获取最新文本并赋值给TMP_Text组件。public class LocalizedText : MonoBehaviour { public string TextKey; private TMP_Text _text; private void Awake() { _text GetComponentTMP_Text(); } private void OnEnable() { Refresh(); EventSystem.Instance.SubscribeLanguageChangedEvent(OnLanguageChanged); } private void OnDisable() { EventSystem.Instance.UnsubscribeLanguageChangedEvent(OnLanguageChanged); } private void OnLanguageChanged(LanguageChangedEvent evt) { Refresh(); } private void Refresh() { if (_text ! null !string.IsNullOrEmpty(TextKey)) { _text.text TextManager.Get(TextKey); } } }这个组件替代了直接往TMP_Text上填文本的做法。策划或UI在预制体上只需要填一个TextKey文本内容由运行时决定。这样做还有一个额外的好处如果某个Key在配置表里改了内容不需要重新打预制体只需要更新配置表就行。注意这个组件在OnEnable时会调用一次Refresh这意味着每次界面打开都会重新取一次文本。对于频繁打开关闭的界面可以考虑加一个缓存机制避免重复查找。不过实测下来字典查找的开销非常小除非是极端情况否则不需要过早优化。4. 资源本地化图片、字体和音频怎么跟着语言走4.1 图片资源的语言适配文本搞定了接下来是资源。游戏里有很多带文字的图片比如Logo、按钮底图、活动横幅等。这些图片在不同语言下需要替换成对应版本。ET框架的资源管理是基于地址寻址的资源路径可以动态拼接。我的做法是在资源命名上约定一个规则需要本地化的资源在文件名后面加上语言标识。比如login_banner_zh.png、login_banner_en.png。加载的时候根据当前语言拼接路径public static string GetLocalizedAssetPath(string basePath) { string language TextManager.CurrentLanguage; string localizedPath ${basePath}_{language}; if (ResourceComponent.Instance.Exists(localizedPath)) { return localizedPath; } return basePath; }这段逻辑的意思是先尝试加载带语言后缀的资源如果不存在就回退到默认资源。这样对于没有做本地化版本的资源不需要额外处理直接用默认的就行。4.2 字体资源的处理字体是资源本地化里比较容易被忽略的一块。中文字体文件通常很大一个完整的中文字体动辄十几MB如果每种语言都打包一套完整字体包体会膨胀得很厉害。我的建议是按语言拆分字体资源。中文用一套字体英文和欧洲语言用另一套通常体积小很多日文、韩文各自用对应的字体。ET框架支持资源的分组加载可以把不同语言的字体放到不同的资源组里根据当前语言动态加载对应的字体组。具体实现上可以在TMP的FontAsset上做文章。TMP支持FontAsset的Fallback机制可以设置一个主字体和多个备选字体。当主字体里找不到某个字符时会自动从备选字体里找。利用这个机制可以把常用字符放在主字体里生僻字符或特定语言的字符放在Fallback字体里。实测经验中文字体的优化空间很大。如果游戏里的中文文本量不是特别大可以用字体子集化工具只保留用到的字符能把字体文件从十几MB压到一两MB。这个优化在出海项目里效果非常明显。4.3 音频资源的语言版本管理音频的本地化相对简单主要是配音。如果游戏有角色配音不同语言需要不同的语音包。处理思路和图片类似按语言分目录存放加载时根据当前语言选择对应的目录。但音频有一个特殊之处语音和文本的时长可能不一致。中文一句话可能两秒说完英文可能要三秒日文可能两秒半。如果UI上有语音播放的进度条或者字幕同步需要考虑到这个差异。我的做法是在配置表里给每条语音标注时长UI根据实际时长来调整显示节奏。5. 踩坑记录多语言方案落地时最容易出问题的几个环节5.1 文本拼接的语序陷阱这是多语言里最经典的坑。中文说获得3个金币英文是Obtained 3 Gold日文是金貨を3個獲得。如果代码里写成获得 count 个 itemName翻译的时候根本没法处理。正确的做法是用占位符。配置表里存的是获得{0}个{1}运行时用string.Format填充。这样翻译人员可以根据目标语言的语序自由调整占位符的位置。英文可以写成Obtained {0} {1}日文可以写成{1}を{0}個獲得。但占位符也有坑。有些语言的复数形式很复杂比如英文的1 item和2 items俄语更复杂有单数、少数、多数好几种形式。如果项目要支持这些语言简单的占位符就不够了需要引入复数规则处理。不过对于大多数项目来说如果目标语言不涉及复杂的复数变化占位符方案已经够用了。5.2 配置表导出的编码问题ET的配置表导出工具默认可能用的是某种编码格式如果翻译内容里包含特殊字符比如日文的假名、韩文的谚文、欧洲语言的重音符号导出后可能会出现乱码。我遇到过一次翻译表里填了法文导出后é变成了é。排查了半天发现是导出工具在写文件时用了错误的编码。解决办法是在导出配置里显式指定UTF-8编码并且在读取的时候也用UTF-8。这个问题在不同版本的ET框架里表现可能不一样建议在项目初期就用多语言文本做一次导出测试确认编码没问题。5.3 语言切换后的界面残留语言切换后大部分文本会通过事件刷新但有些地方容易遗漏已经打开的弹窗如果弹窗是在语言切换前打开的且没有监听语言切换事件里面的文本不会更新。滚动列表里的项列表项通常是复用的如果复用的时候没有重新取文本会显示旧语言的文本。缓存的数据有些界面会把文本缓存到本地变量里语言切换后缓存没有失效。我的处理方式是语言切换事件触发后除了刷新当前显示的文本还会强制关闭所有非核心弹窗让玩家重新打开。这样虽然体验上稍微有点突兀但能确保不会出现中英混杂的情况。对于滚动列表在Item的刷新逻辑里强制重新取文本不依赖缓存。5.4 服务端文本的同步问题ET是双端框架服务端也会产生面向玩家的文本比如邮件内容、排行榜名称、错误提示等。这些文本的多语言处理需要特别注意。我的做法是服务端只返回文本Key和参数不返回具体文本。客户端收到Key和参数后用本地的TextManager获取对应语言的文本并格式化。这样做的好处是服务端不需要关心玩家用什么语言也不需要存储多语言文本逻辑更干净。但有些场景下服务端必须返回具体文本比如玩家昵称、公会名称这种用户生成内容。这类文本不做翻译直接原样返回。需要在UI上做好区分避免把用户生成内容也当成可翻译文本去处理。6. 一套可复用的多语言接入流程6.1 项目初期的接入清单如果你正在启动一个新项目打算用ET框架做多语言可以按这个清单来建立文本规范约定所有面向玩家的文本必须通过TextManager.Get获取禁止硬编码。创建配置表建TextKey和TextTranslation两张表定义好字段结构。实现TextManager包括配置加载、文本获取、语言切换、事件通知。封装LocalizedText组件替代直接往TMP_Text填文本的做法。约定资源命名规则需要本地化的资源加语言后缀加载时动态拼接。字体方案确定确定主字体和Fallback字体的策略做好子集化优化。服务端文本规范约定服务端返回Key参数的模式不返回具体文本。这套流程走下来多语言的基础设施就搭好了。后续加语言只需要在配置表里加数据不需要改代码。6.2 中期项目的补救措施如果项目已经做了一半之前没考虑多语言现在要补工作量会大很多但也不是没法做。我的建议是分步走第一步全局搜索硬编码文本。用IDE的搜索功能把所有中文字符串找出来逐个替换成TextManager.Get调用。这一步最耗时但必须做。第二步梳理配置表。把配置表里所有面向玩家的文本字段找出来迁移到多语言配置表里。可以写个脚本做批量迁移减少手工操作。第三步处理动态拼接。把代码里所有字符串拼接的地方找出来改成占位符格式。这一步需要仔细因为拼接逻辑往往散落在各处。第四步UI组件替换。把预制体上的TMP_Text组件替换成LocalizedText组件填上对应的TextKey。这个过程大概会占到项目总工作量的10%到20%取决于项目规模和文本量。所以还是那句话多语言越早做越好。6.3 翻译团队协作的注意事项多语言离不开翻译。和翻译团队协作的时候有几个点需要提前对齐提供上下文光给一个Key和原文翻译人员可能不知道这个词用在什么场景。最好在配置表里加一个备注字段说明这个词的用途、语气、字数限制等。约定占位符格式告诉翻译人员占位符不能改、不能删、不能调换顺序除非目标语言需要。控制文本长度不同语言的文本长度差异很大中文四个字翻译成英文可能要十几个字符。UI上要预留足够的空间或者在配置表里标注字数限制。版本管理翻译内容会不断更新需要有一套版本管理机制确保翻译团队拿到的是最新的文本Key列表翻译完成后能正确合并回配置表。我自己的做法是每次导出翻译任务时附带一个变更说明列出新增、修改、删除的Key。翻译团队只需要处理变更部分不需要每次全量翻译。这样效率高很多也不容易出错。7. 一些让多语言更好维护的工程习惯7.1 Key的命名规范TextKey的命名看起来是小事但Key多了之后命名混乱会让人非常头疼。我一般遵循这样的规范用英文小写字母加下划线如login_title、item_desc_1001。按模块加前缀如ui_、item_、skill_、mail_。动态内容的Key用Id做后缀如item_name_1001、skill_desc_2003。避免用拼音避免用缩写保证Key本身有可读性。这样命名之后看到Key大概能猜到它的用途查找和排查都方便很多。7.2 缺失翻译的检测机制项目大了之后难免会有Key在配置表里漏了翻译。如果等到运行时才发现可能已经上线了。我的做法是在打包前加一个检测步骤遍历所有TextKey检查每种目标语言是否都有对应的翻译内容。缺失的Key输出到日志里让策划补全。这个检测可以写成一个编辑器工具在CI流程里自动执行。检测不通过就不允许打包从流程上保证翻译的完整性。7.3 语言包的按需加载如果游戏支持的语言很多把所有语言的翻译数据都加载到内存里是浪费。我的做法是只加载当前语言的翻译数据切换语言时重新加载。ET的配置加载支持条件过滤实现起来不难。对于资源图片、字体、音频也是同样的思路。不同语言的资源放在不同的资源组里根据当前语言动态加载对应的资源组。ET的资源管理支持资源组的引用计数和卸载切换语言时把旧语言的资源组卸载掉释放内存。7.4 测试环节的语言覆盖多语言功能的测试容易被忽略。我的建议是在测试计划里明确加入多语言相关的用例切换每种支持的语言检查所有界面的文本是否正确显示。检查是否有文本溢出、截断、重叠的情况。检查资源是否正确替换有没有加载到错误语言的资源。检查服务端返回的文本是否正确显示。检查语言切换后已打开的界面是否正确刷新。这些用例看起来简单但实际执行的时候往往能发现不少问题。尤其是文本溢出英文和德文的文本通常比中文长很多UI上很容易出问题。8. 关于多语言方案选型的一点个人看法做了几个出海项目之后我对多语言方案的选择有一些自己的体会。ET框架下自建多语言方案前期确实需要投入一些开发成本但后期的可控性和维护性是最好的。你完全掌控文本的存储结构、加载时机、切换逻辑遇到问题也能快速定位和修复。如果项目规模很小文本量不大也可以考虑更轻量的方案比如直接用JSON文件存翻译运行时加载。但如果项目有一定规模或者有双端需求我还是建议走配置表这条路。ET的配置体系已经帮你处理了加载、缓存、热更这些底层问题你只需要在上面搭一层文本管理逻辑就行。另外多语言不只是技术问题更是流程问题。技术方案再好如果策划不配合、翻译流程不顺畅、测试不覆盖最后还是会出现各种问题。所以我的经验是多语言这件事要从项目管理的层面去推动技术方案只是其中一环。把规范定好、流程跑通、工具到位剩下的就是执行了。最后分享一个我一直在用的小技巧在开发阶段可以加一个快捷键快速切换语言。这样在调试UI的时候可以随时切换语言看效果不用每次都去设置界面里改。这个快捷键只在编辑器下生效打包后自动禁用。实现起来很简单在Update里检测按键调用TextManager.SwitchLanguage就行。别小看这个功能它能帮你省下大量来回切换的时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑