十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

游戏场景一键翻译:从文本提取到引擎回写的本地化实践

游戏场景一键翻译:从文本提取到引擎回写的本地化实践 在实际游戏研发和本地化工作中“游戏场景翻译”从来都不是把 Excel 里的词条复制一遍再粘回去这么简单。真正麻烦的是场景里的对话文本、NPC 名称、任务描述、UI 提示、动画事件里挂的字符串它们分散在预制体、场景文件、表格和脚本资源中。如果团队使用的是 Unity 或 Unreal 这类商业引擎场景内文本往往还和资源 GUID、组件引用、蓝图节点绑定在一起人工逐条替换既慢又容易漏还会在回写时破坏资源引用。这里要解决的问题就是“整个游戏场景也能一键翻译导回引擎直接用”这件事背后的一套可控流程先把场景里的字符串标准化提取出来交给翻译服务处理再按原有资源结构写回最后在引擎里验收。这篇文章会围绕一条主线展开从场景文本的分布形态开始讲清楚为什么“一键翻译”不是简单替换文本然后给出一个基于 Python JSON 资源清单 翻译 API 的最小示例覆盖文本提取、去重、翻译、回写、验证五个环节。中间会给出参数说明、常见坑、排查链路和发布前验收清单。适合负责游戏本地化工具开发、引擎工具链建设、海外版本上包的开发者和技术策划阅读。学完后你可以把同一套思路迁移到 Unity、Unreal 或自研引擎的资源管线中不再手工处理场景翻译。1. 先理解游戏场景翻译为什么不能靠“截图 人工抄写”1.1 翻译对象不仅是 UI而是整个场景很多刚接触本地化的同学会以为翻译游戏就是把界面上的“Start”“Options”“Quit”换成对应语言。但放到“整个游戏场景”这个范围时翻译对象会扩大很多场景中挂在 3D 物体上的文本组件例如告示牌、门牌、任务目标文字。对话系统里的角色台词可能存储在场景对象上也可能引用外部对话表格。任务系统的标题、描述、完成条件提示可能由任务配置表驱动。音频、动画事件里挂的字幕 key 或文本内容。场景内可交互物品的名称和描述文本。UI 预制体里的文本、占位符、格式化字符串。这些文本的存储位置不同读取时机不同显示方式也不同。如果要做到“整个游戏场景一键翻译”必须先有能力枚举出这些文本而不是只处理 UGUI 或 UMG 的 Text 控件。1.2 一键翻译的本质可寻址文本提取 回写“一键翻译”从工程角度拆解其实是“可寻址文本提取”和“可寻址回写”的组合。可寻址的意思是每一段文本都能定位到它所属的资源文件、字段位置和读取路径。只有把文本变成可寻址数据翻译完成后才能精确写回。如果只是把场景文件导成文本再批量替换翻译后极容易出现三类问题文本内容被替换但字体或文本组件配置没变新语言显示为方块。翻译后字符串长度变化UI 布局溢出。回写时破坏了引擎的序列化结构导致场景无法加载或资源引用丢失。所以完整流程必须保留一个“中间翻译表”。这个表不直接改引擎场景而是记录每条文本在资源中的定位信息翻译完成后按定位信息回写。翻译服务只负责语言转换工程脚本负责定位和回写两者解耦。1.3 一次翻译流程的完整链路假设输入是一个 Unity 场景文件或 Unreal 的 Map 资源下面是一条比较常见的完整链路资源扫描读取场景文件、预制体、DataTable、对话资源提取所有需要翻译的文本。文本规范化去除空格差异统一换行符拆分带参数的格式化字符串。生成中间表生成 JSON 或 CSV每条记录包含 sourceText、textKey、资源路径、字段路径。翻译处理调用翻译服务或交由人工翻译得到 targetText。回写资源根据中间表里的资源路径和字段路径把翻译结果写回场景文件。引擎校验打开引擎检查场景是否正常加载文本是否显示正确引用是否完整。导出报告标记漏译、超长、含非法字符的条目进入下一轮修复。这条链路里最关键的是第 1 步和第 5 步。文本提取做得不完整后面翻译表再准确也没有意义回写逻辑不稳定场景资源可能直接损坏。注意不要只验证程序能启动还要验证输入、输出、异常分支和日志是否符合预期。场景翻译工具最容易出现“运行没报错但场景里某些文本仍然是原文”的情况。2. 环境准备与资源检查清单2.1 引擎版本与文本来源的匹配关系开始写脚本前先确认目标引擎的版本和文本存储格式。Unity 3D 与 Unreal Engine 在“场景文本”上差异很大。引擎场景文本主要位置常见资源格式回写风险点UnityScene 文件、Prefab、ScriptableObject、TextMeshPro 资源、DataTableYAML 序列化文本GUID 引用、MonoBehaviour 字段映射UnrealMap、UMG、DataTable、String Table、蓝图字符串.umap、.uasset、.json资产导入器版本、FText 的命名空间与 key自研引擎自定义场景格式、Excel、JSON 配置自定义二进制或 JSON二进制结构不稳定需强类型解析如果原始项目没有明确资源格式建议先从“最容易拿到文本的格式”入手。Unity 的 .unity 和 .prefab 在文本模式 YAML 下可以直接用脚本读取Unreal 的 .umap 是二进制资产通常不建议直接改原文件而是通过 Unreal 的本地化 Dashboard 或 String Table 完成翻译。2.2 文本资源存在哪几种形态同一个场景里的文本不一定只存在于场景文件中。项目越大文本越倾向于外置到配置表或本地化资源中。做一键翻译前先盘点文本的四种形态硬编码在场景组件中的文本例如 Unity Text 组件的 Text 属性、TMP 的 text 属性。这种文本提取直接但回写要小心。通过 key 引用的本地化文本场景里存的是quest_001_title翻译表里存的是多语言文本。这种场景“需要翻译的是翻译表而不是场景文件”。配置表驱动的文本任务表、对话表、物品表里的文本。场景只是运行时读取真正翻译对象在配置表。代码或蓝图中的格式化字符串包含{0}、{name}等占位符。这类字符串不能整体直译需要先拆模板再翻译。针对这四种形态常见项目会采用不同的处理策略。硬编码场景文本适合“直接回写方案”key 引用文本适合“翻译表方案”配置表驱动文本适合“批量表格翻译方案”格式化字符串则必须保留占位符。2.3 环境准备清单在写一键翻译脚本前按下面的清单检查环境。如果哪一项缺失后面一定会花时间补。- Python 3.8 或 Node.js 16用于编写批处理工具 - 目标引擎编辑器可打开项目并能在命令行下执行批处理 - 场景文件或配置表的可读副本避免直接操作正在使用的版本 - 翻译 API 的访问密钥或本地翻译词库文件 - Git 或 SVN用于翻译前后的版本对比和回滚 - 字体资源确认目标语言是否已有对应字体如果使用翻译 API还要确认 API 的地区、域名、QPS 限制和计费方式。学习阶段可以先用少量文本测试不要一次性提交整个场景的全部文本。3. 实操一键翻译脚本的完整示例下面这个示例以 Unity 的 YAML 场景文件和 JSON 中间表为例演示“提取文本 - 翻译 - 回写”的最小闭环。实际项目里你可以把读取和写入部分替换成自己的资源格式。3.1 项目结构和准备工作localization-tool/ ├── scenes/ │ └── Level1.unity ├── output/ │ ├── extraction.json │ └── translation.json ├── translate.py └── requirements.txtrequirements.txt只需要一个 HTTP 客户端库例如requestsrequests2.25.0安装命令pip install -r requirements.txt如果你的项目不允许使用第三方库Python 标准库的urllib也够用。下面的代码默认使用requests因为它处理 JSON 和 HTTP 错误更清晰。3.2 文本提取与去重合并Unity 的 .unity 文件是 YAML 文本。场景中挂在 GameObject 上的 MonoBehaviour 组件会把可序列化字段以缩进块的形式写在 YAML 里。这里的关键思路是不需要解析完整 Unity YAML只要按规则找出m_Text:一类字段再把它们和资源路径绑定即可。import json import re SCENE_FILE scenes/Level1.unity TEXT_FIELD_PATTERNS [ re.compile(rm_Text:\s*(.*)$), re.compile(rtext:\s*(.*)$), ] def extract_texts(path): entries [] with open(path, r, encodingutf-8) as f: lines f.readlines() for idx, line in enumerate(lines): for pattern in TEXT_FIELD_PATTERNS: match pattern.search(line) if match: raw_text match.group(1).strip().strip() if not raw_text: continue # 这里用行号作为粗粒度的定位信息实际项目建议记录组件路径 entries.append({ sourceText: raw_text, file: path, line: idx 1, field: m_Text, textKey: f{path}:{idx 1}:{line.strip()} }) return entries entries extract_texts(SCENE_FILE) # 去重时保留第一条位置信息 unique_entries {} for e in entries: key e[sourceText] if key not in unique_entries: unique_entries[key] e with open(output/extraction.json, w, encodingutf-8) as f: json.dump(list(unique_entries.values()), f, ensure_asciiFalse, indent2) print(fextracted {len(entries)} entries, unique {len(unique_entries)})这段代码解决的问题是“文本在哪一行”。用行号定位虽然简单但在场景文件被修改后容易失效。更稳妥的做法是记录 GameObject 路径例如GameObject/Canvas/StartButton/Text回写时再根据路径重新定位。为了示例清晰这里用了行号实际项目建议改成对象路径定位。3.3 调用翻译服务并生成双语对照表翻译 API 的服务商和参数各不相同这里用通用示例。调用前需要把待翻译文本过滤一遍去掉占位符、变量名和不需要翻译的内容。import requests import json API_URL https://api.example.com/translate API_KEY your-api-key def translate_text(text, target_langzh-CN): resp requests.post( API_URL, headers{Authorization: fBearer {API_KEY}}, json{ text: text, source: en, target: target_lang, }, timeout10, ) resp.raise_for_status() data resp.json() return data[translatedText] with open(output/extraction.json, r, encodingutf-8) as f: entries json.load(f) for entry in entries: entry[targetText] translate_text(entry[sourceText]) with open(output/translation.json, w, encodingutf-8) as f: json.dump(entries, f, ensure_asciiFalse, indent2)这段代码的问题在于逐条请求文本量大时耗时会很长。实际项目中要加批量接口、失败重试、并发控制和缓存。下面第 4 节会给出参数选择建议。3.4 写回场景并校验 key写回是风险最高的一步。示例是按行匹配原始文件名和行号把m_Text:后面的内容替换成翻译结果。为了保护原文件先复制一份再写临时文件最后替换。import json import shutil TRANSLATION_FILE output/translation.json SCENE_FILE scenes/Level1.unity BACKUP_FILE scenes/Level1.unity.bak def write_back(path, translations): with open(path, r, encodingutf-8) as f: lines f.readlines() # 按文件名 行号建立索引 trans_by_line {} for t in translations: if t[file] path: trans_by_line[t[line]] t[targetText] modified False for idx, line in enumerate(lines, start1): if idx in trans_by_line: lines[idx - 1] f m_Text: {trans_by_line[idx]}\n modified True if modified: with open(path, w, encodingutf-8) as f: f.writelines(lines) return modified shutil.copyfile(SCENE_FILE, BACKUP_FILE) with open(TRANSLATION_FILE, r, encodingutf-8) as f: translations json.load(f) changed write_back(SCENE_FILE, translations) print(ftranslated {len(translations)} entries, scene changed: {changed})回写后要做两件事第一确认行号前后偏差没有导致错误替换第二用引擎打开场景检查是否有 YAML 解析错误。因为场景文件改动后行号可能变化所以真实项目更推荐用“对象路径 属性名”定位。3.5 一键执行入口把上面的三个步骤串起来就是“一键翻译”的最小形态python translate.py --scene scenes/Level1.unity --lang zh-CN --dry-runtranslate.py中的主流程如下import argparse import json from extract import extract_texts from translate import translate_entries from writeback import write_back def main(): parser argparse.ArgumentParser() parser.add_argument(--scene, requiredTrue) parser.add_argument(--lang, defaultzh-CN) parser.add_argument(--dry-run, actionstore_true) args parser.parse_args() entries extract_texts(args.scene) unique_entries deduplicate(entries) translated translate_entries(unique_entries, args.lang) if args.dry_run: with open(output/preview.json, w, encodingutf-8) as f: json.dump(translated, f, ensure_asciiFalse, indent2) else: write_back(args.scene, translated) if __name__ __main__: main()--dry-run这个参数非常有用。它只生成翻译预览文件不修改场景。正式回写前先跑一遍 dry-run 人工检查能避免很多低级错误。注意不要把 dry-run 当成可选优化。即使脚本逻辑再简单回写场景前也要先预览 diff确认没有把不需要翻译的文本替换掉。4. 关键配置与参数选择4.1 翻译服务参数翻译 API 的参数直接决定结果质量。常见配置项包括源语言、目标语言、领域词库、是否保留 HTML 标签、是否拆分长句。参数示例值作用配置错误的表现source_langen源语言代码语言识别错误部分文本不翻译target_langzh-CN目标语言代码输出为错误语言glossary_idgame_ui游戏领域词库 ID专有名词不统一preserve_tagstrue保留 等富文本标签标签被翻译或破坏formathtml / text输入文本格式类型富文本内容被错误处理timeout10单次请求超时长文本频繁超时其中preserve_tags在游戏文本里特别重要。Unity 的 TextMeshPro 支持富文本标签例如colorredWarning/color。如果翻译服务不理解这些标签可能会把标签内容也翻译成中文回写进场景后显示就会异常。4.2 批处理与并发参数场景文本数量可能是几千条。逐条请求不仅慢还会触发 QPS 限制。批处理参数建议如下参数建议值说明batch_size50 到 100 条一次请求提交多条文本max_retries3 次网络抖动或 429 限流时重试retry_backoff1 秒、2 秒、4 秒指数退避避免持续打满服务concurrency5 到 10并发请求数按 API 配额调整rate_limit20 QPS不要超过服务商限制如果并发设置过高会看到大量 HTTP 429 或 5xx 错误。此时不是代码有 bug而是请求频率超过限制建议降低 concurrency。4.3 回写模式参数回写时建议提供三种模式而不是只有一个“直接写文件”dry-run只生成翻译预览不改场景。write-back把翻译结果写入场景。report-only只生成漏译、超长、错误报告。三种模式的差异如下模式是否改文件适用场景注意事项dry-run否提交翻译前检查检查占位符是否保留write-back是小范围场景试翻回写前备份原文件report-only否大版本批量翻译用于统计翻译完成度生产环境建议先 report-only再 dry-run最后 write-back。每一步都要生成日志文件方便定位是哪条文本导致问题。5. 运行验证从中间产物到引擎内预览5.1 三种验证方式脚本跑完后不能只看控制台输出“translated successfully”。按下面三个层次验证结果。第一层中间表验证。打开translation.json检查 sourceText 和 targetText 是否一一对应占位符是否还在。{ sourceText: Press {0} to open, targetText: 按 {0} 打开, file: scenes/Level1.unity, line: 1024, textKey: scenes/Level1.unity:1024 }第二层diff 验证。用 Git 或 SVN 查看场景文件改动确认只有预期文本发生变化。git diff scenes/Level1.unity第三层引擎内预览。打开编辑器加载被翻译的场景运行到对应关卡检查显示、字体、排版和动态拼接的文本。5.2 需要检查的六类问题场景翻译的验证不是“看一遍有没有中文”。按下面的表格逐项检查。检查项检查方法问题示例资源完整性引擎是否报错、场景是否能打开回写后 YAML 缩进错误文本完整性搜索是否还有遗漏的英文关键词对话文本漏译占位符运行时是否出现 {0} 未替换格式化字符串被破坏布局适配目标语言文本是否超出边界中文变长导致按钮文字溢出字体支持是否出现方块或问号字体缺少目标语言字符特殊字符是否出现转义异常、换行异常引号、换行符被错误处理这六类问题中“资源完整性”优先级最高。如果场景文件已经被破坏其他检查都没有意义。建议在回写后立即打开引擎先确认场景能加载再进入运行态检查文本效果。6. 常见问题与排查路径6.1 文本提取为空或缺失现象脚本执行后extraction.json里只有几条数据或者完全没有数据。可能原因和排查顺序文本字段名与正则不匹配。Unity 不同版本可能使用m_Text、m_TextComponent.text或 TMP 的m_text。场景文件编码不是 UTF-8部分文本读取异常。文本存储在外部配置表中场景文件内部没有可提取内容。脚本读取的是.meta文件或文本资源副本而不是真实 Scene。检查方式grep -n m_Text scenes/Level1.unity | head -n 20如果 grep 能看到m_Text而脚本提取不到优先检查正则和缩进。如果 grep 看不到任何结果说明该场景的文本不是常规字段需要换到预制体、配置表或对话资源中去提取。6.2 回写后引擎里没变化现象翻译结果已经写在 JSON 里但打开引擎后场景仍然显示原文。可能原因场景文件没有被引擎重新导入和加载。译员文本被写到了错误的行引擎读取的是其他文本组件。项目运行时会从翻译表读取文本场景文件里的文本不是最终显示来源。场景缓存未刷新编辑器还在使用旧版本。检查方式grep -n 目标语言文本 scenes/Level1.unity如果场景文件里已经是目标语言文本但引擎显示原文就去查运行时文本覆盖逻辑。例如很多项目会在Start时读取语言配置并重新赋值文本组件此时即使场景文件被翻译运行后也会被覆盖。解决方式是翻译场景文件的同时也要处理运行时覆盖逻辑或本地化字符串表。否则会出现“编辑器里看到译文运行时看到原文”的割裂现象。6.3 翻译结果截断或乱码现象翻译后的文本显示不全或者出现方块、问号、乱码符号。可能原因字体资源不含目标语言字符集。文本长度超过组件宽高限制。翻译 API 返回了 HTML 实体或特殊 Unicode 字符回写时未转义。文件编码在写回时从 UTF-8 变成了系统默认编码。处理方式检查字体资源确认是否包含中文字符或对应语种字符。把超长文本单独导出报告人工调整措辞或 UI 布局。回写前统一对翻译结果做转义处理。确保文件读写都指定encodingutf-8不要依赖系统默认编码。with open(path, r, encodingutf-8) as f: ... with open(path, w, encodingutf-8) as f: ...6.4 场景翻译排查清单问题现象第一步检查第二步检查处理方向提取不到文本确认资源格式用 grep 查看字段名调整提取规则只有部分文本被翻译查看 translation.json确认去重逻辑检查漏掉的资源文件回写后没变化确认写入了哪个文件确认引擎缓存检查运行时覆盖逻辑引擎报 YAML 错误查看错误行号对比备份文件修复缩进和转义中文显示方块确认字体确认字符集替换字体或设置 fallback运行时提示格式化错误查看占位符检查模板顺序保持占位符顺序7. 更接近生产级的落地建议7.1 不要翻译的文本必须提前标记场景里不是所有字符串都适合翻译。玩家昵称、ID、邮件地址、代码路径、美术资源命名、特殊符号串都不应该进入翻译表。一键翻译工具必须有一个“跳过规则”。常见跳过规则示例只包含数字、空格和符号的字符串。以Assets/开头疑似资源路径的字符串。包含 URL 或mailto:协议的字符串。引擎内部字段名例如m_Name、m_Script。被标记为noLocalize或类似 metadata 的字段。实现时可以在提取阶段增加过滤函数def should_skip(text): if not text: return True if text.startswith(Assets/): return True if http:// in text or https:// in text: return True if text.isdigit(): return True return False7.2 使用 SourceString 作为回写锚点用行号定位不够稳定。生产级工具建议使用“源文本 资源路径 字段路径”作为回写锚点。即使是同一个场景文件在多人协作时也可能发生大量增删行行号会漂移。较好的做法是提取时记录 GameObject 层级路径和组件索引。回写时按路径找到 GameObject 和组件。再按字段名写入文本。如果是 Unity可以使用编辑器扩展脚本在编辑器内完成读取和写入避免直接手写 YAML。Unreal 则优先使用 String Table 或 LocRes 做本地化不要直接改二进制 Map 文件。7.3 版本管理与回滚策略一键翻译脚本一旦回写错误可能影响整个场景文件。回写前必须自动备份cp scenes/Level1.unity scenes/Level1.unity.bak-$(date %Y%m%d%H%M%S)同时建议翻译中间表和回写脚本都纳入版本管理。回写后生成 diff 文件由人工或 CI 检查。保留上一次成功的翻译结果便于快速回滚。对于大场景分批回写不要一次性修改几千条文本。注意一键翻译工具真正交付时应该包含“安全网”。没有备份、没有 diff、没有 dry-run 的一键回写本质上是在制造生产事故。8. 验收与后续扩展8.1 发布前验收清单当翻译完成后不要直接上包。按下面的清单走一遍能拦住大多数问题。- [ ] 所有目标场景文件都能在编辑器中正常打开 - [ ] 场景中的 UI 和 3D 文本都显示为目标语言 - [ ] 对话、任务、物品名称等配置表文本已同步翻译 - [ ] 格式化字符串中的占位符在运行时能正确替换 - [ ] 字体支持目标语言的全部字符 - [ ] 文本长度变化后的 UI 布局没有遮挡或溢出 - [ ] 不存在漏译、空翻译、乱码 - [ ] 场景翻译后的 diff 检查通过 - [ ] 备份文件已保留回滚方案明确 - [ ] 构建包在目标平台上运行验证通过这一份清单既适用于手工验收也可以写成自动化脚本。自动化脚本至少能跑前三项场景可加载性、空翻译检查、占位符检查。8.2 扩展方向场景翻译工具做完后可以往几个方向继续扩展。第一个方向是接入持续集成。在 CI 中自动扫描新增场景或修改过的场景生成待翻译文本提交到翻译平台。开发提交代码时自动触发减少人工跑脚本的步骤。第二个方向是建设术语库和风格库。游戏里有大量专有名词例如角色名、技能名、地名。用术语库保证这些词在场景、配置表和 UI 中保持一致。第三个方向是加入自动化视觉检查。截图对比原文和译文在场景中的渲染效果检测文本溢出、遮挡和字体缺失。这个方向对工具链要求更高但对多语言版本质量提升最大。第四个方向是把“场景翻译”扩展成“资源本地化”。除了文本还要处理图片里的文字、音频里的语音、视频字幕和本地化 UI 布局。到这一步一键翻译就是本地化平台的一部分而不只是一个脚本。回到最开始的问题整个游戏场景一键翻译并导回引擎直接用技术上完全可行但前提是文本可寻址、流程可回滚、验证可量化。工具脚本只是其中一环真正支撑它的是对场景资源结构的理解以及对回写风险的敬畏。建议第一次落地时先选一个文本量小于 200 条的场景做试点跑通“提取 - 翻译 - 回写 - 引擎验收”全流程再逐步推广到全部关卡。这样既能把风险控制在小范围内也能让团队逐渐沉淀出适合自身引擎和项目的本地化工作流。
返回列表