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>
This commit is contained in:
Developer
2026-06-12 15:09:47 +08:00
parent 5d65744618
commit 3d849bd468
23 changed files with 688 additions and 127 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-12

View File

@@ -0,0 +1,103 @@
## 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` 是否需要定义统一的错误码枚举?(待实施时根据现有错误类型决定)
- 程序名称(用户在数据库中输入的中文)是否需要支持英文模式下显示英文别名?(**默认否**,用户数据原样展示)

View File

@@ -0,0 +1,26 @@
## Why
当前应用中大量中文字符串直接硬编码在 Dart 文件中(约 456 处,跨 53 个文件导致1无法切换为英文界面2新增语言时需要逐个文件查找修改3维护成本高且容易遗漏。项目虽已有 `AppLocalizations` 基础设施,但覆盖率不足,多数 UI 文本仍为字面量中文。
## What Changes
- 扫描 `lib/` 下所有硬编码的中文字符串,逐一替换为 `AppLocalizations.of(context)` 调用
- 补充所有缺失的翻译键到 `AppLocalizations`(含中英文值)
- 优化 `AppLocalizations` 类的调用方式,消除 `BuildContext` 强依赖场景(如 Snackbar、Dialog
- 确保运行时可动态切换语言,无需重启应用
- **不涉及**:数据库种子数据、日志输出中的中文(不影响 UI 国际化)
## Capabilities
### New Capabilities
- `i18n`: 完整覆盖应用所有 UI 文本的国际化能力。支持中文(默认)和英文两种语言,所有用户可见文本均通过 `AppLocalizations.of(context)` 获取。
### Modified Capabilities
- (无现有 spec 需要修改)
## Impact
- **代码修改**:约 50+ 个 Dart 文件中的 UI 部分,将硬编码字符串替换为翻译函数的调用
- **新增翻译键**:预估新增约 100-150 个翻译键到 `AppLocalizations`
- **无外部依赖变更**:沿用现有手写翻译系统,不引入 `intl``slang` 等第三方包
- **无破坏性变更**:所有替换为纯文本替换,不改变 UI 结构和逻辑

View File

@@ -0,0 +1,49 @@
## ADDED Requirements
### Requirement: 完整的 UI 文本国际化覆盖
应用 SHALL 通过 `AppLocalizations` 提供所有面向用户的 UI 文本,禁止在 widget 构建逻辑中使用字面量中文字符串。
#### Scenario: 所有可见文本通过翻译函数获取
- **WHEN** 开发者在任何 widget 的 `build()` 方法、`AppBar` 标题、`SnackBar` 内容、`Dialog` 文本或按钮文案中显示文本
- **THEN** 文本必须来自 `AppLocalizations.of(context)` 的 getter 或方法调用,不得直接写中文字面量
#### Scenario: 静态分析检测硬编码中文
- **WHEN** 运行 `flutter analyze` 或代码评审检查 `lib/` 目录下任意 `.dart` 文件中 widget 渲染部分
- **THEN** 不应出现包含中文字符(`[一-鿿]`)的字面量字符串
### Requirement: 中英双语翻译完整性
`AppLocalizations` SHALL 为所有翻译键同时提供中文(`zh`)和英文(`en`)两种语言的取值,避免运行时回退到键名或硬编码默认值。
#### Scenario: 翻译键同时存在于两种语言
- **WHEN** 新增任意翻译键到 `_localizedValues['zh']`
- **THEN** `_localizedValues['en']` 必须包含同名键,且值为对应的英文翻译
#### Scenario: 切换语言后所有界面文本即时更新
- **WHEN** 用户在系统设置中将语言从中文切换到英文
- **THEN** 应用所有页面(首页、程序管理、程序详情、运行控制、系统设置、完成页)的 UI 文本应立即变为英文,无需重启应用
### Requirement: 非 BuildContext 场景的本地化支持
`AppLocalizations` SHALL 提供在没有 `BuildContext` 的场景(如 Provider/Service 层抛出错误消息、日志)中获取当前语言文本的方式,或明确将这些场景排除在国际化范围之外。
#### Scenario: Service 层错误消息的本地化
- **WHEN** 设备运行 Service 需要向 UI 反馈错误(如串口连接失败)
- **THEN** Service 层应返回稳定的错误码或英文键名,由 UI 层通过 `AppLocalizations` 转换为本地化文本展示给用户
#### Scenario: 调试日志保留原始语言
- **WHEN** 代码通过 `dart:developer``log()` 或类似 API 输出调试日志
- **THEN** 允许保留中文日志内容,无需国际化(仅面向开发者)
### Requirement: 单复数与参数化文本
`AppLocalizations` SHALL 支持包含动态参数(如数量、程序名称)的翻译,禁止通过字符串拼接构造可见文本。
#### Scenario: 带参数的翻译方法
- **WHEN** 显示形如「成功导入 5 个程序」的消息
- **THEN** `AppLocalizations` 应提供方法形式(如 `programsImportedCount(int n)`)返回完整本地化字符串,而不是要求调用方拼接 `importSuccess + n + programsImported`
#### Scenario: 替换现有拼接式调用
- **WHEN** 重构现有代码中通过 `'${l.importSuccess} $count ${l.programsImported}'` 形式拼接的字符串
- **THEN** 应替换为单个参数化方法调用,确保英文语序自然(如 `'Successfully imported $count programs'`

View File

@@ -0,0 +1,66 @@
## 1. 准备阶段:扩充翻译表
- [x] 1.1 审计每个 feature 模块,整理所有需要新增的翻译键(中文 + 英文)清单
- [x] 1.2 在 `lib/core/localization/app_localizations.dart` 中新增所有缺失的翻译键 getter中英文
- [x] 1.3 将拼接式调用(如 `importSuccess + count + programsImported`)改为参数化方法(如 `importedPrograms(int count)`
- [x] 1.4 运行 `flutter analyze` 确保 `AppLocalizations` 改动无报错
## 2. core 模块替换
- [x] 2.1 替换 `lib/core/router/app_router.dart` 中的硬编码中文(路由错误页等)
- [x] 2.2 替换 `lib/core/theme/app_theme.dart` 中的硬编码中文(若有)
- [x] 2.3 验证grep 该模块剩余中文字面量为零
## 3. shared 模块替换
- [x] 3.1 替换 `lib/shared/widgets/` 下所有通用组件的中文CommonButton、CommonCard、StatusIndicator 等)
- [x] 3.2 替换 `lib/shared/utils/constants.dart` 中面向 UI 暴露的常量(仅 UI 文本,业务常量保留)
- [x] 3.3 验证grep 该模块剩余中文字面量为零
## 4. features/home 模块替换
- [x] 4.1 替换 `lib/features/home/pages/home_page.dart`
- [x] 4.2 替换 `lib/features/home/pages/complete_page.dart`
- [x] 4.3 替换 `lib/features/home/widgets/status_bar.dart`
- [x] 4.4 替换 `lib/features/home/widgets/program_list.dart`
- [x] 4.5 替换 `lib/features/home/widgets/running_control_panel.dart`
- [x] 4.6 替换 `lib/features/home/widgets/run_status_monitor.dart`
- [x] 4.7 验证grep 该模块剩余中文字面量为零
## 5. features/programs 模块替换
- [x] 5.1 替换 `lib/features/programs/pages/programs_page.dart`
- [x] 5.2 替换 `lib/features/programs/providers/programs_provider.dart`(仅 UI 反馈消息)
- [x] 5.3 替换 `lib/features/programs/services/excel_import_service.dart`错误提示通过错误码返回UI 层翻译)
- [x] 5.4 验证grep 该模块剩余中文字面量为零models 中的字段名注释除外)
## 6. features/program_detail 模块替换
- [x] 6.1 替换 `lib/features/program_detail/` 下所有 page 与 widget 中的中文
- [x] 6.2 验证grep 该模块剩余中文字面量为零
## 7. features/device 模块替换(仅 UI 层)
- [x] 7.1 替换 `lib/features/device/` 下所有 page 与 widget 中的中文
- [x] 7.2 Service 层抛出的 Exception 改为携带错误码(保留中文日志)
- [x] 7.3 UI 层调用 Service 时捕获错误码并通过 `AppLocalizations` 转为本地化消息
- [x] 7.4 验证grep `lib/features/device/` 下 UI 部分剩余中文字面量为零
## 8. features/settings 模块替换
- [x] 8.1 替换 `lib/features/settings/` 下所有 page 与 widget 中的中文
- [x] 8.2 验证grep 该模块剩余中文字面量为零
## 9. 全量回归与验证
- [x] 9.1 全量扫描:`grep -rn "[一-鿿]" lib/ --include="*.dart"` 并人工审阅每条结果,确认仅为日志/注释/数据库种子数据
- [x] 9.2 运行 `flutter analyze`,确保零报错零警告
- [x] 9.3 运行 `flutter test`,确保现有测试全部通过
- [ ] 9.4 启动应用,在中文模式下访问首页、程序管理、程序详情、运行控制、设置、完成页所有路径
- [ ] 9.5 在设置中切换为英文,重复 9.4 的全部路径检查,确认所有 UI 文本变为英文且无遗漏
- [ ] 9.6 切回中文,确认应用正常恢复
## 10. 收尾
- [x] 10.1 更新 `CLAUDE.md`「当前实现状态」段落,记录 i18n 完整覆盖已完成
- [ ] 10.2 在 commit message 中按 feature 分组列出修改