
1. 项目概述如果你正在用 Godot Engine 开发游戏并且梦想着你的作品能被全球玩家体验那么本地化Localization就是你绕不开的一环。别以为这只是简单的“把文本翻译一下”真正的多语言游戏从界面文字、对话、物品描述到音频、字体甚至图像资源都需要一套系统化的处理方案。标题里的“3步实现”听起来很诱人但背后其实是对 Godot 国际化i18n系统的一次深度梳理。今天我就结合自己踩过的坑和实战经验带你彻底搞懂 Godot 的本地化工具链让你能高效、优雅地为游戏披上多语言的外衣。简单来说Godot 的本地化核心就是围绕Translation资源和TranslationServer展开的。它支持 CSV、Gettext PO 等多种格式并能与资源重映射、字体回退等机制联动形成一个完整的解决方案。但官方文档信息量巨大且分散新手容易迷失在细节里。本文将帮你理清脉络从核心概念到实战配置再到避坑指南用最直白的方式讲清楚如何三步走通这个流程。2. 核心概念与准备工作在动手之前我们必须先理解几个核心概念这能帮你避免后续很多混乱。2.1 国际化 (i18n) vs 本地化 (L10n)虽然常混用但两者有细微差别。国际化是让软件具备支持多语言能力的设计过程比如将代码中所有硬编码的文本提取为可翻译的键。本地化则是针对特定语言或地区区域设置Locale进行适配的具体工作包括翻译文本、调整日期格式、替换图像等。Godot 的“国际化”系统实际上涵盖了两者。2.2 区域设置 (Locale)这是本地化的基石格式通常是语言代码_国家地区代码例如en: 英语zh_CN: 简体中文中国大陆zh_TW: 繁体中文中国台湾ja_JP: 日语日本Godot 会根据系统区域设置或你的设置自动选择对应的翻译文件。理解这一点很重要因为它决定了资源重映射和复数形式的规则。2.3 翻译键与源语言最佳实践是不要直接用自然语言如“Play Game”作为翻译键。原因有二一是英文单词可能有歧义如“Close”可以是“关闭”也可以是“靠近”二是翻译人员可能需要在不同上下文中对同一个英文词做不同翻译。推荐使用全大写、带下划线的描述性键名例如UI_MAIN_MENU_PLAY_BUTTON。源语言文件如en.csv中键对应的值就是你的开发语言通常是英语文本。其他语言文件则使用相同的键但值是对应的翻译。2.4 项目初始设置在开始翻译前确保项目设置正确。打开项目 项目设置找到常规 国际化 区域设置。这里设置项目的回退区域设置。当 Godot 找不到玩家系统语言的翻译时就会使用这个回退语言。通常设为en英语。注意很多开发者会忽略“伪本地化”Pseudolocalization功能。我强烈建议在开发早期就开启它在“区域设置”设置下方。它能自动将所有可翻译文本替换为更长、带特殊字符的版本如[Hëllö Wörld]帮你快速发现UI布局是否无法容纳更长的翻译文本以及哪些字符串忘了做本地化处理。3. 三步实现本地化全流程现在我们进入核心的“三步走”实战流程。这三步构成了一个完整的工作闭环。3.1 第一步提取与准备可翻译文本这是最基础也最容易出错的一步。目标是收集游戏中所有需要翻译的字符串。3.1.1 在场景和代码中标记文本对于场景中的控件Label, Button, CheckBox等Godot 会自动翻译其text属性只要该文本内容在翻译文件中能找到对应的键或完全匹配的源字符串。但为了清晰和避免歧义我强烈建议使用键名。例如一个标签的文本不要直接写“开始游戏”而是写成UI_START_GAME。然后在你的源语言翻译文件如en.csv中添加一行UI_START_GAME, Start Game。在zh_CN.csv中则是UI_START_GAME, 开始游戏。在 GDScript 代码中使用tr()函数来获取翻译# 不推荐硬编码无法翻译 $Label.text Game Over # 推荐使用翻译键 $Label.text tr(UI_GAME_OVER) # 如果必须用英文作键且可能有歧义添加上下文 $CloseButton.text tr(Close, Actions) # 动作“关闭” $DistanceLabel.text tr(Close, Distance) # 距离“近”3.1.2 处理带变量的动态文本游戏中有大量像“你获得了 5 个金币”这样的句子。直接拼接字符串 (You got str(gold) gold coins.) 会让翻译变得极其困难因为不同语言语序不同。Godot 的解决方案是使用格式字符串和tr()的配合。更佳实践是使用命名占位符它让翻译者能自由调整占位符顺序var gold_count 5 # 方式一顺序占位符不够灵活 var message1 tr(You got %d gold coins.) % gold_count # 方式二命名占位符推荐 var message2 tr(You got {num} gold coins.).format({num gold_count}) # 翻译者可以将 {num} 放在句子任何位置3.1.3 处理复数形式“1 apple” 和 “2 apples” 在英语中词形不同其他语言规则更复杂比如俄语、阿拉伯语。Godot 提供了tr_n()函数来处理var apple_count 5 # 参数单数形式复数形式数量[可选]上下文 var text tr_n(You have %d apple., You have %d apples., apple_count) % apple_countGodot 会根据当前区域设置的复数规则自动选择正确的字符串形式。你只需要在翻译文件中为每种语言提供正确的单复数形式即可。3.1.4 生成翻译模板POT 文件手动维护所有tr()调用里的字符串键太容易遗漏。Godot 编辑器可以帮你自动扫描项目提取所有待翻译的字符串生成一个.potPortable Object Template模板文件。进入项目 项目设置 本地化 从场景和脚本中提取字符串。点击提取字符串按钮。Godot 会扫描所有场景.tscn和脚本.gd文件。提取完成后点击生成 POT 文件...选择一个保存位置如translations/messages.pot。这个.pot文件包含了所有唯一的源字符串或键及其出现的上下文文件名和行号是交给翻译人员的完美基础。3.2 第二步创建与管理翻译文件有了模板接下来就是为每种目标语言创建具体的翻译文件。3.2.1 选择翻译文件格式Godot 主要支持两种格式CSV (逗号分隔值)简单用 Excel 或 Google Sheets 就能编辑。格式是键, 翻译文本。对于复数或上下文需要一些约定如用特殊分隔符。适合小型项目或快速原型。Gettext PO行业标准功能强大。支持复数形式、上下文、译者注释等元数据。需要msginit,msgmerge等工具链但管理更专业。适合中大型项目。这里我以更通用的 CSV 格式为例PO 文件流程类似但需要外部工具。3.2.2 创建翻译 CSV 文件用电子表格软件如 LibreOffice Calc 或 Excel新建一个文件。第一列是键Key第二列是翻译Translation。不要有表头。将messages.pot中的msgid内容复制到第一列。在第二列填入对应语言的翻译。将文件保存为 UTF-8 编码的 CSV 文件例如zh_CN.csv。示例zh_CN.csv内容UI_START_GAME,开始游戏 UI_GAME_OVER,游戏结束 ITEM_POTION_HEAL,治疗药水 You got {num} gold coins.,你获得了 {num} 枚金币。3.2.3 导入翻译到 Godot在文件系统中右键点击你的 CSV 文件如zh_CN.csv。选择导入...。在导入面板中将导入为类型改为Translation。关键步骤在翻译标签页下设置区域设置Locale。这里必须填写正确的语言代码如zh_CN。这是 Godot 识别该文件对应哪种语言的依据。点击重新导入。Godot 会生成一个zh_CN.translation的二进制资源文件效率更高。3.2.4 将翻译添加到项目仅仅导入还不够需要告诉 Godot 在游戏中使用它。进入项目 项目设置 本地化 翻译。点击添加...按钮选择你刚刚生成的zh_CN.translation文件。重复此步骤添加所有语言的翻译文件。现在Godot 就知道项目支持哪些语言了。3.3 第三步运行时控制与资源本地化翻译文本就位后我们需要让游戏在运行时能切换语言并处理非文本资源的本地化。3.3.1 设置与切换语言游戏启动时通常应该读取玩家上次设置的语言或检测系统语言。使用TranslationServer单例来控制。extends Node func _ready(): # 1. 从配置文件加载玩家首选语言如果没有则使用系统语言 var saved_lang Settings.get_value(game, language, auto) var target_lang if saved_lang auto: target_lang OS.get_locale_language() # 系统语言可能是 zh但我们有 zh_CN这里需要简单匹配 # 可以做一个映射或者遍历已加载的翻译找到最匹配的 target_lang _find_best_match_locale(target_lang) else: target_lang saved_lang # 2. 设置语言 TranslationServer.set_locale(target_lang) # 3. 重要翻译不会自动应用到已存在的控件。 # 你需要手动触发界面更新通常通过一个自定义信号或调用一个刷新函数。 update_ui_texts() func _find_best_match_locale(system_locale: String) - String: # 简单示例如果系统是 zh优先返回 zh_CN var loaded_locales TranslationServer.get_loaded_locales() if system_locale in loaded_locales: return system_locale # 尝试匹配前缀如 zh 匹配 zh_CN for loc in loaded_locales: if loc.begins_with(system_locale): return loc # 都不匹配回退到项目设置中的回退语言 return en func update_ui_texts(): # 遍历所有需要更新文本的控件重新设置 tr() 文本 # 例如如果你将标签文本存储在节点的 original_key 属性中 for label in get_tree().get_nodes_in_group(localizable_labels): label.text tr(label.original_key)3.3.2 非文本资源的本地化重映射游戏中的图片、声音、字体甚至整个场景都可能因地区而异。Godot 的资源重映射Remapping功能就是为此而生。准备资源为每种语言准备不同的版本。例如res://assets/ui/button_normal.png(默认英文版)res://assets/ui/button_normal_zh.png(中文版)res://assets/ui/button_normal_ja.png(日文版)配置重映射进入项目 项目设置 本地化 重映射。点击添加重映射。在路径中选择你的默认资源如res://assets/ui/button_normal.png。在下方表格中为每种语言区域设置指定替代资源路径。例如为zh_CN添加替代路径res://assets/ui/button_normal_zh.png。当游戏语言切换为中文时任何加载res://assets/ui/button_normal.png的请求Godot 都会自动返回res://assets/ui/button_normal_zh.png。这对于替换包含文字的图片、角色语音、符合文化背景的图标等非常有用。3.3.3 字体与双向文本RTL支持对于中文、日文等语言你需要包含相应字符集的字体文件。Godot 的 DynamicFont 支持字体回退Fallback这是处理多语言字体最优雅的方式。创建一个DynamicFont资源。在字体数据中设置你的主要字体如英文字体。在回退列表中添加其他语言的字体如中文字体。将这个 DynamicFont 资源设置为你 UI 主题的默认字体或具体控件的自定义字体。这样当渲染文本时如果主字体缺少某个字符Godot 会自动尝试回退列表中的字体直到找到能显示该字符的字体为止。对于阿拉伯语、希伯来语等从右到左RTL书写的语言Godot 提供了自动支持。控件的layout_direction属性可以设置为autoGodot 会根据文本内容自动判断并镜像 UI 布局如将按钮对齐从左侧换到右侧。你只需要确保字体支持这些字符即可。4. 高级技巧与避坑指南掌握了基本流程下面这些实战经验能让你走得更稳。4.1 翻译工作流优化使用版本控制将.pot模板文件和.csv/.po翻译文件纳入版本控制。.translation二进制文件由 Godot 生成可以忽略在.gitignore中添加*.translation。与翻译平台集成许多专业翻译平台如 Crowdin, Weblate可以直接导入.pot文件翻译完成后导出.po或.csv。Godot 的 CSV 导入器也能很好地处理这些导出文件。增量更新游戏更新后用 Godot 重新生成.pot文件然后使用msgmerge工具针对 PO 文件或编写简单脚本针对 CSV将新字符串合并到已有的翻译文件中避免翻译人员重复劳动。4.2 常见问题与排查翻译不生效检查区域设置代码确保导入翻译时填写的区域设置如zh_CN与TranslationServer.set_locale()使用的完全一致。大小写敏感。检查翻译文件是否已添加在“项目设置 本地化 翻译”中确认文件列表里有你的翻译。检查控件自动翻译控件的Auto Translate模式是否被误设为Disabled对于代码设置的文本是否使用了tr()函数重启场景切换语言后已加载场景中的控件文本不会自动刷新。你需要像上面例子一样手动更新或重新加载 UI。游戏发布后翻译丢失导出项目时确保在导出预设 资源中勾选了过滤导出并且你的翻译文件.translation没有被过滤规则排除。更稳妥的方式是在导出 资源中手动将翻译文件添加到导出包含列表。伪本地化字符串显示为乱码或方框这通常是因为项目默认字体不支持扩展字符集。伪本地化会用重音字符替换英文字母。你需要为你的 UI 控件设置一个支持 Latin Extended-A 等字符集的字体如 Noto Sans。复数形式翻译错误确保在翻译文件尤其是 PO 文件中正确填写了复数形式。不同语言的复数规则不同在 PO 文件中会体现为msgid_plural和多个msgstr[]条目。Godot 的tr_n()函数依赖这些信息。4.3 性能考量按需加载翻译对于支持语言极多的游戏可以考虑在游戏启动时不加载所有翻译而是根据玩家选择动态加载和卸载Translation资源以减少内存占用。资源重映射的代价资源重映射发生在资源加载时。如果为大量资源配置了重映射且频繁切换语言可能会导致卡顿。建议在加载界面或非关键时段进行语言切换并重新加载必要的场景。4.4 扩展使用脚本管理本地化对于大型项目可以创建一个LocalizationManager单例自动加载脚本集中处理所有本地化逻辑# LocalizationManager.gd extends Node signal language_changed var current_locale: String en func _ready(): load_saved_language() func load_saved_language(): var saved Settings.get_setting(language, auto) set_locale(saved) func set_locale(locale_code: String): if locale_code auto: locale_code OS.get_locale_language() if locale_code ! current_locale and TranslationServer.has_locale(locale_code): TranslationServer.set_locale(locale_code) current_locale locale_code Settings.set_setting(language, locale_code) emit_signal(language_changed) # 通知所有需要刷新的UI组件 get_tree().call_group(localizable_ui, update_localization) else: print_debug(Locale not supported or already set: , locale_code) func get_supported_locales() - Array: return TranslationServer.get_loaded_locales() func get_locale_display_name(locale_code: String) - String: # 实现一个将 zh_CN 显示为“中文简体”的函数 var display_names { en: English, zh_CN: 简体中文, ja_JP: 日本語, } return display_names.get(locale_code, locale_code)这个管理器提供了干净的语言切换接口并通过信号机制解耦了语言切换和UI更新。5. 总结与最佳实践清单走完这三步你的 Godot 游戏就已经具备了坚实的多语言基础。最后我总结一份最佳实践清单供你在实际开发中对照规划先行在项目早期就决定好本地化策略包括使用哪种文件格式CSV/PO、键的命名规范、资源命名规范。键名而非原文始终坚持使用描述性键名如MENU_START作为tr()的参数和场景中的文本避免使用原始语言字符串作为键。拥抱上下文和复数对于有歧义的词使用上下文参数tr(Close, Actions)对于数量变化的句子使用tr_n()。善用伪本地化在 UI 开发阶段就开启伪本地化提前发现布局和遗漏翻译的问题。字体回退是朋友使用 DynamicFont 的回退列表来优雅支持多语言字符集而不是为每种语言创建独立的字体资源。测试每一种语言切换到你支持的每一种语言完整地玩一遍游戏。检查文本溢出、图片替换、音频播放、字体显示是否都正常。导出必验证在导出最终版本前务必用导出包测试所有语言确保翻译文件被打包进去且能正确加载。为翻译者提供上下文如果你使用 PO 文件利用#注释为翻译者提供字符串出现的场景、角色性别等额外信息这能极大提高翻译质量。本地化看似繁琐但一旦建立起规范的工作流后续的维护和扩展就会变得非常顺畅。Godot 提供的这套工具链已经相当成熟理解其核心机制后你就能游刃有余地应对全球市场带来的挑战。记住好的本地化不仅仅是文字的转换更是对另一种文化体验的尊重和适配。