Files
kuaishai2/openspec/changes/fix-hardcoded-chinese-i18n/design.md
Developer 3d849bd468 feat(i18n): 完成全量 UI 文本国际化,替换所有硬编码中文为 AppLocalizations 调用
- core/localization: 新增约 60 个翻译键(含参数化方法),中英双语覆盖
- shared/widgets: CommonDialog 默认参数国际化
- features/home: 完成页操作步骤指引、状态栏串口连接状态、程序列表状态标签
- features/programs: 表头状态列、表单验证提示、导入/模板操作反馈、删除确认(参数化)
- features/program_detail: 步骤列表/表单标题、删除确认、速度档位显示(参数化)
- features/device: run_state_provider 错误消息改为错误码
- features/settings: 升级页、密码面板、语言面板、U盘导入面板、串口配置面板全部替换

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-12 15:09:47 +08:00

104 lines
5.1 KiB
Markdown
Raw 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.
## Context
项目已使用手写的 `AppLocalizations` 类(`lib/core/localization/app_localizations.dart`),通过 `_localizedValues` 嵌套 Map 存储中英文翻译,并由 `LocaleProvider` 控制当前语言。基础设施完备,但实际 UI 代码大量使用中文字面量,覆盖率不足。
i18n 审计结果:
- 53 个 `.dart` 文件包含中文字符
- 约 456 处硬编码字符串
- 现有 `AppLocalizations` 已定义约 100 个键,但许多 UI 文本(如 SnackBar、Dialog、表单标签、占位符、错误消息仍未通过翻译函数获取
## Goals / Non-Goals
**Goals:**
- 用户可见的所有 UI 文本均通过 `AppLocalizations` 获取
- 切换语言后所有页面即时刷新
- 翻译键命名统一、易于维护
- 保留现有手写翻译方案,避免引入新依赖(如 `intl_utils``slang`
**Non-Goals:**
- 不改造日志/调试输出中的中文(仅面向开发者)
- 不本地化数据库种子数据(属于业务数据,由用户编辑)
- 不引入 `.arb` 文件 + 代码生成方案
- 不新增除中英文之外的第三种语言(架构需支持,但本次不实现)
## Decisions
### 决策 1沿用手写 `AppLocalizations`,不迁移到 `flutter gen-l10n`
**选择**:保留现有 `Map<String, Map<String, String>>` 结构。
**理由**
- 项目已具备完整的代理(`Delegate`)和 `LocaleProvider` 设置,迁移成本高
- 翻译规模有限(预估最终 250 个左右键),手写可维护
- 避免引入 `build_runner` 代码生成步骤
**已考虑替代方案**
- `flutter_localizations + .arb` 文件:标准方案但需引入代码生成,对小项目过度
- `slang` 包:类型安全但需新增依赖和构建步骤
### 决策 2参数化文本统一使用方法形式
**选择**将带参数的翻译从「getter + 拼接」改为「方法返回完整字符串」。
示例:
```dart
// 旧(错误)
String get importSuccess => '成功导入';
String get programsImported => '个程序';
// 调用:'${l.importSuccess} $count ${l.programsImported}'
// 新(正确)
String importedPrograms(int count) =>
locale.languageCode == 'en'
? 'Successfully imported $count programs'
: '成功导入 $count 个程序';
```
**理由**:英文语序与中文不同,拼接会产生不自然的文本(如 "Successfully imported 5 programs" 才正确,而非 "Successfully imported 5 programs")。
### 决策 3分模块按 feature 推进
将替换工作按 feature 拆分,每个 feature 一个原子提交:
1. `core` 模块router、theme
2. `features/home`
3. `features/programs`
4. `features/program_detail`
5. `features/device`(仅 UI 层service 层错误消息走错误码)
6. `features/settings`
7. `shared/widgets`
每个 feature 完成后运行 `flutter analyze` + 手动切换语言验证。
### 决策 4Service 层错误使用错误码而非翻译
`SerialPortService``DeviceMessageService` 等返回 `Exception` 时使用稳定的英文错误码或自定义枚举,由 UI 层调用 `AppLocalizations` 的方法转换为本地化消息。
**理由**Service 层无法持有 `BuildContext`,强行注入 `Locale` 会污染领域层职责。
### 决策 5新增翻译键的命名约定
- 采用 `camelCase`
- 按模块前缀分组:`home.*``program.*``step.*``device.*``settings.*``common.*`
- 但为了兼容现有键,新增键先采用相同的平铺命名风格,待整体替换完成后视情况重构
## Risks / Trade-offs
- **[风险] 漏改字符串**456 处替换易遗漏 → **缓解**:完成后用 grep `[一-鿿]` 全量扫描 `lib/features/**/*.dart``lib/shared/**/*.dart`,确保 UI 层无中文字面量
- **[风险] 英文翻译质量参差不齐**:开发者非母语翻译 → **缓解**保持术语表统一device、program、step、position、volume 等),由审阅时统一校对
- **[风险] 现有 Dialog/Snackbar 使用了拼接字符串**:英文语序错乱 → **缓解**:通过参数化方法重构(决策 2
- **[风险] 修改面广,潜在 UI 回归**:可能误改逻辑代码 → **缓解**:严格只替换字符串字面量,每个 feature 完成后人工运行验证
- **[权衡] 不引入代码生成**:可读性 vs. 类型安全 → 接受手写翻译表的维护成本以换取零构建步骤
## Migration Plan
1. **准备**:扩充 `AppLocalizations` 翻译表,补齐所有需要的新键(中英文)
2. **逐模块替换**:按决策 3 顺序,每个 feature 一次提交
3. **回归验证**:每次提交后启动应用,切换中英文,确认无遗漏文本
4. **最终扫描**grep 全量检查 `lib/features/``lib/shared/``lib/core/router/``lib/core/theme/` 中是否仍有中文字面量(排除注释)
5. **回滚策略**:每个 feature 独立提交,可单独 revert
## Open Questions
- Service 层抛出的 `Exception` 是否需要定义统一的错误码枚举?(待实施时根据现有错误类型决定)
- 程序名称(用户在数据库中输入的中文)是否需要支持英文模式下显示英文别名?(**默认否**,用户数据原样展示)