Files
2026-04-10 22:59:39 +08:00

261 lines
9.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 需求文档:UI文案国际化(i18n)
## 引言
《坦克探险》微信小游戏需要支持中英文双语UI。根据用户所在区域自动展示对应语言的文案,中文地区显示中文,其他地区显示英文。
---
## 技术方案
### 1. i18n 模块结构
`js/i18n/` 目录下创建以下文件:
- **`I18n.js`** — 核心管理器,负责语言检测和文案获取
- **`zh.js`** — 中文语言包
- **`en.js`** — 英文语言包
### 2. 语言检测
通过微信 `wx.getSystemInfoSync().language` 自动检测:
- `zh_CN``zh_TW``zh_HK` 等以 `zh` 开头 → 使用中文
- 其他 → 使用英文(默认 fallback)
### 3. 使用方式
各场景文件通过 `const { t } = require('../i18n/I18n');` 引入翻译函数:
- 简单文案:`t('menu.title')``'坦克探险'` / `'Tank Adventure'`
- 带参数模板:`t('pvp.hp', { count: 3 })``'生命 x3'` / `'HP x3'`
### 4. Key 命名规范
按场景分组,使用点号分隔:
- `menu.*` — 主菜单
- `room.*` — 双人对战房间
- `teamRoom.*` — 3v3团队房间
- `pvp.*` — 双人对战游戏
- `team.*` — 3v3团队游戏
- `pvpResult.*` — 双人对战结算
- `teamResult.*` — 3v3团队结算
- `game.*` — 经典模式
- `common.*` — 通用文案
---
## 需求
### 需求 1:创建 i18n 核心模块
**用户故事:** 作为开发者,我需要一个 i18n 模块来管理多语言文案,支持自动语言检测和带参数的文案模板。
#### 验收标准
1. 创建 `js/i18n/I18n.js`,提供 `t(key, params)` 函数
2. 创建 `js/i18n/zh.js`,包含所有中文文案
3. 创建 `js/i18n/en.js`,包含所有英文文案
4. 通过 `wx.getSystemInfoSync().language` 自动检测语言
5. 支持 `{variable}` 占位符插值
6. 缺失 key 时 fallback 到英文,仍缺失则返回 key 本身
---
### 需求 2:主菜单场景(MenuScenei18n 化
#### 验收标准
| Key | 中文 | 英文 |
|-----|------|------|
| `menu.title` | 坦克探险 | Tank Adventure |
| `menu.subtitle` | 经典坦克对战 | TANK WAR |
| `menu.classic` | 经典模式 | Classic |
| `menu.endless` | 无尽模式 | Endless |
| `menu.pvp` | 双人对战 | PVP |
| `menu.team3v3` | 3v3 对战 | 3v3 Battle |
| `menu.ranking` | 排行榜 | Ranking |
| `menu.settings` | 设置 | Settings |
---
### 需求 3:双人对战房间场景(RoomScenei18n 化
#### 验收标准
| Key | 中文 | 英文 |
|-----|------|------|
| `room.title` | 双人对战 | PVP Battle |
| `room.idleHint` | 创建房间或输入房间号加入 | Create a room or join with a code |
| `room.create` | 创建房间 | Create Room |
| `room.join` | 加入房间 | Join Room |
| `room.connecting` | 连接中{dots} | Connecting{dots} |
| `room.roomCode` | 房间号: | Room Code: |
| `room.waiting` | 等待对手加入{dots} | Waiting for opponent{dots} |
| `room.shareHint` | 将房间号分享给好友 | Share the room code with your friend |
| `room.inputCode` | 输入房间号: | Enter Room Code: |
| `room.opponentFound` | 对手已找到! | Opponent found! |
| `room.starting` | 即将开始... | Game starting... |
| `room.tapBack` | 点击任意位置返回 | Tap anywhere to go back |
| `common.back` | ← 返回 | ← Back |
| `common.joinBtn` | 加入 | Join |
| `common.cannotConnect` | 无法连接服务器 | Cannot connect to server |
| `common.connectFailed` | 连接失败 | Connection failed |
| `common.disconnected` | 与服务器断开连接 | Disconnected from server |
---
### 需求 43v3 团队房间场景(TeamRoomScenei18n 化
#### 验收标准
| Key | 中文 | 英文 |
|-----|------|------|
| `teamRoom.title` | 3v3 团队对战 | 3v3 Team Battle |
| `teamRoom.chooseMode` | 选择游戏方式 | Choose how to play |
| `teamRoom.createTeam` | 🎮 组队开黑 | 🎮 Create Team |
| `teamRoom.soloMatch` | ⚡ 快速匹配 | ⚡ Quick Match |
| `teamRoom.teamId` | 队伍:{id} | Team: {id} |
| `teamRoom.leader` | 队长 | Leader |
| `teamRoom.ready` | ✓ 已准备 | ✓ Ready |
| `teamRoom.notReady` | 未准备 | Not Ready |
| `teamRoom.emptySlot` | 空位 | Empty |
| `teamRoom.invite` | 📨 邀请好友 | 📨 Invite |
| `teamRoom.startMatch` | 🔍 开始匹配 | 🔍 Start Match |
| `teamRoom.disband` | 解散队伍 | Disband |
| `teamRoom.readyBtn` | ✓ 准备 | ✓ Ready |
| `teamRoom.cancelReady` | 取消准备 | Cancel Ready |
| `teamRoom.leaveTeam` | 退出队伍 | Leave Team |
| `teamRoom.matching` | 匹配中{dots} | Matching{dots} |
| `teamRoom.waitTime` | 已等待 {seconds} 秒 | Waited {seconds}s |
| `teamRoom.cancelMatch` | 取消匹配 | Cancel Match |
| `teamRoom.matchFound` | 对手已找到! | Match found! |
| `teamRoom.enterBattle` | 即将进入战斗... | Entering battle... |
| `teamRoom.tapBack` | 点击任意位置返回 | Tap anywhere to go back |
| `teamRoom.shareTitle` | 坦克3v3,速来开黑! | Tank 3v3, join the battle! |
| `common.kicked` | 你已被踢出队伍 | You have been kicked from the team |
---
### 需求 5:双人对战游戏场景(PvpGameScenei18n 化
#### 验收标准
| Key | 中文 | 英文 |
|-----|------|------|
| `pvp.playerLabel` | P{slot} (我方) | P{slot} (You) |
| `pvp.hp` | 生命 x{count} | HP x{count} |
| `pvp.kills` | 击杀:{count} | Kills: {count} |
| `common.paused` | 暂停 | PAUSED |
| `common.tapContinue` | 点击继续 | Tap to continue |
| `pvp.youWin` | 你赢了! | YOU WIN! |
| `pvp.draw` | 平局 | DRAW |
| `pvp.youLose` | 你输了 | YOU LOSE |
---
### 需求 63v3 团队对战游戏场景(TeamGameScenei18n 化
#### 验收标准
| Key | 中文 | 英文 |
|-----|------|------|
| `team.teamA` | A队 | Team A |
| `team.teamB` | B队 | Team B |
| `team.myTeam` | 我方:{team}队 | You: {team} Team |
| `team.killDeath` | 杀:{kills} 亡:{deaths} | K:{kills} D:{deaths} |
| `team.respawn` | {seconds}秒后重生 | Respawning in {seconds}s |
| `team.victory` | 胜利! | VICTORY! |
| `team.defeat` | 失败 | DEFEAT |
| `team.baseHpSummary` | A队:{hpA} 生命 \| B队:{hpB} 生命 | Team A: {hpA} HP \| Team B: {hpB} HP |
| `team.disconnectTitle` | ⚠ 连接断开 | ⚠ Connection Lost |
| `team.reconnecting` | 重连中{dots} ({attempts}/{max}) | Reconnecting{dots} ({attempts}/{max}) |
| `team.reconnectHint` | 请稍候,您的坦克将由AI代管 | Please wait, your tank will be controlled by AI |
---
### 需求 7:双人对战结算场景(PvpResultScenei18n 化
#### 验收标准
| Key | 中文 | 英文 |
|-----|------|------|
| `pvpResult.title` | 对战结果 | MATCH RESULT |
| `pvpResult.victory` | 🏆 胜利! | 🏆 VICTORY! |
| `pvpResult.draw` | ⚔️ 平局 | ⚔️ DRAW |
| `pvpResult.defeat` | 💀 失败 | 💀 DEFEAT |
| `pvpResult.kills` | 击杀 | Kills |
| `pvpResult.lives` | 生命 | Lives |
| `pvpResult.timeRemaining` | 剩余时间:{time} | Time remaining: {time} |
| `pvpResult.rematch` | 再来一局 | Rematch |
| `pvpResult.backMenu` | 返回菜单 | Back to Menu |
---
### 需求 83v3 团队结算场景(TeamResultScenei18n 化
#### 验收标准
| Key | 中文 | 英文 |
|-----|------|------|
| `teamResult.title` | 3v3 对战结果 | 3v3 MATCH RESULT |
| `teamResult.victory` | 🏆 胜利! | 🏆 VICTORY! |
| `teamResult.defeat` | 💀 失败 | 💀 DEFEAT |
| `teamResult.teamAHp` | A队:{hp} 生命 | Team A: {hp} HP |
| `teamResult.teamBHp` | B队:{hp} 生命 | Team B: {hp} HP |
| `teamResult.baseDestroyed` | 基地被摧毁 | Base Destroyed |
| `teamResult.disconnectedReason` | 断线 | Disconnected |
| `teamResult.teamAHeader` | A队 | Team A |
| `teamResult.teamBHeader` | B队 | Team B |
| `teamResult.myTeamSuffix` | (我方) | (You) |
| `teamResult.player` | 玩家 | Player |
| `teamResult.k` | 杀 | K |
| `teamResult.d` | 亡 | D |
| `teamResult.a` | 助 | A |
| `teamResult.dmg` | 伤害 | DMG |
| `teamResult.bot` | 🤖 机器人 | 🤖 Bot |
| `teamResult.duration` | 对战时长:{time} | Match duration: {time} |
| `teamResult.mvp` | ⭐ MVP{name}{kills} 击杀) | ⭐ MVP: {name} ({kills} kills) |
| `teamResult.rankUp` | 📈 积分 +{points} | 📈 Rank +{points} |
| `teamResult.mvpBonus` | MVP加成 +5 | (MVP bonus +5) |
| `teamResult.rankDown` | 📉 积分 -{points} | 📉 Rank -{points} |
| `teamResult.rematch` | 再来一局 | Rematch |
| `teamResult.backMenu` | 返回菜单 | Back to Menu |
---
### 需求 9:经典模式游戏场景(GameScenei18n 化
#### 验收标准
| Key | 中文 | 英文 |
|-----|------|------|
| `game.level` | 第 {level} 关 | Level {level} |
| `game.hp` | 生命 x{count} | HP x{count} |
| `game.fireLevel` | LV{level} | LV{level} |
| `game.enemies` | 敌人: {count} | Enemies: {count} |
| `game.score` | {score}分 | {score}pts |
| `game.gameOver` | 游戏结束 | GAME OVER |
| `game.stageClear` | 关卡通过! | STAGE CLEAR! |
---
## 边界情况与技术约束
### 边界情况
1. **中文字体渲染**Canvas 中使用 `'Arial'` 字体渲染中文时,微信小游戏环境下系统会自动 fallback 到系统中文字体,无需额外处理。
2. **文案长度变化**:中英文文案长度不同,替换后需确认UI布局不会溢出或错位。
3. **`GameScene` 中的字符串比较**`text === '游戏结束'` 改为 `text === t('game.gameOver')`,确保逻辑不受语言影响。
4. **错误消息来源**:部分错误消息可能来自服务端(如 `data.message`),本次仅替换客户端硬编码的文案。
### 技术约束
1. 所有文案替换涉及 `js/scenes/` 目录下的场景文件和新建的 `js/i18n/` 模块。
2. 替换操作不应影响游戏逻辑,仅修改展示层的字符串。
3. 不需要在设置页面增加语言切换选项,完全依赖微信系统语言自动检测。
### 成功标准
1. 中文区域用户看到全中文UI,非中文区域用户看到全英文UI。
2. 替换后游戏功能正常,无因文案修改导致的逻辑错误。
3. 文案在各场景中布局合理,无溢出或错位现象。