初次提交

This commit is contained in:
2026-07-20 10:56:52 +08:00
commit 7bcc0026e0
462 changed files with 50191 additions and 0 deletions
+197
View File
@@ -0,0 +1,197 @@
# 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` 可预览各语言 |