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

5.1 KiB
Raw Blame History

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_utilsslang

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 + 拼接」改为「方法返回完整字符串」。

示例:

// 旧(错误)
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 层错误使用错误码而非翻译

SerialPortServiceDeviceMessageService 等返回 Exception 时使用稳定的英文错误码或自定义枚举,由 UI 层调用 AppLocalizations 的方法转换为本地化消息。

理由Service 层无法持有 BuildContext,强行注入 Locale 会污染领域层职责。

决策 5新增翻译键的命名约定

  • 采用 camelCase
  • 按模块前缀分组:home.*program.*step.*device.*settings.*common.*
  • 但为了兼容现有键,新增键先采用相同的平铺命名风格,待整体替换完成后视情况重构

Risks / Trade-offs

  • [风险] 漏改字符串456 处替换易遗漏 → 缓解:完成后用 grep [一-鿿] 全量扫描 lib/features/**/*.dartlib/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 是否需要定义统一的错误码枚举?(待实施时根据现有错误类型决定)
  • 程序名称(用户在数据库中输入的中文)是否需要支持英文模式下显示英文别名?(默认否,用户数据原样展示)