## 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>` 结构。 **理由**: - 项目已具备完整的代理(`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` + 手动切换语言验证。 ### 决策 4:Service 层错误使用错误码而非翻译 `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` 是否需要定义统一的错误码枚举?(待实施时根据现有错误类型决定) - 程序名称(用户在数据库中输入的中文)是否需要支持英文模式下显示英文别名?(**默认否**,用户数据原样展示)