# 04 本地化工作流 (Localization Workflow) > 项目遵循 **ADR-A1**:所有玩家可见字符串必须通过 `tr("KEY")` 包裹,禁止业务代码中出现裸字符串。 --- ## 1. 文件位置 ``` translations/ zh_CN.po ← 简体中文(权威主档) zh_TW.po ← 繁体中文 en.po ← 英文 ja.po ← 日文 ``` --- ## 2. `.po` 文件格式 每条翻译的格式为: ```po msgid "UI_WAVE" msgstr "第 %d 波" ``` - `msgid`:代码中 `tr("KEY")` 使用的键 - `msgstr`:该语言显示的文字 - `%d`、`%s`、`%.1f` 等格式占位符必须与代码中 `tr("KEY") % value` 完全对应 --- ## 3. 添加新翻译字符串(标准流程) ### Step 1 — 在代码中使用 tr() 键 ```gdscript # ❌ 错误:裸字符串 label.text = "商店已关闭" # ✅ 正确:通过键引用 label.text = tr("SHOP_CLOSED_MSG") # 带参数 label.text = tr("UI_WAVE") % current_wave # → "第 3 波" label.text = tr("UI_KILLS") % [kills, alive] # → "击杀: 7 存活: 3" ``` ### Step 2 — 在全部四个 `.po` 文件中添加对应条目 **zh_CN.po**(权威档,写游戏实际中文): ```po msgid "SHOP_CLOSED_MSG" msgstr "商店已关闭" ``` **zh_TW.po**(繁体,改繁体字): ```po msgid "SHOP_CLOSED_MSG" msgstr "商店已關閉" ``` **en.po**(英文): ```po msgid "SHOP_CLOSED_MSG" msgstr "Shop Closed" ``` **ja.po**(日文): ```po msgid "SHOP_CLOSED_MSG" msgstr "ショップが閉店しました" ``` ### Step 3 — 验证(运行时检查) 在游戏内执行脚本验证无缺键: ```gdscript var keys = ["SHOP_CLOSED_MSG"] # 你新加的键 for loc in ["zh_CN","zh_TW","en","ja"]: Locale.set_locale(loc) for k in keys: if tr(k) == k: print("缺键!语言: %s 键: %s" % [loc, k]) ``` --- ## 4. 键名规范 **格式**:`<模块>_<实体>_<含义>`,全大写 + 下划线 | 前缀 | 用途 | 示例 | |:---|:---|:---| | `UI_` | 通用 HUD 标签 | `UI_WAVE`, `UI_HP` | | `SHOP_` | 商店界面 | `SHOP_TITLE`, `SHOP_REROLL` | | `INV_` | 背包界面 | `INV_TITLE`, `INV_BENCH` | | `SETTLE_` | 结算屏 | `SETTLE_TITLE`, `SETTLE_STATS` | | `SETTINGS_` | 设置面板 | `SETTINGS_DIFFICULTY` | | `STATUS_` | 状态消息 | `STATUS_PURCHASED` | | `DIFF_` | 难度名称 | `DIFF_BEGINNER`, `DIFF_CHALLENGE` | | `SPELL_` | 法术名/描述 | `SPELL_SPARK_BOLT_NAME`, `SPELL_SPARK_BOLT_DESC` | | `CORE_` | Core 名/描述 | `CORE_WAND_BASIC_NAME` | | `STATUS_EFFECT_` | 状态效果名 | `STATUS_EFFECT_BURN_NAME` | | `BOSS_` | Boss 名称 | `BOSS_FINAL_NAME` | --- ## 5. 法术 / Core 名称本地化(待完成) 当前法术 `display_name` 字段存储的是裸英/中文字符串(如 `"Spark Bolt"`)。正式发布前需键化。 > **注意**:修改后须同步更新商店按钮渲染(`combat_s2.gd → _refresh_shop_ui`),将 `spell.display_name` 改为 `tr(spell.display_name)`。 **Step 1 — 修改 `wand_preset.gd`** ```gdscript # 当前(裸字符串) n.display_name = "Spark Bolt" n.description = "发射一颗期限 4s 的电光飞弹,造成 3 伤害。" # 修改为(键) n.display_name = "SPELL_SPARK_BOLT_NAME" n.description = "SPELL_SPARK_BOLT_DESC" ``` **Step 2 — 在四语 `.po` 中添加** ```po # zh_CN.po msgid "SPELL_SPARK_BOLT_NAME" msgstr "电花弹" msgid "SPELL_SPARK_BOLT_DESC" msgstr "发射一颗快速电花飞弹,造成 3 点闪电伤害。" ``` **Step 3 — 显示时包裹 tr()** ```gdscript # UI 显示法术名时 btn.text = tr(spell.display_name) # 法术描述 desc_label.text = tr(spell.description) ``` --- ## 6. 添加新语言 **Step 1 — 创建新 `.po` 文件**(以韩语为例) ``` translations/ko.po ``` 内容:复制 `en.po`,将 `Language: en` 改为 `Language: ko`,翻译所有 `msgstr`。 **Step 2 — 在 `locale_manager.gd` 中注册** ```gdscript const LOCALES: Array = ["zh_CN", "zh_TW", "en", "ja", "ko"] # 追加 "ko" ``` 新语言将自动在商店语言切换按钮中循环出现。 --- ## 7. 在游戏内切换语言(测试用) ```gdscript # 切换到英文 Locale.set_locale("en") # 循环切换(等同商店 🌐 按钮) Locale.cycle_locale() # 查看当前语言 print(Locale.get_locale()) ``` --- ## 8. 提交规范 每次提交涉及玩家可见字符串的改动时,须**同时更新四个 `.po` 文件**。允许以英文或空字符串占位(`msgstr ""`),但 PR 审查时须标注"待翻译"。 --- ## 9. 工具推荐 | 工具 | 用途 | |:---|:---| | [Poedit](https://poedit.net) | `.po` 文件 GUI 编辑器,支持模糊匹配和自动翻译接口 | | VSCode + gettext 插件 | 轻量化文本编辑 | | Godot 内置本地化 | `Project → Project Settings → Localization → Translations` 可预览各语言 |