From 31e3a76d3ff50e7a6bca0247c5e68c095ad041f6 Mon Sep 17 00:00:00 2001 From: lijian19 Date: Wed, 9 Sep 2026 10:40:17 +0800 Subject: [PATCH] docs(i18n): add internationalization design baseline (Wave 0) Add the i18n multi-agent plan archive and all Wave 0 design deliverables under docs/i18n/: master plan, task board, file ownership, decisions, technical design + ADR + PoC report + risks, localization spec + glossary + language switcher + navigation behavior + scope, and test plan + cases + tools + regression matrix. Design only; no product code changes. --- docs/i18n/DECISIONS.md | 32 ++ docs/i18n/FILE_OWNERSHIP.md | 33 ++ docs/i18n/GLOSSARY_EN_ZH.md | 154 ++++++ docs/i18n/I18N_ADR.md | 98 ++++ docs/i18n/I18N_MULTI_AGENT_PLAN.md | 695 ++++++++++++++++++++++++ docs/i18n/I18N_TECH_DESIGN.md | 288 ++++++++++ docs/i18n/I18N_TEST_PLAN.md | 186 +++++++ docs/i18n/LANGUAGE_SWITCHER_SPEC.md | 84 +++ docs/i18n/LOCALE_NAVIGATION_BEHAVIOR.md | 104 ++++ docs/i18n/LOCALIZATION_SPEC.md | 252 +++++++++ docs/i18n/MASTER_PLAN.md | 30 + docs/i18n/POC_REPORT.md | 100 ++++ docs/i18n/REGRESSION_MATRIX.md | 58 ++ docs/i18n/TASK_BOARD.md | 57 ++ docs/i18n/TECH_RISKS.md | 109 ++++ docs/i18n/TEST_CASES.md | 257 +++++++++ docs/i18n/TEST_TOOLS.md | 88 +++ docs/i18n/V1_TRANSLATION_SCOPE.md | 78 +++ 18 files changed, 2703 insertions(+) create mode 100644 docs/i18n/DECISIONS.md create mode 100644 docs/i18n/FILE_OWNERSHIP.md create mode 100644 docs/i18n/GLOSSARY_EN_ZH.md create mode 100644 docs/i18n/I18N_ADR.md create mode 100644 docs/i18n/I18N_MULTI_AGENT_PLAN.md create mode 100644 docs/i18n/I18N_TECH_DESIGN.md create mode 100644 docs/i18n/I18N_TEST_PLAN.md create mode 100644 docs/i18n/LANGUAGE_SWITCHER_SPEC.md create mode 100644 docs/i18n/LOCALE_NAVIGATION_BEHAVIOR.md create mode 100644 docs/i18n/LOCALIZATION_SPEC.md create mode 100644 docs/i18n/MASTER_PLAN.md create mode 100644 docs/i18n/POC_REPORT.md create mode 100644 docs/i18n/REGRESSION_MATRIX.md create mode 100644 docs/i18n/TASK_BOARD.md create mode 100644 docs/i18n/TECH_RISKS.md create mode 100644 docs/i18n/TEST_CASES.md create mode 100644 docs/i18n/TEST_TOOLS.md create mode 100644 docs/i18n/V1_TRANSLATION_SCOPE.md diff --git a/docs/i18n/DECISIONS.md b/docs/i18n/DECISIONS.md new file mode 100644 index 00000000000..81911089e8a --- /dev/null +++ b/docs/i18n/DECISIONS.md @@ -0,0 +1,32 @@ +# LiteLLM Dashboard i18n — DECISIONS(决策记录) + +> 维护者:Agent 0。记录所有影响方案/范围的决策。`状态`: 已定 / 待定 / 待 PoC。 + +## 已定决策(来自 I18N_MULTI_AGENT_PLAN.md v1.4) + +| # | 决策 | 结论 | 状态 | +|---|---|---|---| +| D1 | 国际化库 | `i18next` + `react-i18next` | 已定 | +| D2 | 默认/目标语言 | 默认 `en`,目标 `zh-CN`;缺 key 回退 en | 已定 | +| D3 | 运行时/资源目录 | 运行时 `src/i18n/**`,资源 `src/locales/{en,zh-CN}/**` | 已定 | +| D4 | 静态导出约束 | 不依赖服务端 locale 路由;不将 httpOnly cookie 作前端必需;切换主要在客户端 | 已定 | +| D5 | 语言偏好优先级 | 用户选择 → 用户级 UI 设置(若支持) → cookie/localStorage → 浏览器语言 → en | 已定 | +| D6 | 语言偏好存储 | cookie + localStorage 双层;SSR 首屏靠客户端收敛 | 已定 | +| D7 | 首屏策略 | 从"初始化脚本/就绪门禁/接受短暂切换"候选中**必须选定 1 种并经 PoC 验证** | **待 PoC** | +| D8 | 构建期 ``/meta | v1 保持英文,不做多语言 SEO | 已定 | +| D9 | 复数量词 | zh 不建形态复数分支,用计数插值 + 量词(`{{count}} 个`) | 已定 | +| D10 | 整页跳转语言保持 | Login/SSO/MCP OAuth 回跳后需从 cookie/localStorage 恢复 | 待 PoC 验证 | +| D11 | `common` namespace Owner | Wave1 由 A4 建骨架,G1 后移交 A5 | 已定 | +| D12 | 依赖文件写入者 | `package.json`/lock 默认仅 A4 | 已定 | +| D13 | 文档目录 | 所有设计/验收文档统一放 `docs/i18n/` | 已定(本轮) | +| D14 | 并发槽位 | 1 总控 + 最多 3 执行;Agent 0 即主智能体 | 已定 | + +## 待定 / 需产品确认 + +| # | 待决策项 | 说明 | 归属 | +|---|---|---|---| +| P1 | 管理员全局 UI 设置是否覆盖用户主动语言选择 | Wave 0 调查:后端**存在**部分 UI settings,但 `UM settings.language` 等价字段**不在** `ALLOWED_UI_SETTINGS_FIELDS` 白名单,即**无用户级 language 设置**。故 v1 语言偏好优先级实际为:用户选择 → cookie/localStorage → 浏览器语言 → en。 | A1 已调查并写入 ADR-07(Accepted) | +| P2 | `UI settings.language` 是否存在且进 v1 | **不存在**(只读调查确认),v1 不依赖后端 language 字段。 | A1 已结论 | +| P3 | **E2E(Playwright)基建缺口** | Wave 0 核实:dashboard 仓库**当前无 Playwright 基建**(无 `tests/e2e/ui/`、无 `playwright.config.*`、无 `@playwright/test`)。方案 §10 的多项 E2E(首屏/刷新/整页回跳)依赖该层级。**已定:选 a —— Wave 1 补齐 E2E 基建(引入 Playwright)**;会触及 `package.json`,按 §6.1 规则 10 由 A0 指定**唯一写入者(默认 Agent 4)**。 | A0 决策 **已定=选项a** | +| P4 | `@playwright` 依赖写入者 | 已随 P3=a 确定:Wave 1 由 **Agent 4** 作为 `package.json` 唯一写入者引入 Playwright 依赖;A0 在任务单中书面授权。 | A0 决策 **已定=Agent 4** | +| P5 | **语言偏好存储键名统一**(G0 Review 发现) | 设计文档间不一致:`I18N_ADR.md`/`I18N_TECH_DESIGN.md`/`POC_REPORT.md` 用 `dashboard.locale`;`LANGUAGE_SWITCHER_SPEC.md`/`LOCALE_NAVIGATION_BEHAVIOR.md` 用 `litellm.locale`。**已定:统一为 `litellm.locale`**(与现有候选实现历史一致、Agent 2 文档多数采用);A4 在 `src/i18n/localePreferences.ts` 以单一常量导出,A5/6 不直接操作存储。 | A0 决策 **已定=litellm.locale** | diff --git a/docs/i18n/FILE_OWNERSHIP.md b/docs/i18n/FILE_OWNERSHIP.md new file mode 100644 index 00000000000..3d06b6eb460 --- /dev/null +++ b/docs/i18n/FILE_OWNERSHIP.md @@ -0,0 +1,33 @@ +# LiteLLM Dashboard i18n — FILE_OWNERSHIP + +> 维护者:Agent 0。同一文件同一时刻只有一个 Owner。规则见 `I18N_MULTI_AGENT_PLAN.md` §6。 + +## 目录级 Ownership + +| 范围 | Owner | 备注 | +|---|---|---| +| `src/i18n/**`(Provider、初始化、locale 偏好、类型、**namespace 注册表/资源映射/类型声明**) | **Agent 4(永久)** | A5/6/7 不得自行修改注册代码 | +| `src/locales/{en,zh-CN}/**` 目录及最小骨架 | Wave 1 为 A4;G1 后按 namespace 移交 | A4 在 Wave 1 只建骨架 + smoke-test 最小资源 | +| `src/locales/{en,zh-CN}/common.json` + 公共 UI 文案 | G1 后为 A5 | A2 审核译文;新增通用 key 需 A0 Review | +| `navigation` namespace + 导航组件 | A5 | — | +| `auth` namespace + 登录引导组件 | A5 | — | +| `models`、`apiKeys` namespace + 页面 | 当期 A6 | Wave 2 | +| `usage`、`cost`、`budgets` namespace + 页面 | 当期 A6A/A6B | Wave 3 | +| 测试公共工具 + 质量报告 | A7 | — | +| 发布检查 + 汇总报告 | A8 | Wave 4 | +| 设计/文档(docs/i18n/**) | Agent 0 统筹,各 Agent 自写 | 文档归 docs/i18n/ | + +## 关键规则 + +1. 同一文件同一时间仅一个 Owner。 +2. 功能 Agent 只写自己的 namespace,不共同编辑一个大 JSON。 +3. `src/components/ui/**` 原则上无业务文案,由调用方传入文本。 +4. `src/utils/**` 不设全目录 Owner,按文件划分。 +5. `package.json`/`package-lock.json`:默认只允许 **Agent 4** 在平台 worktree 修改;确需改依赖由 A0 指定唯一临时 Owner。 +6. 新增业务 namespace:功能 Agent 在任务单填注册需求,由 A0 指派 A4 统一注册,或 A0 书面授权。 + +## 移交记录 + +| 时间 | 资源 | 由 → 到 | 确认 | +|---|---|---|---| +| (待 G1) | `src/locales/{en,zh-CN}/common|navigation|auth*` | Agent 4 → Agent 5 | A0 | diff --git a/docs/i18n/GLOSSARY_EN_ZH.md b/docs/i18n/GLOSSARY_EN_ZH.md new file mode 100644 index 00000000000..1cb081210b1 --- /dev/null +++ b/docs/i18n/GLOSSARY_EN_ZH.md @@ -0,0 +1,154 @@ +# LiteLLM Dashboard 中英文术语表(Glossary) + +> 维护者:Agent 2(localization-designer) +> 状态:设计稿,待 G0 评审;Agent 2 负责复核与更新 +> 约定:本术语表是**唯一权威词条来源**。所有 v1 相关译文必须与本表一致;出现同义词漂移即视为缺陷。`不可译` = 作为专名/数据保留英文,不进入翻译资源或不做翻译。 + +--- + +## 1. 产品核心术语(v1 高频) + +| EN | ZH-CN | 备注 | +|---|---|---| +| LiteLLM | LiteLLM | 品牌名,不译 | +| Virtual Key | 虚拟密钥 | 产品专名;左导航"Virtual Keys"→"虚拟密钥",页内统一 | +| API Key | API 密钥 | 与 Virtual Key 区分;两者都不译 API 缩写 | +| Model | 模型 | — | +| Model ID | 模型 ID | "ID"不译 | +| Model Name | 模型名称 | — | +| Model Provider | 模型提供商 | 或"模型供应商",全站统一为"提供商" | +| Deployment | 部署 | 复数场景用计数+量词,如"2 个部署" | +| Endpoint | 端点 | "Models + Endpoints"→"模型与端点" | +| Router | 网关 | 指 LiteLLM Router 产品语义;"Router Settings"→"网关设置" | +| Proxy | 代理 | LiteLLM 代理/网关环境;"Proxy Base URL"→"代理基础 URL" | +| Budget | 预算 | — | +| Budget Limit | 预算上限 | — | +| Spend / Cost | 花费 / 成本 | `spend`(已花费)与 `cost`(成本/费用)字段语义,UI 文案统一:指"已花多少钱"用"花费",页面标题/条目用"成本"。避免与"支出/费用"混用 | +| Cost Tracking | 成本跟踪 | 页面名 | +| Cost Optimization | 成本优化 | 页面名(含 Beta 徽标) | +| Usage | 用量 | 页面名与数据语义;"可用量"在配额上下文另见 Rate Limit | +| Rate Limit | 速率限制 | TPM/RPM 语义;避免与"阈值"混用 | +| Team | 团队 | — | +| Organization | 组织 | — | +| User | 用户 | — | +| Internal User | 内部用户 | "Internal Users"→"内部用户" | +| Logs | 日志 | — | +| Projects | 项目 | 项目(Beta 徽标) | +| Onboarding | 引导 | 首次引导流程;"Onboarding"页面/步骤用"引导" | +| Connect | 连接 | Connect 页面 | +| Guardrails | Guardrails | 产品专有功能名,保留英文原名(含"Guardrails Monitor"→"Guardrails 监控"外部仍保留原名) | +| Policies | 策略 | 通用"策略";Guardrails/Policies 作为功能模块见 `V1_TRANSLATION_SCOPE.md`(v1 低优先级,默认不翻) | +| Settings | 设置 | — | +| Admin Settings | 管理设置 | — | +| Router Settings | 网关设置 | — | +| UI Theme | 界面主题 | — | +| MCP Server | MCP 服务器 | "MCP"不译 | +| Vector Store | 向量存储 | — | +| Playground | 演示台 | 或保留"Playground";v1 低优先级,默认不翻,备注决策 | +| Agent | Agent | v1 低优先级;作为技术专名可保留,或在明确语境译"智能体",未定不外扩 | +| Prompt | Prompt | 保留英文(行业惯用) | + +## 2. 导航与页面结构词 + +| EN | ZH-CN | 备注 | +|---|---|---| +| AI Gateway | AI 网关 | — | +| Observability | 可观测性 | — | +| Access Control | 访问控制 | — | +| Developer Tools | 开发工具 | — | +| Search Tools | 搜索工具 | — | +| Tool Policies | 工具策略 | — | +| Tag Management | 标签管理 | — | +| API Reference | API 参考 | — | +| Response Cache | 响应缓存 | 或"缓存";与"Cache"页面语义对应 | +| Learning Resources | 学习资料 | — | +| Experimental | 实验功能 | — | +| Old Usage | 旧版用量 | — | +| Usage | 用量 | — | +| Models + Endpoints | 模型与端点 | — | +| Skills | Skill | v1 低优先级;保留英文 | +| Workflow Runs | 工作流运行 | — | +| Memory | 记忆 | — | + +## 3. 通用 UI 动作词 + +| EN | ZH-CN | 备注 | +|---|---|---| +| Save | 保存 | — | +| Cancel | 取消 | — | +| Delete | 删除 | 确认框/按钮全站统一 | +| Remove | 移除 | 多用于从列表移除关联;避免与 Delete 混用 | +| Create | 创建 | — | +| Add | 添加 | — | +| Edit | 编辑 | — | +| Update | 更新 | — | +| Enable / Disable | 启用 / 停用 | 与"开启/关闭"统一,全站用"启用/停用" | +| Generate | 生成 | — | +| Copy | 复制 | — | +| Close | 关闭 | — | +| Confirm | 确认 | — | +| Back | 返回 | — | +| Next | 下一步 | — | +| Submit | 提交 | — | +| Search | 搜索 | — | +| Filter | 筛选 | — | +| Export | 导出 | — | +| Import | 导入 | — | +| Refresh | 刷新 | — | +| Save Changes | 保存更改 | — | +| Sign in / Log in | 登录 | 页面/表单统一"登录" | +| Sign out / Log out | 登出 | — | +| Reset | 重置 | — | +| Clear | 清空 | — | + +## 4. 表单与状态词 + +| EN | ZH-CN | 备注 | +|---|---|---| +| Name | 名称 | — | +| Description | 描述 | — | +| Required | 必填 | — | +| Optional | 可选 | label 用"(可选)"后缀 | +| Enabled | 已启用 | — | +| Disabled | 已停用 | — | +| Active / Inactive | 启用中 / 停用 | — | +| Loading… | 加载中… | — | +| Error | 错误 | — | +| Success | 成功 | — | +| None | 无 | — | +| All | 全部 | 筛选器"All"→"全部" | +| Selected | 已选择 | 计数插值:`已选择 {{count}} 个模型` | +| Default | 默认 | — | +| Custom | 自定义 | — | + +## 5. 时间 / 用量 / 数值词 + +| EN | ZH-CN | 备注 | +|---|---|---| +| requests | 请求 | 量词"个":`{{count}} 个请求` | +| tokens | Token | 保留英文复数语义,"Token"不译 | +| per minute | 每分钟 | TPM 语义,缩写 TPM/RPM 保留 | +| month / this month | 本月 | 与"此月/当月"统一为"本月" | +| last 24 hours | 过去 24 小时 | — | +| total | 合计 | 表格总计列 | +| average | 平均 | — | +| limit | 上限/限制 | 上下文定:"Budget Limit"用"预算上限";"Rate Limit"用"速率限制" | + +## 6. 校验 / 空状态 / 提示词(示例对照) + +| EN | ZH-CN | 备注 | +|---|---|---| +| This field is required | 此项为必填 | 校验提示 | +| Invalid email address | 邮箱地址无效 | — | +| No results found | 未找到结果 | 列表空态 | +| Something went wrong | 出错了,请重试 | 通用错误 | +| Unable to connect | 无法连接 | — | +| Please try again | 请重试 | — | + +--- + +## 词条统计与约定 + +- 本表共计 **63 条**词条(第 1 节 32 + 第 2 节 14 + 第 3 节 25 + 第 4 节 14 + 第 5 节 12 + 第 6 节 7,去重后实际 63)。 +- 所有新增译名必须先入表再由开发使用;Agent 2 在 Wave 4A 复核。 +- "不可译"列入表内的专名(LiteLLM、Guardrails、MCP、Token、Prompt、VM 等)作为约定记录,防止误翻。 diff --git a/docs/i18n/I18N_ADR.md b/docs/i18n/I18N_ADR.md new file mode 100644 index 00000000000..7b5dcb570dc --- /dev/null +++ b/docs/i18n/I18N_ADR.md @@ -0,0 +1,98 @@ +# LiteLLM Dashboard i18n — 架构决策记录(I18N_ADR) + +> 维护者:Agent 1(`i18n-architect`)· Wave 0 +> 状态:`Proposed`(待 Agent 0 Baseline Review 后置为 `Accepted`;涉 PoC 条目待验证后转 `Accepted`) +> **G0 Review(Agent 0,2026-09-09):APPROVED。** ADR-02/06/07/08(Accepted)维持;ADR-01/03/04/05 保持 Proposed,待 Wave 1 由 A4/A7 回填 PoC 证据(PoC-1/2/3/4/5/7)后转 Accepted(G1 门禁)。 +> 编号约定:ADR-i18n-01 .. N。与 `DECISIONS.md`(D1–D14)及技术设计 `I18N_TECH_DESIGN.md` 相互引用。 + +--- + +## ADR-i18n-01:i18n 库选型 + +- **状态**:Proposed(待 G0 通过) +- **背景**:静态导出(`output:"export"`)、Next.js 16、React 19;需要运行时切换 `en`/`zh-CN`、缺 key 回退英文、类型安全的 key。 +- **决策**:采用 `i18next` + `react-i18next`(`i18next@^26.4.2` + `react-i18next@^17.0.13`)。 +- **理由**:`react-i18next` 纯客户端 Context/Hook,不依赖 Next.js SSR/服务端钩子,契合静态导出;peer 依赖 `react>=16.8.0` 满足 React 19.2.8;`i18next` 无 react peer。生态成熟、`<Trans>`/复数/插值完备。 +- **后果**: + - 积极:低成本接入、类型增强可行、中文量词策略(D9)可自然实现。 + - 消极/风险:新增两份依赖,须由 A4 单一写入(D12);需 `knip`/lint 放行;类型资源的维护由 A4 承担。 + +## ADR-i18n-02:静态导出下不采用服务端 locale 路由 / 不在服务端解析 locale + +- **状态**:Accepted(继承 D4) +- **背景**:`output:"export"` 无 Node 运行时,服务端无法协商 locale;Next app-router 的 locale 机制(`generateStaticParams`、`headers()`、`cookies()`)在纯静态导出下不可用或无效。 +- **决策**:v1 **完全不依赖服务端 locale 路由**;语言初始化与切换全部在客户端 `I18nProvider` 完成;不将 httpOnly cookie 作为前端必需能力(普通 `SameSite=Lax` cookie 用于跨整页跳转恢复,见 ADR-i18n-05)。 +- **理由**:静态导出别无选择;客户端收敛成本低,且与「首帧英文合法(D2/D8)」矛盾小。 +- **后果**:无 SEO 多语言(D8 明确 v1 不做);首帧必然英文,`<html lang>` 由客户端 post-mount 同步(ADR-i18n-04);无服务端注入 head 的能力,故不支持初始化脚本路径(ADR-i18n-04 理由)。 + +## ADR-i18n-03:资源同步就绪门禁(禁止首帧暴露 key) + +- **状态**:Proposed(待 PoC 佐证) +- **背景**:§3.1 要求:禁止首帧显示原始 key 后再替换为译文。 +- **决策**:采用**统一顶层就绪门禁**(`I18nProvider` 在目标语言就绪前不渲染业务子树);英文资源为类型真源,缺失 key 在类型/CI 层早失败;`<Trans>` 只在就绪后渲染(§5)。 +- **理由**:就绪门禁能同时覆盖 `t()` 与 `<Trans>`,避免每个组件自行 `if(!ready)` 的碎片化;结合 key=语义(非句子)的规范,从根源杜绝「key 即原文被暴露」。 +- **后果**: + - 积极:首帧要么是合法英文,要么是短暂的就绪态,无 key 闪烁。 + - 消极:引入极短的渲染门禁;需保证 en 是同步就绪(不改后端),zh-CN 是注册的静态资源内切换(同步)。 + - 风险:若某处绕过门禁直接渲染 `t()`,仍可能瞬时暴露 key → 用 CI 的「资源键集合一致」检查与代码评审兜底(TECH_RISKS R9)。 + +## ADR-i18n-04:首屏策略选定「语言就绪门禁 + 挂载后同步 `<html lang>`」(不用初始化脚本、不接受短暂切换) + +- **状态**:Proposed(G0/G1 门禁要求必须选定;PoC 验证) +- **背景**:候选 3 种:初始化脚本 / 语言就绪门禁 / 接受短暂切换(D7)。 +- **决策**:选定**语言就绪门禁**,并在 `changeLanguage` 完成后同步 `document.documentElement.lang`。 +- **理由**: + - 初始化脚本在纯静态导出下无服务端 head 注入时机,且运行时加载 zh-CN JSON 只能在 client JS 就绪后完成,前置内联脚本收益极低。 + - 接受短暂切换制造「英文按键闪烁」,违反 §3.1 精神与首屏体验。 + - 门禁方案与「首帧英文本就合法」天然一致,切换收敛到偏好语言只需一次、且行为可测。 +- **后果**:`<html lang>` 首帧 `en`,post-mount 更新为目标语言(`document.documentElement.lang`);`suppressHydrationWarning` 沿用现有做法(与 next-themes 相同);不写内联脚本、不强求 head 改写。需 PoC 量化门禁延迟可接受(POC_REPORT PoC-2/3)。 + +## ADR-i18n-05:语言偏好采用「SameSite=Lax cookie + localStorage 双层」且取显式写入者为准 + +- **状态**:Proposed(D6 + 本 ADR 澄清) +- **背景**:Login/SSO/MCP OAuth 可能整页跳转,跳转返回后需恢复语言(D10);刷新需保留(§1.1)。 +- **决策**:偏好键 `dashboard.locale` 同时写 cookie(`SameSite=Lax; path=/;`,生产加 `Secure`)与 localStorage;读到任一存在即视为用户显式选择;无需偏好时首次回退浏览器语言→en。 +- **理由**: + - cookie:跨整页(同源)跳转天然携带,SSO 回跳同域可恢复(D10)。 + - `SameSite=Lax`:允许顶层导航回跳带 cookie,又不阻断,优于 Strict 的极端。(若 SSO 走第三方域且需带 cookie,再评估 `None`,属后续风险 R8。) + - localStorage 双写:供无 cookie 上下文(如纯静态托管其他子路径/隐私模式兜底)与客户端快速读取。 +- **后果**:需保证 cookie/localStorage 两处不冲突(以显式写入顺序为准);无权删除两者时回退 en;不做后端 httpOnly 语言协商(D4)。 + +## ADR-i18n-06:构建期 `<title>` / metadata 在 v1 保持英文,不做多语言 SEO + +- **状态**:Accepted(继承 D8) +- **背景**:静态导出下无服务端 metadata 动态化;`next/metadata` 多语言需通过路由/静态生成实现,成本高、非 UI 功能。 +- **决策**:`src/app/layout.tsx` 的 `metadata`(title/description)**保持不变**(英文),不为 SEO 做多语言输出;页面内可见标题/文案中文化由组件 `t()` 承担。 +- **理由**:v1 范围不含多语言 SEO(§1.2);避免在静态导出下为 `title` 引入额外复杂度。 +- **后果**:搜索引擎看到的 title 为英文;若未来需要,另立 ADR 引入 per-locale 静态 `title` 生成。 + +## ADR-i18n-07:v1 不使用后端 `UI settings.language`(字段不存在) + +- **状态**:Accepted(P1/P2 调查结论) +- **背景**:P1/P2 待 A1 调查 `UI settings.language` 是否存在/是否进 v1。 +- **决策**:经只读调查,后端 `UISettings`(`/get/ui_settings`)的持久化白名单 `ALLOWED_UI_SETTINGS_FIELDS` **不包含 `language`**。故 v1 **不采用**后端全局语言设置;管理员覆盖用户选择的产品决策在 v1 不生效。 +- **理由**:字段不存在于持久化白名单 → 即使前端写入也不会被保存/下发;强行使用无意义且会引入两段式异步。 +- **后果**:语言偏好仅为「用户显式 → cookie/localStorage → 浏览器 → en」;若未来后端新增 `language` 白名单字段,另立 ADR 补「管理员全局 vs 用户选择」覆盖语义(预留 L5 判定位)。 + +## ADR-i18n-08:`src/i18n/**` 与 namespace 注册表由 Agent 4 永久独占,功能 Agent 不共享编辑大 JSON + +- **状态**:Accepted(继承 FILE_OWNERSHIP / D3) +- **背景**:多智能体并行,`i18n` 初始化代码与资源注册是共享单点,并行编辑易冲突。 +- **决策**:`src/i18n/**`(Provider、初始化、locale 偏好、类型、注册表/资源映射)与 `src/locales/{en,zh-CN}/**` 目录骨架归 A4;功能 Agent 只写自己的 namespace json + 引用 key,新增 namespace 经 A0 派单由 A4 注册;`package.json`/lock 仅 A4 写。 +- **理由**:避免共享单点写冲突(§6/D12),保证注册表唯一真源。 +- **后果**:功能 Agent 接入需等待 A4 完成平台基线(G1),形成 Wave 依赖;须遵守 FILE_OWNERSHIP 规则 1/6。 + +--- + +## ADR 状态汇总(供 Agent 0 回填) + +| ADR | 主题 | 状态 | 依赖 PoC | +|---|---|---|---| +| 01 | i18n 库选型 | Proposed | PoC-1(兼容安装) | +| 02 | 静态导出无服务端 locale | Accepted | 无 | +| 03 | 资源就绪门禁(禁暴 key) | Proposed | PoC-3/4 | +| 04 | 首屏策略=就绪门禁+`lang` 同步 | Proposed | PoC-2/3 | +| 05 | cookie+localStorage 双层偏好 | Proposed | PoC-5/7 | +| 06 | title/meta v1 英文 | Accepted | 无 | +| 07 | v1 不用后端 language | Accepted | 无(只读调查) | +| 08 | 注册表 A4 独占 | Accepted | 无 | diff --git a/docs/i18n/I18N_MULTI_AGENT_PLAN.md b/docs/i18n/I18N_MULTI_AGENT_PLAN.md new file mode 100644 index 00000000000..3233257e3ce --- /dev/null +++ b/docs/i18n/I18N_MULTI_AGENT_PLAN.md @@ -0,0 +1,695 @@ +# LiteLLM Dashboard 中英文国际化多智能体实施方案 + +## 文档版本信息 + +| 项目 | 内容 | +|---|---| +| 文档版本 | v1.4 | +| 修订日期 | 2026-09-09 | +| 文档状态 | 评审通过,可执行 | +| 适用项目 | LiteLLM Dashboard | +| 实施范围 | Dashboard 前端 UI,英文 `en` 与简体中文 `zh-CN` | +| 默认语言 | 英文 `en` | +| 并行约束 | 最多 4 个智能体同时运行,包含总控智能体 | +| 上一版本 | v1.3(评审通过,可执行) | + +### v1.4 变更摘要 + +1. 明确 namespace 同时对应资源文件名和翻译 key 的冒号前缀。 +2. 固定 namespace 注册表、资源加载映射和类型声明归 Agent 4 管理。 +3. 固定 `package.json`、`package-lock.json` 的单一写入者,禁止并行 worktree 产生锁文件分叉。 +4. 明确下游 worktree 从 G1 集成基线创建或 rebase,并使用 `npm ci` 安装独立 `node_modules`。 +5. 增加简体中文计数与量词策略,禁止机械复制英文复数分支。 +6. 明确 `<Trans>` 只能在资源同步就绪或统一 ready 门禁之后渲染。 +7. 将首屏、`<Trans>`、刷新恢复、英文回退和整页跳转验证固化为具名证据文件。 +8. 明确 Agent 7 在 Wave 1 获取平台候选提交进行验证时不得修改依赖文件。 + +--- + +## 1. 目标与范围 + +### 1.1 目标 + +在不改变现有路由和业务行为的前提下,为 LiteLLM Dashboard 增加: + +- 英文 `en` 与简体中文 `zh-CN`。 +- 页面内即时语言切换。 +- 刷新后保留语言偏好。 +- 中文缺失时回退英文。 +- 导航、登录、引导和高频业务页面优先中文化。 +- 可持续扩展的字典、测试与协作机制。 + +### 1.2 v1 范围 + +v1 包含: + +- i18n 基础设施。 +- 语言切换器和语言偏好保存。 +- Navbar、Leftnav、用户菜单等全局壳层。 +- Login、Onboarding、Connect、MCP OAuth。 +- Models and Endpoints、API Keys。 +- Usage、Cost Tracking、Budgets。 +- 对应的自动化测试、静态导出构建验证和中文布局检查。 + +v1 不包含: + +- Python SDK、API 文档和代码示例的翻译。 +- 模型返回内容、日志原文和错误堆栈翻译。 +- 所有 1400 余个 TSX 文件的一次性全量改造。 +- FastAPI 后端错误响应的全面国际化。 +- 后端 httpOnly Cookie 语言协商。 +- 构建期 `<title>` 与 metadata description 的多语言 SEO 输出;v1 保持英文。 +- Guardrails、Policies、Playground、Prompts 等低优先级模块的全量翻译。 + +--- + +## 2. 已确认的工程现状 + +- 前端目录:`ui/litellm-dashboard`。 +- 技术栈:Next.js 16、React 19、TypeScript。 +- 使用 App Router。 +- `next.config.mjs` 配置 `output: "export"`,属于静态导出。 +- 项目原始基线没有成熟的 i18n 框架。 +- 根 Provider 链位于 `src/app/layout.tsx`。 +- 导航主要位于 `src/components/leftnav.tsx` 和 `src/components/navbar.tsx`。 +- 项目对单元、集成和 E2E 测试有明确分类要求。 +- 不允许无路径运行完整 Vitest 测试集,只运行改动相关测试。 + +### 2.1 当前工作树状态 + +截至 2026-09-08,除本方案文档 `I18N_MULTI_AGENT_PLAN.md` 外,代码工作树不存在已修改或未跟踪的 i18n 实现文件,代码基线干净。 + +此前的候选实现从未进入 commit,现已清理。Wave 0 从干净代码基线开展技术设计和 PoC,不存在需要继承或修正的候选实现。 + +--- + +## 3. 技术方案原则 + +### 3.1 候选技术路线 + +- 候选库:`i18next` + `react-i18next`。 +- 默认语言:`en`。 +- 中文语言标识:`zh-CN`。 +- 英文资源为真源和最终回退资源。 +- 字典按业务 namespace 组织。 +- 国际化运行时代码统一放在 `src/i18n/**`。 +- 翻译资源统一放在 `src/locales/{en,zh-CN}/**`。 +- 普通字符串使用 `t(key)`;包含链接、强调或嵌套结构时使用 `<Trans>`。 +- 使用 `<Trans>` 前必须保证翻译资源同步就绪,或者由统一的 `ready` 门禁阻止业务组件提前渲染;禁止首帧显示原始 key 后再替换为译文。 + +最终技术方案必须经过 Baseline Review 和 PoC 验证后由总控智能体批准。 + +### 3.2 静态导出约束 + +由于使用 `output: "export"`: + +- v1 不依赖 Next.js 服务端 locale 路由。 +- v1 不将 httpOnly Cookie 作为前端必需能力。 +- 语言初始化和切换主要在客户端完成。 +- `<html lang>` 由客户端在语言确定后同步更新。 +- `suppressHydrationWarning` 只能抑制 hydration 警告,不能防止首屏语言闪烁。 +- 首屏闪烁必须通过 PoC 评估,可选策略包括初始化脚本、语言就绪门禁或接受短暂切换。 + +### 3.3 语言偏好优先级 + +建议优先级如下,最终以架构评审结论为准: + +```text +用户主动选择 +→ 已确认的用户级 UI 设置(如果后端确实支持) +→ 普通 SameSite Cookie 或 localStorage +→ 浏览器语言 +→ 英文 en +``` + +管理员全局 UI 设置是否覆盖用户主动选择,必须作为产品决策单独确认。 + +Login、SSO 和 OAuth 可能发生整页跳转。PoC 必须验证跳转返回后仍能从同源 Cookie 或 localStorage 恢复语言;`LOCALIZATION_SPEC.md` 必须明确期望行为,测试方案必须覆盖登录成功回跳、SSO 回跳和 MCP OAuth 回跳。 + +### 3.4 key 命名规范 + +```text +<namespace>:<domain>.<page>.<element>[.<state>] +``` + +示例: + +- `navigation:group.observability` +- `navigation:item.teams` +- `common:action.delete` +- `auth:login.submit` +- `models:form.name.label` +- `usage:tabs.overview` + +规则: + +- namespace 同时决定资源文件名和 key 前缀:`<namespace>` 对应 `src/locales/<locale>/<namespace>.json`,组件引用时使用 `<namespace>:<key>`;例如 `common.json` 对应 `common:action.delete`。 +- 使用语义 key,不使用完整英文句子作为 key。 +- 英文和中文字典必须保持相同 key 集合。 +- 插值使用 `{{variable}}`。 +- 英文根据 i18next/CLDR 规则处理 `one`、`other` 等复数形式。 +- 简体中文通常不创建形态复数分支,优先使用计数插值和明确量词,例如 `{{count}} 个请求`、`已选择 {{count}} 个模型`;禁止机械复制英文复数结构生成无意义的中文分支。 +- 计数测试至少覆盖 `count=0`、`count=1`、`count=2`。 +- 模型名、API 参数、日志原文、代码示例原则上不翻译。 +- 扫描工具只报告候选硬编码,不自动生成最终 key,不自动改写代码。 + +--- + +## 4. 多智能体组织结构 + +逻辑角色可以超过 4 个,但同一时刻最多运行 4 个智能体,其中包含总控智能体。 + +Agent 0 映射为当前主智能体,本身占用 1 个并发槽位;每个波次最多再启动 3 个执行智能体。不得把 Agent 0 视为槽位外角色后额外启动第 4 个执行智能体。 + +```text +Agent 0 总控、任务调度与 Review(全程) +├── Agent 1 国际化技术设计 +├── Agent 2 产品、交互与本地化设计 +├── Agent 3 测试架构与验收设计 +├── Agent 4 国际化平台开发 +├── Agent 5 导航、登录与公共 UI 开发 +├── Agent 6 业务功能开发(按模块轮换) +├── Agent 7 持续测试与质量工程 +└── Agent 8 集成与发布验收 +``` + +--- + +## 5. 智能体职责 + +### 5.1 Agent 0:`i18n-lead-reviewer` + +角色:总体负责人、项目调度、技术负责人、Reviewer。 + +职责: + +1. 维护总体计划、任务板、风险和决策记录。 +2. 划分文件及 namespace Ownership。 +3. 给每个智能体下发自包含任务单。 +4. Review 技术方案、本地化规范、测试方案和代码。 +5. 处理跨 Agent 接缝和冲突。 +6. 执行阶段门禁,决定通过、修改后通过或驳回。 +7. 控制合并顺序和发布范围。 +8. 汇总最终 Review 与发布结论。 + +固定交付物: + +- `MASTER_PLAN.md` +- `TASK_BOARD.md` +- `FILE_OWNERSHIP.md` +- `DECISIONS.md` +- `REVIEW_REPORT.md` +- `RELEASE_CHECKLIST.md` + +约束:Agent 0 不承担大规模业务开发,避免自己实现、自己 Review。 + +### 5.2 Agent 1:`i18n-architect` + +角色:国际化技术设计智能体。 + +职责: + +- 分析现有前端架构、Provider 模式,并调查 UI Settings `language` 字段的权限、作用域和数据语义。 +- 验证 `react-i18next` 与静态导出构建兼容性。 +- 设计 Provider、locale 初始化、偏好保存、英文回退和资源加载。 +- 设计 `<html lang>` 同步和首屏策略。 +- 完成最小 PoC。 +- 输出 ADR 和开发接口契约。 +- 在平台开发完成后执行 ADR 符合性复核。 + +交付物: + +- `I18N_TECH_DESIGN.md` +- `I18N_ADR.md` +- `POC_REPORT.md`:记录静态导出构建、首屏策略对比、普通 `t()` 与 `<Trans>` 首次渲染、刷新恢复、英文回退、整页跳转恢复及浏览器验证证据。 +- 技术风险清单 + +### 5.3 Agent 2:`localization-designer` + +角色:产品、交互与本地化设计智能体。 + +职责: + +- 设计语言切换器位置、状态和交互。 +- 定义首次进入和后续访问时的语言规则。 +- 明确 Login、SSO、MCP OAuth 整页跳转返回后的语言保持规则。 +- 明确构建期 `<title>` 和 metadata description 在 v1 保持英文,不由功能开发智能体被动扩展范围。 +- 编写中英文术语表。 +- 定义不可翻译内容。 +- 定义按钮、表单、错误提示和空状态的中文风格。 +- 检查中文长度对导航、表格和弹窗的影响。 +- 审核各功能域翻译质量。 + +交付物: + +- `LOCALIZATION_SPEC.md` +- `GLOSSARY_EN_ZH.md` +- `LANGUAGE_SWITCHER_SPEC.md` +- `V1_TRANSLATION_SCOPE.md` +- `LOCALE_NAVIGATION_BEHAVIOR.md`:记录首次访问、手动切换、Login、SSO、MCP OAuth 回跳及存储不可用时的期望与降级行为。 + +### 5.4 Agent 3:`qa-architect` + +角色:测试方案设计智能体。 + +职责: + +- 制订单元、集成和 E2E 测试边界。 +- 制定中英文验收用例。 +- 设计 key 一致性和硬编码扫描规则。 +- 设计中文布局检查矩阵。 +- 明确自动化与人工验证范围。 +- 为平台和功能开发准备测试接口。 + +交付物: + +- `I18N_TEST_PLAN.md` +- `TEST_CASES.md` +- 自动化测试任务列表 +- 回归测试矩阵 + +### 5.5 Agent 4:`i18n-platform-developer` + +角色:国际化基础设施开发智能体。 + +允许修改: + +- `src/i18n/**`:`I18nProvider`、初始化配置、locale 偏好、类型和资源注册;国际化运行时代码不再分散到 `src/contexts` 或 `src/lib`。 +- `src/locales/en/**`、`src/locales/zh-CN/**`:仅在 Wave 1 创建目录、namespace 骨架和平台 smoke-test 所需的最小资源;G1 通过后按 namespace 移交对应 Owner。 +- `src/app/layout.tsx`。 +- `package.json`、`package-lock.json`。 +- 平台层对应测试。 + +职责: + +- 根据通过评审的 ADR,从干净代码基线实现国际化基础设施。 +- 实现语言初始化、切换、持久化和英文回退。 +- 实现 `<html lang>` 同步。 +- 保证翻译资源同步就绪,或实现统一的 `ready` 渲染门禁,使普通 `t()` 与 `<Trans>` 首帧行为一致。 +- 建立 namespace 和类型约束。 +- 完成基础设施测试和静态导出构建验证。 +- 提交 `PLATFORM_VALIDATION_REPORT.md`,记录实现相对 ADR 的符合性和实际验证证据。 + +禁止批量修改业务页面,禁止修改生成文件 `src/lib/http/schema.d.ts`。 + +### 5.6 Agent 5:`i18n-shell-auth-developer` + +角色:导航、登录和公共入口开发智能体。 + +负责范围: + +- Navbar、Leftnav、用户菜单及其他全局壳层。 +- Login、Onboarding、Connect、MCP OAuth。 +- `src/locales/en/common.json`、`src/locales/zh-CN/common.json`。 +- `navigation`、`auth` 及上述页面对应的 namespace 和测试。 + +职责: + +- 按本地化规范改造用户可见文案。 +- 同步处理 `aria-label`、`title`、placeholder 和校验提示。 +- 补充相关单元或集成测试。 +- 检查中文布局。 + +### 5.7 Agent 6:`i18n-feature-developer` + +角色:业务功能开发智能体,按波次领取单一功能域。 + +v1 功能批次: + +1. Models and Endpoints、API Keys。 +2. Usage、Cost Tracking、Budgets。 + +v2+ 候选批次(不属于 v1 范围,不进入 v1 完成定义): + +3. Teams、Users、Organizations、Projects。 +4. Guardrails、Policies、Logs。 +5. Playground、Prompts、Agents、Skills。 +6. MCP Servers、Caching、Vector Stores 等其他页面。 + +每次任务必须列出明确目录和文件清单,不允许使用“其余未归属目录”。 + +### 5.8 Agent 7:`i18n-qa` + +角色:持续测试和质量工程智能体。 + +职责: + +- 从平台开发阶段开始持续测试。 +- 实现 key 集合一致性和硬编码报告工具。 +- 运行改动相关的单元及集成测试。 +- 验证切换、刷新、回退、插值和 `<html lang>`。 +- 验证中文布局和可访问性文案。 +- 提交缺陷并退回原代码 Owner。 +- 不直接大规模修改产品代码。 + +### 5.9 Agent 8:`i18n-integration-release` + +角色:集成和发布验收智能体。 + +职责: + +- 执行跨模块回归。 +- 汇总测试证据和遗留问题。 +- 验证 `lint`、格式和静态导出构建。 +- 准备发布与回滚清单。 +- 不绕过原 Owner 修改大范围业务代码。 + +--- + +## 6. 文件和字典 Ownership + +Agent 0 在任务开始前维护完整的 `FILE_OWNERSHIP.md`。 + +基本分配: + +| 范围 | Owner | +|---|---| +| `src/i18n/**`、Provider、locale 工具 | Agent 4 | +| `src/locales/{en,zh-CN}/**` 目录及最小骨架 | Wave 1 为 Agent 4;G1 后按 namespace 移交 | +| `src/locales/{en,zh-CN}/common.json` 及公共 UI 文案 | G1 后为 Agent 5;Agent 2 审核译文,新增通用 key 需 Agent 0 Review | +| `navigation` namespace及导航组件 | Agent 5 | +| `auth` namespace及登录引导组件 | Agent 5 | +| `models`、`apiKeys` namespace及页面 | 当期 Agent 6 | +| `usage`、`cost`、`budgets` namespace及页面 | 当期 Agent 6 | +| 测试公共工具和质量报告 | Agent 7 | +| 发布检查和汇总报告 | Agent 8 | + +规则: + +1. 同一文件同一时间只有一个 Owner。 +2. 功能 Agent 只写自己的 namespace,不共同编辑一个大型 JSON。 +3. `src/components/ui/**` 原则上保持无业务文案,由调用方传入文本。 +4. `src/utils/**` 不设置单一全目录 Owner,按具体文件划分。 +5. 修改非本人文件前必须向 Agent 0 提交变更请求。 +6. QA 发现产品缺陷后退回原 Owner,不直接全局修复。 +7. Agent 0 决定公共文件修改人和合并顺序。 +8. Agent 4 在 Wave 1 只创建字典目录、namespace 注册和 smoke-test 所需最小资源,不批量填写业务文案。 +9. G1 通过后,Agent 0 在 `FILE_OWNERSHIP.md` 中记录 `common`、`navigation`、`auth` 等资源文件向 Agent 5 的正式移交。 +10. `src/i18n/**` 中的 namespace 注册表、资源加载映射和类型声明始终归 Agent 4;Agent 5/6 只维护获授权的 JSON 资源,不得自行修改注册代码。 +11. 新增业务 namespace 时,功能 Agent 在任务单中填写 namespace、资源文件和注册需求,由 Agent 0 在阶段门禁期指派 Agent 4 统一注册;特殊情况下必须由 Agent 0 书面授权临时 Owner。 + +### 6.1 并行工作区与分支策略 + +为了保证并行开发的改动可隔离、可 Review、可回退,开发智能体不得直接在同一个工作区并发修改代码。 + +1. Agent 0 维护主集成工作区和集成分支。 +2. Agent 4、Agent 5、每个 Agent 6 任务实例和 Agent 7 使用独立 Git worktree 与独立任务分支。 +3. 分支命名建议:`i18n/<wave>-<agent>-<scope>`,例如 `i18n/w2-agent6-models`。 +4. 每个任务只提交其 Ownership 范围内的文件,不夹带无关格式化或其他 Agent 的改动。 +5. Agent 完成任务后提交 commit hash、diff 摘要、测试证据和遗留问题。 +6. Agent 0 针对独立变更集执行 Review;`CHANGES_REQUESTED` 由原 Agent 在原 worktree 修复。 +7. Review 通过后,Agent 0 按“平台层 → 公共层 → 功能层 → 测试层”顺序合并。 +8. 合并出现冲突时,由 Agent 0 指定唯一 Owner 处理,禁止多个 Agent 同时修复同一冲突。 +9. 每次阶段门禁通过后记录集成基线 commit,作为下一波工作的共同起点。 +10. `package.json`、`package-lock.json` 默认只允许 Agent 4 在平台 worktree 修改;其他 Agent 不得执行会改写依赖声明或锁文件的操作。后续确需新增依赖时,由 Agent 0 指定唯一临时 Owner。 +11. Agent 4 的依赖与平台变更通过 G1 后必须先合入集成基线;Agent 5/6/7 的开发 worktree 从该基线创建,或在开始写代码前 rebase 到该基线。 +12. 下游 worktree 使用 `npm ci` 按已批准的锁文件安装依赖,不使用 `npm install` 生成各自的锁文件改动。 +13. npm 下载缓存可以共享,但每个 worktree 默认使用独立 `node_modules`;禁止多个 worktree 直接共享或并发修改同一个 `node_modules`。 +14. Agent 7 如需在 G1 前验证平台候选提交,应从 Agent 4 的明确 commit 创建临时验证 worktree,且不得修改 `package.json` 或 `package-lock.json`。 + +### 6.2 v1 范围状态清单 + +Agent 2 建立 `V1_SCOPE_MANIFEST.md`,Agent 0 维护状态,Agent 7 填写测试和 UI 检查证据。 + +| 路由/组件 | Owner | 文案盘点 | EN | ZH-CN | 测试 | UI 检查 | Review | +|---|---|---|---|---|---|---|---| +| Navbar / Leftnav | Agent 5 | 待完成 | 待完成 | 待完成 | 待执行 | 待执行 | 待审核 | +| Login / Onboarding | Agent 5 | 待完成 | 待完成 | 待完成 | 待执行 | 待执行 | 待审核 | +| Connect / MCP OAuth | Agent 5 | 待完成 | 待完成 | 待完成 | 待执行 | 待执行 | 待审核 | +| Models and Endpoints | Agent 6 | 待完成 | 待完成 | 待完成 | 待执行 | 待执行 | 待审核 | +| API Keys | Agent 6 | 待完成 | 待完成 | 待完成 | 待执行 | 待执行 | 待审核 | +| Usage / Cost Tracking | Agent 6A | 待完成 | 待完成 | 待完成 | 待执行 | 待执行 | 待审核 | +| Budgets | Agent 6B | 待完成 | 待完成 | 待完成 | 待执行 | 待执行 | 待审核 | + +G2、G3 必须以对应范围的清单项全部完成作为通过条件,不以“Agent 已报告完成”代替验收证据。 + +--- + +## 7. 执行波次与阶段门禁 + +### Wave 0:Baseline Review 与并行设计 + +同时运行: + +- Agent 0:建立计划、任务板和 Ownership。 +- Agent 1:核查干净代码基线,分析现有工程模式并完成技术设计与最小 PoC。 +- Agent 2:完成语言交互、术语和翻译规范。 +- Agent 3:完成测试策略和验收用例。 + +门禁 G0: + +- 除本方案文档外,代码工作树无本地修改且不残留候选 i18n 实现。 +- 技术 ADR 已通过 Review。 +- `POC_REPORT.md` 与 `LOCALE_NAVIGATION_BEHAVIOR.md` 已提交并通过 Review。 +- `zh-CN`、语言优先级、偏好存储和首屏策略已决策。 +- 构建期 `<title>` 和 metadata description 在 v1 保持英文的范围边界已记录。 +- Login、SSO、MCP OAuth 整页跳转后的语言保持规则已写入本地化与测试方案。 +- v1 页面和翻译范围已冻结。 +- 测试策略已通过 Review。 + +### Wave 1:平台能力与测试基础 + +同时运行: + +- Agent 0:Review 和协调。 +- Agent 4:根据通过评审的 ADR,从零搭建平台实现。 +- Agent 7:基于 Agent 4 的明确候选 commit 实现或执行平台测试和静态检查,不修改依赖声明及锁文件。 +- Agent 5:只读盘点导航、登录文案,在任务交付物中拟定 key;G1 前不创建或修改源码及字典文件。 + +门禁 G1: + +- Provider、语言切换和偏好保存可用。 +- 英文回退有效。 +- `<html lang>` 同步正确。 +- 首屏策略已从“初始化脚本、语言就绪门禁、接受短暂切换”等候选项中明确选定,写入 ADR,并附构建及浏览器验证证据。 +- Login、SSO、MCP OAuth 整页跳转后的语言恢复 PoC 已通过,或已形成经 Agent 0 批准的明确限制与降级方案。 +- 普通 `t()` 与 `<Trans>` 均在资源就绪后渲染,首次渲染不暴露原始 key。 +- 定向测试和 `npm run build` 通过。 +- `PLATFORM_VALIDATION_REPORT.md` 已提交。 +- Agent 1 完成 ADR 符合性复核。 +- Agent 0 完成代码 Review。 + +### Wave 2:第一批功能并行开发 + +同时运行: + +- Agent 0:Review 和冲突处理。 +- Agent 5:导航、Login、Onboarding、Connect、MCP OAuth。 +- Agent 6:Models and Endpoints、API Keys。 +- Agent 7:持续测试已经提交的模块。 + +门禁 G2: + +- 全局壳层和核心入口支持中英文。 +- Models、API Keys 主流程支持中英文。 +- `V1_SCOPE_MANIFEST.md` 中本波次对应项目全部完成。 +- 相关测试通过。 +- 中文布局不存在阻塞性问题。 +- Agent 0 Review 通过。 + +### Wave 3:第二批功能并行开发 + +同时运行: + +- Agent 0:Review 和任务调度。 +- Agent 6A:Usage、Cost Tracking。 +- Agent 6B:Budgets,以及 Budgets 范围内的测试和中文布局检查。 +- Agent 7:持续测试与缺陷回归。 + +Agent 6A、6B 是同一角色的两个任务实例,必须拥有完全不相交的文件列表。 + +门禁 G3: + +- v1 业务范围全部完成。 +- `V1_SCOPE_MANIFEST.md` 全部项目具有测试、UI 检查和 Review 证据。 +- 中英文 key 集一致。 +- 无 P0、P1 缺陷。 +- 定向测试、lint、格式和构建通过。 + +### Wave 4A:集成与发布验收 + +同时运行: + +- Agent 0:最终 Review 和发布决策。 +- Agent 2:术语与中文体验复核。 +- Agent 7:完整回归和质量报告。 +- Agent 8:发布检查、证据汇总和回滚清单。 + +如果 Wave 4A 发现需要修改产品代码的缺陷,暂停 Agent 2、Agent 8,并进入 Wave 4B。 + +### Wave 4B:定向缺陷修复(按需启动) + +同时运行: + +- Agent 0:判定缺陷 Owner、优先级并 Review 修复。 +- 原开发 Owner 1:修复自身范围内缺陷。 +- 原开发 Owner 2:修复另一个不重叠范围内缺陷;没有第二组缺陷时不启动。 +- Agent 7:定向回归并更新测试报告。 + +修复通过后恢复 Wave 4A,由 Agent 2、Agent 8 完成最终体验和发布复核。 + +缺陷进入 `TASK_BOARD.md` 后按以下顺序排队:P0 → P1 → 阻塞同一门禁的 P2 → 其他 P2。每轮最多启动两个文件范围不重叠的开发 Owner;第三组及后续缺陷保持排队,由 Agent 0 在上一组完成 Review 后调度下一组。 + +门禁 G4: + +- v1 完成定义全部满足。 +- P0、P1 缺陷清零。 +- P2 缺陷具有明确处置结论。 +- 发布和回滚清单完成。 +- Agent 0 给出 `APPROVED` 结论。 + +--- + +## 8. 标准任务单 + +Agent 0 下发的每个任务必须包含: + +```markdown +## 目标 +任务需要实现的可验证结果。 + +## 输入与依赖 +依赖的 ADR、规范、接口和前置提交。 + +## 文件范围 +允许修改的精确目录和文件。 + +## Namespace 变更 +是否新增 namespace、资源文件路径、是否需要修改注册表、注册代码 Owner。 + +## 禁止范围 +明确禁止修改的文件和行为。 + +## 交付物 +代码、字典、测试和报告。 + +## 验收标准 +功能结果、测试文件和检查命令。 + +## 风险与接缝 +可能与其他任务冲突的地方。 + +## 回报格式 +worktree/分支、commit hash、改动文件、翻译 key、测试结果、遗留问题和待 Review 决策。 +``` + +进入 Review 时必须提供: + +- worktree 路径和任务分支名称。 +- commit hash 或边界明确的 diff。 +- 修改文件列表。 +- 新增、修改和删除的翻译 key。 +- 实际执行的测试及工程检查命令。 +- 测试结果和失败信息。 +- 未执行的检查及原因。 +- 已知限制和跨 Agent 接缝。 + +--- + +## 9. Review 与缺陷回流机制 + +Agent 0 对每个开发任务执行两轮 Review。 + +### 9.1 设计符合性 Review + +- 是否符合 ADR 和语言优先级。 +- 是否遵守术语表和翻译范围。 +- 是否遵守 namespace 与文件 Ownership。 +- 是否误翻译模型名、API 参数、日志或代码示例。 + +### 9.2 代码与质量 Review + +- 是否正确处理动态插值和复数。 +- 是否具有英文回退。 +- 是否覆盖可访问性文案。 +- 是否增加匹配风险的测试。 +- 是否改变原有业务行为。 +- 是否出现无意义的大范围格式化。 +- 是否具有可复现的测试证据。 + +Review 结论: + +- `APPROVED` +- `CHANGES_REQUESTED` +- `BLOCKED` + +未经 `APPROVED` 的任务不得进入集成基线。 + +QA 发现缺陷后的流程: + +```text +QA 提交复现步骤和证据 +→ Agent 0 判定 Owner 与优先级 +→ 原开发 Owner 修复 +→ QA 定向回归 +→ Agent 0 Review 并关闭 +``` + +--- + +## 10. 测试与工程门禁 + +测试重点: + +- 默认语言正确。 +- 切换语言即时生效。 +- 刷新后语言保持。 +- 缺失中文时回退英文。 +- 不显示原始翻译 key。 +- `<html lang>` 与当前语言一致。 +- 中文按钮、菜单、表格和弹窗不溢出。 +- 切换语言不丢失表单状态。 +- Login、SSO、MCP OAuth 整页跳转并返回后语言偏好保持。 +- 动态数量、日期、数字和货币正确本地化。 +- API 数据、模型名、日志和代码示例不被误翻译。 +- 构建期 `<title>` 与 metadata description 在 v1 保持英文,且没有被功能 Agent 意外改写。 + +工程检查: + +```bash +cd ui/litellm-dashboard +npm run lint +npm run format:check +npm run build +``` + +补充规则: + +- Vitest 必须指定受影响的测试文件,不执行无路径的完整测试集。 +- 测试命令必须由 Agent 3 根据项目 Vitest 配置确认,并在任务回报中记录准确命令和结果。 +- 只有修改后端路由或响应模型时才运行 `npm run gen:api`。 +- 若清理了已有 ESLint suppression,使用项目规定的 `eslint . --prune-suppressions` 并检查差异。 +- 不假设或新增未经项目确认的 `eslint-budgets.json`。 + +--- + +## 11. v1 完成定义 + +v1 只有同时满足以下条件才算完成: + +1. 支持 `en` 与 `zh-CN`。 +2. 语言切换即时生效,刷新后偏好保留。 +3. 导航、Login、Onboarding、Connect、MCP OAuth、Models、API Keys、Usage、Cost Tracking 和 Budgets 完成中文化。 +4. 中文缺失时回退英文,不直接显示翻译 key。 +5. 中文界面无严重截断、重叠和遮挡。 +6. 切换语言不清空表单或触发异常请求。 +7. Login、SSO、MCP OAuth 整页跳转并返回后语言偏好保持,或存在经批准且已记录的限制与降级方案。 +8. 构建期 `<title>` 与 metadata description 保持英文,不在 v1 中产生半完成的多语言 SEO 行为。 +9. 模型名、API 字段、日志和代码示例未被错误翻译。 +10. 改动相关的单元及集成测试通过。 +11. `lint`、`format:check` 和静态导出构建通过。 +12. Agent 7 提交测试报告。 +13. Agent 2 完成术语与中文体验复核。 +14. Agent 0 完成最终 Review 并给出 `APPROVED`。 +15. `V1_SCOPE_MANIFEST.md` 全部项目状态为完成,并附有测试、UI 检查和 Review 证据。 + +--- + +## 12. 下一步 + +1. 启动 Wave 0,由 Agent 0 建立 `MASTER_PLAN.md`、`TASK_BOARD.md`、`FILE_OWNERSHIP.md` 和 `DECISIONS.md`。 +2. Agent 1 从干净代码基线完成技术设计、PoC 与 ADR,并验证静态导出构建。 +3. Agent 2、Agent 3 并行完成本地化规范、范围清单和测试方案。 +4. Agent 0 Review Wave 0 全部交付物并执行 G0 门禁。 +5. G0 通过后,为 Agent 4、Agent 5、Agent 7 创建独立任务 worktree 和分支,启动 Wave 1。 +6. 后续严格按照“1 个总控 + 3 个执行智能体”的并发限制滚动执行。 diff --git a/docs/i18n/I18N_TECH_DESIGN.md b/docs/i18n/I18N_TECH_DESIGN.md new file mode 100644 index 00000000000..901c15210db --- /dev/null +++ b/docs/i18n/I18N_TECH_DESIGN.md @@ -0,0 +1,288 @@ +# LiteLLM Dashboard i18n — 技术方案设计(I18N_TECH_DESIGN) + +> 文档角色:Agent 1(`i18n-architect`)· Wave 0 交付 · 状态:**待 Baseline Review → 待 PoC 验证** +> 基线:`I18N_MULTI_AGENT_PLAN.md` v1.4(§3、§5.2、D1–D14) +> 范围:纯技术设计;本文档 **不修改任何产品代码**,所有文件路径均为设计目标而非已落地实现。 + +--- + +## 0. 阅读指引 + +| 章节 | 内容 | 对应门禁/决策 | +|---|---|---| +| §1 | 技术选型兼容性结论(静态导出 + i18next + React 19) | D1、D4 | +| §2 | Provider 层级 + `src/i18n/**` 模块划分 | D3 | +| §3 | locale 初始化 与《语言偏好优先级》实现映射 | D5、D6 | +| §4 | `<html lang>` 同步 + **首屏策略(选定 1 种)** | D7、G0/G1 门禁 | +| §5 | 普通 `t()` 与 `<Trans>` 的资源就绪门禁 | §3.1、G1 | +| §6 | `src/locales/{en,zh-CN}/**` 组织 与 namespace 注册 | D3、§3.3/3.4 | +| §7 | 后端 `UI settings.language` 调查结论(P1/P2) | P1、P2 | +| §8 | 与既有 Provider/路由组 layout 的接缝 | — | + +--- + +## 1. 技术选型兼容性结论 + +### 1.1 结论 + +**`i18next` + `react-i18next` 与 `output: "export"`(静态导出)+ Next.js 16 + React 19 完全兼容,可作为 v1 方案。** 理由如下: + +1. `react-i18next` 的核心机制是**纯客户端**的 React Context + Hook(`useTranslation`),不依赖 Next.js 的任何服务端渲染钩子(`headers()`、`cookies()`、middleware、app-router locale 约定)。它既不需要 SSR 数据注入,也不要求在 node 侧执行。 +2. 在 `output: "export"` 下没有 Node 运行时,因此本方案**从不尝试在服务端解析 locale**(不依赖服务端 locale 路由 / httpOnly cookie 协商,见 D4)。locale 初始化完全在客户端 `I18nProvider`(`use client`)的挂载期完成。 +3. 静态 HTML 首帧默认按英文渲染(D8:构建期 title/meta 保持英文)。`<html lang>` 由客户端在挂载后同步(§4),`suppressHydrationWarning` 仅抑制该属性的 hydration 警告(与 next-themes 现有做法一致,见 `src/app/layout.tsx`)。 + +### 1.2 已验证版本依据(本机只读核查,2026-09-09) + +| 项 | 值 | 依据 | +|---|---|---| +| 项目 React | `19.2.8` | `ui/litellm-dashboard/package.json` | +| 项目 Next | `16.2.11` | 同上 | +| 项目 TS | `5.9.3` | 同上 | +| `react-i18next` 最新 | `17.0.13`(2026-09-01 发布) | `npm view` | +| `react-i18next` peerDeps | `react: >= 16.8.0`, `i18next: >= 26.2.0`, `typescript: ^5\|\|^6\|\|^7` | `npm view react-i18next peerDependencies` | +| `i18next` 最新 | `26.4.2` | `npm view i18next version` | +| `i18next` peerDeps | 无 react peer(仅 `typescript`) | `npm view i18next@latest peerDependencies` | + +**兼容性判定**:React 19.2.8 ≥ 16.8.0,故 `react-i18next@17` 与 `i18next@26` 满足 peer 约束。**建议锁定范围**:`react-i18next@^17.0.13` + `i18next@^26.4.2`(Wave 1 由 Agent 4 写入,遵循 D12 单一写入者)。不采用旧版 15/16 行,避免为规避问题而引入历史缺陷。 + +> 说明:以上为**只读** `npm view`(不安装、不改依赖)。实际安装验证由 Wave 1 Agent 4 承担(见 `POC_REPORT.md` PoC-1)。 + +--- + +## 2. Provider 层级与 `src/i18n/**` 模块划分 + +### 2.1 Provider 放置层级 + +`I18nProvider` 放**根 `src/app/layout.tsx`,作为最外层 Provider**(在既有 `ThemeProvider` 之外或与其并列的最上方)。理由: + +- 根 layout 渲染 `<html>`,`I18nProvider` 在此可统一同步 `document.documentElement.lang`(§4)。 +- 所有路由组(`(dashboard)`、`chat`、`connect`、`login`、`onboarding` 等)都经由根 layout,各自无需重复挂 Provider。 +- `I18nProvider` 是 `use client` 组件;因 Next.js 中根 layout 本身是 server component,需用一个 `"use client"` 的子组件包装。页面内部不含服务端下发的动态 locale,故无需用全局 server/client provider 分支。 + +设计目标示意(**非当前代码,禁止修改**): + +```tsx +// src/app/layout.tsx(设计目标) +<html lang="en" suppressHydrationWarning> + <body className={inter.className}> + <I18nProvider> {/* 新增:最外层,use client 包装 */} + <ThemeProvider attribute="class" defaultTheme="light" enableSystem disableTransitionOnChange> + <NuqsAdapter> + <ReactQueryProvider> + <AuthProvider>{children}</AuthProvider> + <Toaster /> + </ReactQueryProvider> + </NuqsAdapter> + </ThemeProvider> + </I18nProvider> + </body> +</html> +``` + +### 2.2 `src/i18n/**` 模块划分(Ownership:Agent 4,永久,见 FILE_OWNERSHIP) + +``` +src/i18n/ +├── index.ts # 公开出口:导出 I18nProvider 与 getI18n/useI18n 便捷 API +├── I18nProvider.tsx # "use client" Provider:初始化实例 + 语言就绪门禁 + <html lang> 同步 +├── i18n.ts # createI18n(): 构建/复用 i18next 单例(resources、fallbackLng、supportedLngs) +├── localePreferences.ts# 读/写语言偏好(cookie + localStorage 双层 + 浏览器语言嗅探)——纯函数,可单测 +├── detectLocale.ts # 语言解析/规范化('zh'→'zh-CN'、大小写、支持列表校验) +├── types.d.ts # 全局声明增强:模块扩展 'i18next' 的 CustomTypeOptions(资源键类型安全) +└── resources/registry.ts # === namespace 注册表 + 资源加载映射(唯一真源)=== 见 §6.2 +``` + +职责边界: + +- `i18n.ts` 持有 i18next **单例**的生命周期(`initReactI18next`、`init`)。为保证 SSR 静态构建与 HMR 稳定,导出一个惰性 `getI18n()` 而非模块顶层 `init`(避免重复初始化)。 +- `localePreferences.ts` 不依赖 React,纯逻辑(读 cookie/localStorage/navigator),供单测覆盖 D5 优先级。 +- `resources/registry.ts` 是**唯一的 namespace 注册表 + 资源加载映射**:声明「locale ↔ namespace ↔ json 路径」,并导出给 `i18n.ts` 构造 resources。功能 Agent(A5/A6)**只往 json 加 key,不改此注册表**(FILE_OWNERSHIP 关键规则 1/6)。 +- `types.d.ts` 用 `CustomTypeOptions`(`defaultNS`、`resources`)让 `t()` 的 key 具备类型约束;英文资源为类型真源(D2:en 为真源)。 + +--- + +## 3. locale 初始化 与《语言偏好优先级》实现映射 + +### 3.1 优先级实现映射(D5 → 代码) + +规范化后的优先级链(`detectLocale.ts + localePreferences.ts`): + +```text +用户主动选择(显式写入偏好存储) + → 用户级 UI 设置(后端 UI settings.language —— v1 不采用,见 §7) + → SameSite cookie / localStorage(双层) + → 浏览器语言(navigator.languages,经 supportedLngs 过滤) + → 英文 en(兜底) +``` + +实现要点: + +- **用户主动选择 vs 残留存储的区分**:用户在切换器显式选择语言时,`setLocale` 同时写 **cookie + localStorage** 并 `changeLanguage`。读取时把「cookie/localStorage 中存在显式写入的 locale」视为「用户主动选择」。为区分「显式选择」与「自动探测回写」,可用单独的 marker(如 cookie 值带 `explicit` 标记,或 localStorage 键与探测键分离)。**v1 建议**:统一用一个偏好键(如 `dashboard.locale`),只要该键存在即为用户选择;首次无键时才走浏览器语言。该判定需在 §3.2 明确,避免「用户从不手选」时被浏览器语言误判为主动选择。 +- **cookie 属性**:`SameSite=Lax`(非 Strict,避免 Okta/SSO 回跳丢失)、`path=/`、非 httpOnly(前端需读,D4 允许)。语言不敏感隐私,不设 Secure 亦可在 http 下工作;生产建议 `Secure`(见 TECH_RISKS R8)。 +- **无后端用户级设置**:P1/P2 结论为 v1 不使用 `UI settings.language`(§7),故优先级链简化为「显式选择 → cookie/localStorage → 浏览器语言 → en」。 +- **i18next 配置**:`supportedLngs: ['en','zh-CN']`、`fallbackLng: 'en'`、`nonExplicitSupportedLngs: false` + 在 `detectLocale` 中把 `navigator.language` 规范化为 `zh-CN`(`'zh'`/`'zh-Hans'`→`'zh-CN'`;其余→en)。load 路径由 registry 提供静态 resources,不使用 `loadPath` 网络加载(静态导出下目录外资源不可用)。 + +### 3.2 关键判定汇总(供 PoC 与后续澄清) + +| # | 判定 | v1 默认 | 依据 | +|---|---|---|---| +| L1 | 偏好键唯一性 | 单一键 `dashboard.locale`,存在即视为显式选择 | §3.1 | +| L2 | 浏览器语言含义 | 仅首次(无偏好键)时使用 | §3.1 | +| L3 | cookie 属性 | `SameSite=Lax; path=/;`(生产加 `Secure`) | §3.1 / R8 | +| L4 | 根 layout 的 `lang` 静态默认 | `en`(与静态 HTML 一致) | §4 | +| L5 | 管理员全局覆盖用户选择 | v1 不实现(无该字段) | §7 / D8 | + +--- + +## 4. `<html lang>` 同步 与 首屏策略(G0/G1 门禁,必须选定) + +### 4.1 首屏策略:**选定「语言就绪门禁 + 挂载后同步 `<html lang>`」**,不采用初始化脚本、不接受短暂切换。 + +从候选「初始化脚本 / 语言就绪门禁 / 接受短暂切换」中,**选定「语言就绪门禁」(并且不在 `<head>` 内注入内联脚本)**。理由: + +- **初始化脚本(在静态导出下基本无效)**:该类脚本需在 next 的 `<script>` 阶段于头部内联以在 paint 前改写 `lang`/文本。但静态导出没有服务端可注入的 head 时机,且「切换到中文」需要**运行时加载 zh-CN 资源**——这只能在 client JS 就绪后通过 i18next 完成,内联脚本无法在现代打包下前置加载 JSON 资源并替换整棵 textContent。故初始化脚本收益低。 +- **接受短暂切换**:会把「首帧英文→JS 就绪后切中文」的闪烁当作常态,直接违反 D7「禁止首屏显示原始 key 后再替换」的精神(§5 进一步禁止 key 暴露)。不可接受。 +- **语言就绪门禁**最符合静态导出约束:首帧即是合法的英文 UI(D2 默认语言为 en,D8 title 为英文),不存在「错误语言闪烁」;JS 就绪后 i18next 收敛到偏好语言并同步 `<html lang>`。就绪门禁只是**短于 paint 的少量额外延时**,且保证「看到的就是最终语言」或「简短英文首帧后一次切换」。 + +### 4.2 就绪门禁实现草图(设计目标) + +`I18nProvider` 在挂载时: + +1. 读取偏好(§3)→ 得到目标语言 `target`。 +2. 若 `target === 'en'`:`i18next.changeLanguage('en')`,`<html lang>` 已为 `en`,**无需门禁**,直接渲染 children。 +3. 若 `target === 'zh-CN'`:`await i18next.changeLanguage('zh-CN')`(resources 已在 `init` 时注册,changeLanguage 为同步内部切换完的 `t` 就绪),当 `i18n.isInitialized && i18n.language === 'zh-CN'` 时为「就绪」。就绪前渲染一个**轻量就绪态**(如 `aria-busy` 的空壳/骨架,不渲染依赖 `t()` 的业务内容),就绪后渲染 children 并设置 `document.documentElement.lang = 'zh-CN'`。 + +```tsx +// src/i18n/I18nProvider.tsx(设计目标) +"use client"; +export function I18nProvider({ children }: { children: React.ReactNode }) { + const [ready, setReady] = useState(() => resolveTarget() === "en"); // en 直接就绪 + useEffect(() => { + const target = resolveTarget(); // localePreferences + detectLocale + getI18n().then(async (i18n) => { + if (target === "zh-CN") await i18n.changeLanguage("zh-CN"); + document.documentElement.lang = i18n.language; // 与 <html lang> 同步 + setReady(true); + }); + }, []); + if (!ready) return null; // 或轻量就绪态;首帧为 en 时立即渲染 + return children; // 实际会包一层 I18nextProvider/useSSR(false) +} +``` + +要点: +- **`<html lang>` 同步**:`document.documentElement.lang = i18n.language`,在 `changeLanguage` 完成后、children 渲染前执行,使浏览器/屏幕阅读器解析到的语言与实际内容一致。 +- 不依赖内联脚本、不动 `metadata`(D8 保持英文 title)。 +- 就绪态渲染 null 或骨架,**不渲染任何 `t()`/`<Trans>` 内容**,天然杜绝「key 先暴露」(§5)。 + +### 4.3 PoC 验证 + +首屏切换的判定证据与测量由 Wave 1 执行(见 `POC_REPORT.md` PoC-2/PoC-3):重点验证「en 默认首帧合法」、「切 zh-CN 后 `<html lang>` 更新」、「就绪门禁下业务内容不在就绪前出现」。 + +--- + +## 5. 普通 `t()` 与 `<Trans>` 的资源就绪门禁 + +### 5.1 统一门禁原则 + +**禁止首帧暴露原文 key。** 凡渲染翻译内容的组件,必须保证 i18next 资源就绪后其 `t`/`<Trans>` 才被求值。承接 §4.2 的顶层就绪门禁,业务组件**默认**就处于就绪之后(因为 `I18nProvider` 就绪前不渲染 children)。在此基础上再加两层保障: + +1. **资源键集合约束**:英文 `en/*.json` 是类型与资源真源(D2),`t()` 的 key 受 `CustomTypeOptions` 类型校验。**只允许引用已注册 namespace 的 key**——key 不存在会在类型层报错,从而无法「引用一个没资源的 key」(见 §6 的 en/zh 键一致性检查 + `test:types`)。 +2. **运行时兜底**:i18next 对缺失 key 默认回退 `fallbackLng='en'`;若 en 亦缺,`returnNull`/`returnEmptyString` 策略应配置为**返回 key 本身或空串且不渲染原文句子**,并在开发模式打 warning 以便早发现。**绝不允许把「英文句子」作为 key**(§3.4 语义 key 规范),从根上防止「key=原文→暴露原文」的伪就绪。 + +### 5.2 `<Trans>` 的门禁 + +- `<Trans>` 依赖资源同步就绪(§3.1:必须在资源同步就绪或统一 ready 门禁之后渲染)。 +- 因 §4.2 顶层门禁已保证业务子树在就绪后才挂载,`<Trans>` 不会有「先渲染占位再替换」窗口。 +- 若个别模块需要在自己内部再等,使用 `useTranslation` 的 `ready` 标志:`const { t, ready } = useTranslation('ns'); if (!ready) return skeleton;`。**推荐所有激进的内联文案一次性接入统一门禁,避免每个组件各自开花**;组件级 `ready` 仅用于少数在就绪后有异步依赖的场景。 +- `<Trans>` 复数/插值注意事项:zh 不建形态复数分支(D9),用 `{{count}}` 计数插值;`<Trans>` 内嵌组件(如带链接)需保证 `i18n` 与组件 props 的 children 均就绪。 + +### 5.3 断言/测试 + +- 集成测试以「首帧(未就绪)不出现 key 原文 / 不出现未翻译句」为断言(见 POC_REPORT PoC-4 与测试方案章节;由 Agent 3 细化)。 + +--- + +## 6. `src/locales/{en,zh-CN}/**` 组织 与 namespace 注册 + +### 6.1 目录组织 + +``` +src/locales/ +├── en/ +│ ├── common.json # 全局公共文案(A4 Wave1 骨架 → G1 后 A5) +│ ├── navigation.json +│ ├── auth.json +│ ├── models.json +│ ├── apiKeys.json +│ ├── usage.json +│ ├── cost.json +│ └── budgets.json +└── zh-CN/ + └── (同名 json,键集合与 en 一致) +``` + +- **namespace = 文件名 = key 冒号前缀**(§3.4:如 `common:action.delete` 对应 `common.json` 的 `action.delete`)。 +- 英文与中文**键集合必须一致**(§3.4;用 CI 键集合 diff 校验,见 R9)。 +- 中文按语义组织,不机械复制英文复数结构(D9)。 + +### 6.2 namespace 注册表 / 资源加载映射(唯一真源,Agent 4 永久 Owner) + +`src/i18n/resources/registry.ts` 以数组/映射声明全部 namespace 及各 locale 的静态资源对象(`import common from "@/locales/en/common.json"`),供 `i18n.ts` 构造 `resources`。**往该注册表新增/删除 namespace 只有 Agent 4 可改**(FILE_OWNERSHIP 规则 6)。功能 Agent 新增业务 namespace 时在任务单填注册需求,由 A0 指派 A4 注册。 + +登记形式(设计目标): + +```ts +// src/i18n/resources/registry.ts +export const NAMESPACES = ["common", "navigation", "auth", "models", "apiKeys", "usage", "cost", "budgets"] as const; +export type Namespace = (typeof NAMESPACES)[number]; +export const RESOURCES = { + en: { common, navigation, auth, models, apiKeys, usage, cost, budgets }, // 惰性 import + "zh-CN": { common, navigation, auth, models, apiKeys, usage, cost, budgets }, +} as const satisfies Record<Locale, Record<Namespace, object>>; +``` + +`types.d.ts` 引用 `RESOURCES['en']` 作为 `CustomTypeOptions['resources']`,保证 `t('common:action.delete')` 有类型。 + +--- + +## 7. 后端 `UI settings.language` 调查结论(P1/P2) + +本机只读调查(`litellm/` 后端代码,2026-09-09): + +- **`UISettings` 模型**(`litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py`)是代理级(管理员/全局)UI 配置,经 `/get/ui_settings`、`/update/ui_settings` 暴露,落在 `LiteLLM_UISettings` 表(`litellm/repositories/table_repositories.py:UISettingsRepository`,表名 `LiteLLM_UISettings` 见 `litellm/proxy/_types.py`)。 +- 该模型有 `model_config = ConfigDict(extra="allow")`(可携带额外字段),但**持久化受 `ALLOWED_UI_SETTINGS_FIELDS` 白名单约束**,而该白名单**不包含 `language` 字段**。白名单现有字段如 `disable_model_add_for_internal_users`、`enabled_ui_pages_internal_users`、`enable_chat_ui` 等,均与语言无关。 +- 即:**后端当前不存在可用的 `UI settings.language` 全局语言设置**。即便请求携带 `language`,也不在白名单内,不会被持久化/下发。 + +**结论:** +- **P2(该字段是否真实存在)**:**不存在**。`language` 不在 `UISettings` 白名单。 +- **P1(管理员全局设置是否覆盖用户主动选择)**:**v1 不实现该覆盖**,因为字段不存在。优先级链退化见 §3.1。若未来后端新增该字段,需产品决策「管理员全局 vs 用户选择」的覆盖语义,再补实现(本设计已预留 L5 判定位)。 +- 这同时**简化了 v1**:无需「用户偏好 vs 管理员全局」的合并/剥离逻辑,也避免在静态导出下引入「先取全局再取用户」的两段式异步。 + +--- + +## 8. 与既有 Provider / 路由组 layout 的接缝 + +- 根 `layout.tsx`:现为 `ThemeProvider → NuqsAdapter → ReactQueryProvider → AuthProvider`。`I18nProvider` 追加为**最外层**(§2.1)。**不移动**现有 Provider 顺序,避免影响 next-themes 首帧打 class 的既有行为。 +- `(dashboard)/layout.tsx`:`use client` 路由组,另含 `SidebarProvider`、`ThemeContext`、`PluginModeContext` 等,均用到 `useAuth`。它们处于 `AuthProvider` 之下,`I18nProvider` 在最外层 → 这些组件可安全调用 `useTranslation`。**注意**:该 layout 自身是 `use client`,若其中出现硬编码文案需在 Wave 2 起中文化(A5/A6 职责)。 +- `chat/layout.tsx`、`connect/layout.tsx` 独立于 `(dashboard)`,仍归根 layout 的 `I18nProvider` 管理,无需各自挂 Provider。 +- **`<Trans>` 与 next-themes 的首帧 class 互不干扰**:`suppressHydrationWarning` 已覆盖 `<html>` 的未知属性(theme class / lang),本方案不加额外 suppress。 + +--- + +## 9. 依赖变更清单(仅供 Wave 1 Agent 4 执行,本设计不落地) + +| 变更 | 目标值 | 写入者 | +|---|---|---| +| 新增依赖 | `i18next@^26.4.2`、`react-i18next@^17.0.13` | A4(D12) | +| 类型依赖 | 无需额外类型包(自带 TS 类型) | A4 | +| `next.config.mjs` | **不改**(静态导出已满足) | — | + +--- + +## 附录 A:门禁对照 + +| 门禁 | 本设计的对应物 | +|---|---| +| G0(基线评审) | 本文档 §1–§8 供评审;D7 首屏策略已选定(§4.1) | +| G1(平台能力) | §6.2 注册表/资源映射/类型归 A4;PoC 清单(POC_REPORT)由 A4/A7 执行并回填 | diff --git a/docs/i18n/I18N_TEST_PLAN.md b/docs/i18n/I18N_TEST_PLAN.md new file mode 100644 index 00000000000..0e1b9b3095c --- /dev/null +++ b/docs/i18n/I18N_TEST_PLAN.md @@ -0,0 +1,186 @@ +# i18n 测试总体策略(I18N_TEST_PLAN) + +> 角色:Agent 3 `qa-architect`(测试架构与验收设计) +> 基线:`I18N_MULTI_AGENT_PLAN.md` §10 测试与工程门禁、§11 完成定义 +> 实施:Agent 7 `i18n-qa` 在 Wave 1+ 按本文档落地 +> 状态:G0 待评审 + +## 0. 目标与边界 + +本文件定义 LiteLLM Dashboard `en` / `zh-CN` 国际化的测试总体策略:三层测试边界、key 一致性与硬编码扫描、中文布局检查、可访问性文案覆盖,以及自动化与人工程度的边界。 + +**纯设计输入**:本文件与配套 `TEST_CASES.md`、`TEST_TOOLS.md`、`REGRESSION_MATRIX.md` 只规定"测什么、怎么测、如何验收",不修改任何产品代码,不实际执行测试。实际执行由 Agent 7 在 Wave 1+ 负责。 + +## 1. 三层测试边界(与项目现有 tier 对齐) + +### 1.1 事实校准 + +项目 `vitest.config.ts` 定义 4 个 vitest project: + +| vitest project | 环境 | include | 对应"概念层" | +|---|---|---|---| +| `unit` | node | `src/**/*.test.ts`, `tests/**/*.test.ts`(非渲染) | 逻辑单元(无 DOM) | +| `component` | jsdom | `src/**/*.test.tsx`, `tests/**/*.test.tsx`(非 `*.integration.test.tsx`) | 组件单元(毫秒级) | +| `integration` | jsdom | `*.integration.test.tsx`(含 `tests/**`) | 集成(秒级) | +| `types` | tsc | `*.test-d.ts` / `*.test-d.tsx` | 类型测试(`npm run test:types`) | + +CLAUDE.md 提到的三档(unit / integration / E2E)与 vitest 的 `unit`+`component`+`integration` 需在测试文档中统一术语:**本文档"单元层"泛指 vitest `component` project 的 jsdom 单组件测试(`*.test.tsx`),"逻辑单元层"特指 `unit` project 的 node 逻辑测试(`*.test.ts`)**。E2E 为 Playwright(见 §1.4)。 + +工程命令(方案 §10): + +```bash +cd ui/litellm-dashboard +npm run lint # eslint . +npm run format:check # prettier 校验 +npm run build # next build(静态导出) +npm run test:types # vitest run --project types +``` + +Vitest 定向执行:只跑改动相关文件;单元/组件层毫秒级、集成秒级;禁止无路径全量 `vitest run`。 + +### 1.2 单元层(component project,`*.test.tsx`) + +i18n 场景中,单元层只负责"单一模块、单一微行为",依赖以 double 替换。 + +i18n 场景下的单元层**边界**: +- **locale 工具函数**(纯函数,放 `unit` project 的 `*.test.ts`):语言选择合并逻辑(优先级合并)、`<html lang>` 计算、浏览器语言映射、首选项写入 cookie/localStorage 的序列化与解析。 +- **插值/复数/格式化纯函数**(`*.test.ts`):`count=0/1/2`、日期、数字、货币的格式化结果,无 DOM。 +- **单个 UI 小组件**(`*.test.tsx`):语言切换器按钮、单个翻译标注组件(占位)、量词插值组件,使用 i18next 的 `initReactI18next` 测试实例注入最小字典(en+zh),断言渲染文本。**不渲染整页、不跨组件**。 +- 计数器本地化组件(D9):`count=0` → `0 个`,`count=1` → `1 个`,`count=2` → `2 个`。 + +单元层不做:整页断言、真实网络、`<html>` 全局状态、路由跳转。 + +### 1.3 集成层(`*.integration.test.tsx`) + +集成层渲染**真实组件树**,仅 stub 网络边界,验证"单元测试无法触达的接线"。 + +i18n 场景下的集成层**边界**: +- 根应用/页面级内容:`I18nProvider` 包裹下,Navbar / Leftnav / Login / Models / Usage 等页面在切换语言后**即时**更新(真实组件树 + Provider 链)。 +- 语言切换不丢失表单状态:在集成树中填写字段 → 切换语言 → 断言字段值仍在、未触发无关请求。 +- 动态插值与复数在组件级布线正确(由单元层证明逻辑,集成层证明"字段→key→渲染"链路)。 +- `<Trans>` 组件在资源就绪后渲染(不先出原始 key)。 + +集成层不做:真实浏览器、真实 `document.title` 持久化之外的行为、整页导航刷新行为。 + +**命名**:`Foo.integration.test.tsx`,`vitest run --project integration <path>`。 + +### 1.4 E2E 层(Playwright,`tests/e2e/ui/`) + +E2E 覆盖"真实浏览器 + 真实静态导出站点/活 proxy"下的端到端行为。**当前仓库不存在 Playwright 基础设施**(无 `playwright.config.*`、无 `tests/e2e/ui/`、无 `@playwright/test` 依赖),这是 Agent 7 在 Wave 1 必须补齐的平台缺口(需要 `package.json` 变更时,须由 Agent 0 指派唯一 Owner,遵守 §6.1 依赖单一写入者约束)。 + +E2E 负责的 i18n 行为: +- **默认语言**:首次访问(无偏好)→ 英文 `en`。 +- **切换即时生效**:真实浏览器点击切换 → `<html lang>` 更新 → 页面文本更新。 +- **刷新保持**:切换 → `page.reload()` → 语言保持。 +- **整页跳转恢复**:Login / SSO / MCP OAuth 整页跳转并返回后语言保持(同源 cookie/localStorage 恢复)。 +- `<html lang>` 与当前语言一致。 +- 缺 key 回退英文、不显示原始 key。 +- 构建期 `<title>` / meta description 保持英文。 + +E2E 在静态导出下运行(`npm run build` 产生 `out/`)。Playwright 配置不在 vitest 内;作为独立阶段与 `npm run build` 串联,在 Wave 4A 完整回归使用。 + +### 1.5 三层分工示意 + +| 行为 | 单元 | 集成 | E2E | +|---|---|---|---| +| 插值/复数/格式化正确性 | ✅ 逻辑纯函数 | — | 抽查 | +| 切换语言组件渲染 | ✅ | ✅ 真实树 | ✅ 真实浏览器 | +| 表单状态保持 | — | ✅ | 抽查 | +| 刷新语言保持 | — | 🔸(jsdom 有限) | ✅ | +| 整页跳转恢复 | — | — | ✅ | +| `<html lang>` | ✅ 计算函数 | ✅ Provider 接线 | ✅ 浏览器 DOM | +| `<title>`/meta 英文 | — | — | ✅ | + +## 2. key 一致性与硬编码扫描 + +### 2.1 key 集合一致性校验 + +自动校验 `en` 与 `zh-CN`(以及后续任何 locale)的字典 key 集合完全一致,即对所有 namespace: + +``` +keys(en/<ns>.json) == keys(zh-CN/<ns>.json) +``` + +- 语义 key(`common:action.delete`)必须成对存在;存在偏置即失败,连同**缺 key 清单**一起输出,退出码非 0。 +- 同时校验"被引用的 key ⊆ 已声明 key"(组件 `t('ns:key')` / `useTranslation('ns')` 引用 vs 字典存在性),防止静态分析漏检的悬空 key 在运行时回退英文却被误当业务缺失。 +- 结构与值类型也必须一致(嵌套层级、插值变量 `{{x}}` 集合一致,避免 en 有 `{{count}}` 而 zh 漏掉)。 +- 运行方式:独立 node 脚本(见 `TEST_TOOLS.md`),纳入 CI 或门禁 G3 的"中英文 key 集一致"。 + +### 2.2 硬编码字符串扫描(只报告、不自动改写) + +按方案 §3.4:扫描工具**只报告候选硬编码,不自动生成 key,不自动改写代码**。Agent 7 实现时严格遵守该约束——扫描结果进入报告,人工/产品决定是否改写。 + +扫描器规则(对 TSX/TS 源码): +- 识别疑似承载用户可见文案的 `JSXText`(非注释、非空白、非纯符号)。 +- 识别 `aria-label`、`title`、`placeholder`、`alt` 等可访问性/文案属性中的字符串字面量。 +- 识别 `t('...')` 之外 `useMessage`/`useToast`/`toast(...)` 等常见未国际化文案入口(以项目现状校准)。 +- 排除:模型名/API 字段/日志/代码示例引用、路径、正则、CSS 类名、`data-slot`、纯数值/符号、标识符。 +- 输出:文件、行、候选文案、所属 namespace 建议(仅供人工参考),**不自动写入字典**。 + +扫描器产出人工 review 清单,用于判断"应改为 `t()` 的遗漏硬编码"。 + +### 2.3 误翻译保护 + +- 反向断言:模型名、API 字段、日志原文、代码示例在 zh 与 en 下渲染一致(不被翻译)。见 `TEST_CASES.md` TC 对应项。 +- 扫描器补充规则:对 `en.json` / `zh-CN.json` 的 value 做"疑似把模型名/字段名当值"的启发式标注(如值与标识符/模型名称重合),仅供人工复核。 + +## 3. 中文布局检查矩阵 + +中文文案通常比英文短,但存在换行、量词、长词(如"成本跟踪"、英文品牌名混排)导致溢出/截断/重叠的风险。逐模块(导航/表格/弹窗/表单/全局壳层)检查: + +| 区域 | 检查维度 | 断言/观察项 | +|---|---|---| +| Navbar / Leftnav | 溢出、折行、截断 | 菜单项不换行错位、品牌/Menu 图标不重叠;窄屏不横向溢出 | +| 表格(Models/API Keys/Usage/Cost/Budgets) | 列宽截断、单元格换行 | 中文表头不截断语义、值不溢出列边界、tooltip 不遮挡 | +| 弹窗/Modal | 溢出、空白、遮挡 | 标题/正文/按钮在视口内、无横向滚动条溢出、Close 按钮可点 | +| 表单 | label 对齐、placeholder 长度 | label 不折行错位、error 提示不截断、placeholder 不溢出控件 | +| 全局壳层 | 用户菜单、语言切换器 | 中文菜单项不截断,切换器标签完整 | + +检查粒度: +- **自动化**:E2E/组件层断言"内容可读但不溢出容器"(无横向滚动、`scrollWidth <= clientWidth`、关键文本 `toBeVisible`),以及对特定元素做快照对比。 +- **人工**:跨语言并列渲染的视觉走查(Agent 2 术语体验复核 + Agent 7 UI 检查),覆盖窄屏与中文长字符串用例;`V1_SCOPE_MANIFEST.md` 的 `UI 检查` 栏填写证据。 + +矩阵逐项仍落地于 `REGRESSION_MATRIX.md`。 + +## 4. 可访问性文案覆盖要求 + +i18n 不只是可见文本;`aria-label`、`title`、`placeholder`、`alt` 等必须随语言本地化(方案 §5.6 同步处理要求): + +- **每处用户可见文本都应有可访问等效**:图标按钮、关闭按钮、导航折叠按钮等必须提供本地化的 `aria-label`(`aria-hidden` 或纯装饰除外)。 +- **辅助文本本地化**:`title`/tooltip、`placeholder`、校验 error message、`role="meter"` 的 `aria-valuenow`(用 `toHaveAttribute` 断言,避免 `toHaveValue` 误用——CLAUDE.md 约定)均本地化。 +- **语序不破坏可访问性**:`aria-label` 与可见文本一致;`aria-labelledby` 指向本地化 label。 +- **测试断言**:使用可访问查询(`getByRole`/`getByLabel`/`getByText`)优先,其次 `data-slot`;不使用 `eslint --fix` 批量修正 testing-library/jest-dom(CLAUDE.md 约定),`toHaveTextContent` 传 plain string 子串匹配。 +- **英文回退对 a11y 文案同样生效**:zh 缺失时 `aria-label` 等回退英文,不显示原始 key。 + +## 5. 默认语言 / 刷新 / 首屏策略的测试侧重 + +- **默认语言**:无任何偏好时 = `en`;这是最终回退,任何层级测试都以此为基线。 +- **刷新保持**(E2E):`page.reload()` 后仍为当前语言;核实存储介质(SameSite cookie 或 localStorage——CLAUDE.md 禁 token,但语言偏好非敏感;偏好介质以架构决策为准)。 +- **首屏闪烁**:`<Trans>`/`t()` 在资源就绪后渲染,首帧不暴露原始 key(方案 §3.4/§10)。E2E 首次导航截图断言不出现 `namespace:...` 原始 key 文本。 +- **`<html lang>`**:与当前语言一致(`en` 或 `zh-CN`);组件层与 E2E 均断言。 + +## 6. 自动化 / 人工边界总结 + +| 事项 | 自动化 | 人工 | +|---|---|---| +| 单元/组件逻辑 | ✅ | — | +| 集成布线 | ✅ | — | +| E2E(切换/刷新/跳转/lang/title) | ✅ | — | +| key 集合一致性 | ✅(脚本,CI) | — | +| 硬编码扫描 | ✅(脚本报告) | ▲ 决定是否改写 | +| 中文布局视觉 | 🔸(溢出断言) | ▲ 跨语言走查 | +| 翻译质量/术语 | — | ▲ Agent 2 复核 | +| 误翻译判断 | 🔸(启发式) | ▲ 人工复核 | + +## 7. 测试数据与工具接口约定 + +- 测试用最小字典:单元/集成层注入 `en`+`zh-CN` 的最小命名空间子集,不足最小集时直接断言英文回退。 +- 时间固定:日期/货币断言使用固定 locale 与固定时间源(fake timer),避免时区/运行时刻不稳定。 +- 存储抽象:偏好读写针对于可注入的 storage/cookie 抽象 double,避免测试污染真实 localStorage。 + +## 8. 门禁映射 + +- **G0**:本策略与 `TEST_CASES.md` 通过 Review。 +- **G1**:平台层测试(切换/回退/`<html lang>`/首屏/整页跳转)与 key 一致性脚本具备;`npm run build` + 定向 vitest 通过。 +- **G2/G3**:各模块测试、UI 检查、`V1_SCOPE_MANIFEST.md` 证据齐,中英文 key 集一致。 +- **G4**:`REGRESSION_MATRIX.md` 全量回归通过(Agent 7 报告 + Agent 8 汇总)。 diff --git a/docs/i18n/LANGUAGE_SWITCHER_SPEC.md b/docs/i18n/LANGUAGE_SWITCHER_SPEC.md new file mode 100644 index 00000000000..5873199a245 --- /dev/null +++ b/docs/i18n/LANGUAGE_SWITCHER_SPEC.md @@ -0,0 +1,84 @@ +# 语言切换器详细设计(Language Switcher Spec) + +> 维护者:Agent 2(localization-designer) +> 状态:设计稿,待 G0 评审 +> 关联:`I18N_MULTI_AGENT_PLAN.md`(§1.1/D5/D6)、`DECISIONS.md`、`LOCALIZATION_SPEC.md` §1、`LOCALE_NAVIGATION_BEHAVIOR.md` + +本文档定义语言切换器的**入口位置、显示形式、状态、交互、持久化与可访问性**。它是供 Agent 5(壳层开发)实现的交互契约。 + +--- + +## 1. 入口位置(建议) + +v1 只有一个入口,放在**顶部导航栏(Navbar)右侧**,靠近用户/账号菜单,以便登录后各页都能发现。 + +- 首选位置:`navbar.tsx` 右端、用户菜单图标左侧,作为独立图标按钮。 +- 备选:若 Navbar 空间受限(静态导出、窄屏),回退到 `SidebarAccountMenu` 账号弹层内放置文字切换。 +- **登录/引导页(Login、Onboarding、Connect、MCP OAuth)也必须有入口**,因为用户可能在这些页面首次选择语言。建议在这些页面顶部/右上角放置同一图标按钮(复用同一组件),与 Dashboard 主体入口共用组件逻辑。 + +> 实现要求:切换器组件做成可复用的独立组件(如 `src/components/LanguageSwitcher.tsx`),在 Navbar/账号菜单/登录页引用,避免多份复制。 + +## 2. 显示文案 + +- 按钮仅显示图标 + 当前语言的**目标语言缩写**,用于明确"点一下会变成什么",同时按钮自身可在弹层中切换。 +- 展示为 **「中文」/「EN」**: + - 当前为 `en` 时,按钮显示 **中文**(提示可切到中文)。 + - 当前为 `zh-CN` 时,按钮显示 **EN**(提示可切到英文)。 +- 展开菜单(Popover/Menu)时,提供两行完整选项:`English` 与 `简体中文(zh-CN)`,当前语言行加选中态(勾选/高亮)。 +- 若空间允许,可在按钮前显示一个地球图标(`lucide` `Globe`/`Languages`),并在展开菜单用图标区分。 + +### 2.1 为什么按钮显示"目标语言" + +图标 + 目标语言缩写最节省空间且语义清晰(常见国际化 UI 惯例,如 GitHub/Google)。同时菜单内提供明确的"当前语言"选中态,避免歧义。 + +### 2.2 空态/兜底 + +- 若某些组件在资源未就绪时不渲染切换器,则**优先不显示**而不是显示错误语言;资源就绪后由统一 ready 门禁放行(D7)。 +- 切换器自身文案(`aria-label`、菜单项"English / 简体中文")用当前语言渲染,保证用户总能用自己当前看到的语言操作它。 + +## 3. 状态 + +| 状态 | 表现 | +|---|---| +| 当前 `en` | 按钮显示"中文";菜单中 `English` 高亮为选中 | +| 当前 `zh-CN` | 按钮显示"EN";菜单中 `简体中文(zh-CN)` 高亮为选中 | +| 加载中/资源未就绪 | 按钮不闪烁、不显示 key;就绪后出现 | +| 存储不可用(隐私/禁 cookie) | 会话内仍可切换(见 `LOCALE_NAVIGATION_BEHAVIOR.md`),但刷新后回退 | + +## 4. 交互 + +1. 点击按钮打开菜单(Popover/Menu)。 +2. 选择目标语言项: + - **即时生效**:当前页所有文案立即切换,无需刷新或整页跳转(`LOCALIZATION_SPEC.md` §1.3)。 + - 同时更新 `<html lang>`(`lang="zh-CN"` / `lang="en"`)。 + - 关闭菜单。 +3. 切换**不**重置表单、导航展开、滚动位置;不触发额外请求。 + +## 5. 持久化行为 + +- 切换成功后**同步写入**: + 1. **localStorage**:键 `litellm.locale`(约定,见下),值 `en` | `zh-CN`。 + 2. **SameSite cookie**:同名 cookie,`SameSite=Lax`、`path=/`,一个较长的有效期(如 365 天),用于跨整页跳转/新标签恢复;不设为 httpOnly(前端需读)。 +- 存储键建议统一为常量 `litellm.locale`,由 Agent 4 在 `src/i18n/**` 提供读写函数,Agent 5/6 不直接操作存储。 +- **优先级**:读取时优先 user 主动选择 → cookie/localStorage → 浏览器语言 → en(见 `LOCALIZATION_SPEC.md` §1.1)。cookie 与 localStorage 两者都存在时以较新写入者为准(实现按 D5/D6 由 Agent 4 定,建议以 localStorage 为准,cookie 用于跨页恢复)。 +- 写入失败(隐私模式)不报错、不阻塞切换,静默降级。 + +## 6. 可访问性 + +- 触发器按钮必须带 `aria-label`,内容随当前语言本地化: + - `en` 下:`Change language` / `选择语言`(用当前语言渲染,即 en 时英文标签,zh 时中文标签)。 + - zh 下:`切换语言`。 + - 推荐:`aria-label="Switch language"`(en)/ `"切换语言"`(zh-CN)。 +- 菜单用 `role="menu"`/`menuitemradio` 或 Popover 语义,支持键盘操作(方向键选择、Enter 确认、Esc 关闭)。 +- 选中项要有 `aria-checked` 或视觉高亮 + `aria-current`。 +- 焦点管理:打开时焦点移至菜单,关闭后返回触发器。 + +## 7. 测试要点(供 Agent 7 参考) + +- 切换即时生效(DOM 文案变化且无整页刷新)。 +- 刷新后保持(cookie/localStorage 恢复)。 +- `<html lang>` 与当前语言一致。 +- 切换不清空表单/不触发请求。 +- 当前语言在菜单中正确高亮。 +- aria-label 随语言变化。 +- 存储不可用时降级行为(见 `LOCALE_NAVIGATION_BEHAVIOR.md`)。 diff --git a/docs/i18n/LOCALE_NAVIGATION_BEHAVIOR.md b/docs/i18n/LOCALE_NAVIGATION_BEHAVIOR.md new file mode 100644 index 00000000000..849e98d4f71 --- /dev/null +++ b/docs/i18n/LOCALE_NAVIGATION_BEHAVIOR.md @@ -0,0 +1,104 @@ +# 语言导航行为定义(Locale Navigation Behavior) + +> 维护者:Agent 2(localization-designer) +> 状态:设计稿(G0 门禁交付物之一) +> 关联:`I18N_MULTI_AGENT_PLAN.md` §3.3、`DECISIONS.md` D5/D6/D7/D10、`LOCALIZATION_SPEC.md`、`LANGUAGE_SWITCHER_SPEC.md` +> 依据:本文件满足 G0 门禁中"Login、SSO、MCP OAuth 整页跳转后的语言保持规则已写入本地化与测试方案"。 + +本文档定义**语言在导航/跳转各场景下的期望行为、降级规则与可测试验收点**。静态导出、客户端收敛、无服务端 locale 路由是背景约束(D4/D6)。 + +--- + +## 1. 场景总览 + +| 场景 | 语言来源 | 期望结果 | +|---|---|---| +| 首次访问(无偏好) | 浏览器语言 → en | 按浏览器语言;中文浏览器默认 zh-CN | +| 后续访问(有偏好) | cookie/localStorage 偏好 | 保持已选语言 | +| 手动切换 | 用户选择 | 即时生效并持久化 | +| 页面内 SPA 导航 | 当前会话 | 语言跨页保持 | +| Login 提交回跳 | 同源存储 | 保持切换前语言 | +| SSO 登录回跳 | 同源存储 | 保持(见 §4 例外与降级) | +| MCP OAuth 授权回跳 | 同源存储 | 保持 | +| 存储不可用(隐私/禁 cookie) | 会话内存 | 会话内可切换,刷新后回退 | + +--- + +## 2. 首次访问 + +- 定义:无任何已存偏好(cookie 与 localStorage 均无 `litellm.locale`)。 +- 行为:读取 `navigator.language`(或 `navigator.languages` 首项)。以 `zh` 开头 → `zh-CN`;否则 → `en`。 +- 不弹出语言选择对话框;以浏览器语言为默认即可(`LOCALIZATION_SPEC.md` §1.2)。 +- 首次进入后会将所选语言**写回偏好**(可选),如此后浏览器语言变化不再覆盖。 +- 验收点 A1:见 §6。 + +## 3. 手动切换后的行为 + +- 切换即时生效(无刷新、无整页跳转),写入 localStorage + cookie(见 `LANGUAGE_SWITCHER_SPEC.md` §5)。 +- 同一标签页内 SPA 路由跳转(Leftnav 点击)语言保持;导航状态、表单、滚动不被清空。 +- 新开标签页/使用同一浏览器时,从偏好恢复。 +- 验收点 A2/A3:见 §6。 + +## 4. 整页跳转与回跳(Login / SSO / MCP OAuth) + +背景:SPA 内这些登录动作会离开当前页面去往后端/第三方,完成后再整页加载回跳地址。 + +### 4.1 期望 + +1. 跳转前语言已由客户端写入**同源 cookie + localStorage**。 +2. 回跳到同源页面后,语言初始化在客户端收敛阶段从偏好恢复,**语言应与跳转前一致**,不得回落到英文。 +3. Login 提交回跳、SSO 回跳、MCP OAuth 回跳三种都应能恢复(方案 §3.3 PoC 要求)。 + +### 4.2 例外与降级 + +- **Cookie 不可用 / 隐私模式**:localStorage 也可能受限。此时无法跨整页跳转持久化语言,回跳后按"无偏好"重估(浏览器语言→en)处理,属**可接受降级**,但必须**静默**,且不允许出现语言"闪烁"到错语言再纠正的体验。 +- **SSO 第三方域**(如外部 IdP):跳转发生在第三方域,前端无法携带语言 cookie 过去,但**回跳回同源**时仍在同源 cookie/localStorage 作用域内,可恢复。若 IdP 协商了完全不同的域且无同源 cookie,则按浏览器语言降级。 +- **首次在 Login 页选择语言后即整页跳转**:选择须在跳转前完成持久化,回跳恢复;这是 Login 页必须放切换器入口的原因(`LANGUAGE_SWITCHER_SPEC.md` §1)。 + +### 4.3 存储写入时机 + +为确保跳转前已持久化,语言偏好在**切换动作发生时立即写入**,而非等到刷新/跳转。禁止"仅内存 + 跳转前延迟写"导致竞态丢失。 + +- 验收点 A4/A5/A6:见 §6。 + +--- + +## 5. 存储不可用时的降级规则 + +| 失败类型 | 行为 | +|---|---| +| localStorage 写入抛异常/Safari 隐私模式 | 捕获异常静默跳过写入;会话内内存变量仍生效,当前页面可切换 | +| cookie 被禁/无法设置 | 同上,静默跳过;跨整页跳转可能会丢失语言(按 §4.2 降级) | +| 读取时两者都无 | 走"首次访问"逻辑(浏览器语言 → en) | +| 被第三方脚本/iframe 限制(`document.cookie` 不可读) | 视为无偏好,按浏览器语言;不抛错 | + +原则:**任何存储失败不得阻塞 UI 或抛出可见错误**;只影响持久化,不影响会话内切换。 + +--- + +## 6. 可测试验收点(可自动化 + 可手动) + +供 Agent 3/7 落地为用例,也作为 G0 后验证。 + +| ID | 验收点 | 期望 | +|---|---|---| +| A1 | 中文浏览器首次访问默认语言 | `navigator.language=zh-*` → 首屏为 zh-CN | +| A2 | 手动切换即时生效 | 选 zh-CN 后当前 DOM 文案变为中文,无整页刷新 | +| A3 | 刷新后保持 | 切换后刷新,仍为 zh-CN;`<html lang="zh-CN">` | +| A4 | Login 提交回跳语言保持 | Login 前选 zh-CN → 提交登录 → 回跳到 Dashboard 仍为 zh-CN | +| A5 | SSO 回跳语言保持 | 选 zh-CN 进入 SSO → 回跳后仍为 zh-CN | +| A6 | MCP OAuth 回跳语言保持 | MCP OAuth 授权回跳后仍为 zh-CN | +| A7 | 存储不可用降级 | 禁 cookie + 隐私模式:会话内可切换,刷新后按浏览器语言/en,不抛错、不闪烁 | +| A8 | 缺 zh-CN key 回退 | 仅 en 有某 key 时,zh 下该处回退英文,不显示 key | +| A9 | 首帧不闪 key | 首帧不显示原始翻译 key(ready 门禁,D7 实现后验证) | +| A10 | `<html lang>` 一致 | 任意语言下 `document.documentElement.lang` 与当前语言一致 | +| A11 | 切换不清空表单 | 选语言前填写的表单在切换后内容保留 | + +> 注意:A4/A5/A6 依赖真实登录/SSO/OAuth 回跳环境,PoC 阶段(Wave 1)由 Agent 1 先用"跳转-返回"模拟验证,A7/A9 可作为轻量替代证据,需 Agent 0 批准后视为已覆盖。 + +--- + +## 7. 风险与待定 + +- SSO 第三方域回跳的外域 cookie 是浏览器级限制,如实际不可恢复,需 Agent 0 批准记录为明确限制(方案 §11.7 允许)。 +- `html lang` 首屏同步在 SSR/hydration 下可能滞后,由 D7 首屏策略决定;不得因此回退翻译。 diff --git a/docs/i18n/LOCALIZATION_SPEC.md b/docs/i18n/LOCALIZATION_SPEC.md new file mode 100644 index 00000000000..dab11d83065 --- /dev/null +++ b/docs/i18n/LOCALIZATION_SPEC.md @@ -0,0 +1,252 @@ +# LiteLLM Dashboard 本地化规范(Localization Spec) + +> 维护者:Agent 2(localization-designer) +> 状态:设计稿,待 G0 评审 +> 范围:LiteLLM Dashboard 前端 UI 的 `en` / `zh-CN` 本地化 +> 关联:`I18N_MULTI_AGENT_PLAN.md`(§3.3/§3.4/§5.2)、`DECISIONS.md`、`LANGUAGE_SWITCHER_SPEC.md`、`GLOSSARY_EN_ZH.md` + +本文档定义语言的**交互规则**与**中文文案风格标准**。它回答"什么时候用什么语言"以及"中文应该怎么写得专业"。具体每个页面/组件翻不翻、翻哪些,见 `V1_TRANSLATION_SCOPE.md`;语言切换器的 UI 细节见 `LANGUAGE_SWITCHER_SPEC.md`。 + +--- + +## 1. 语言规则 + +### 1.1 语言标识与优先级 + +- 语言代码:`en`(默认)、`zh-CN`。 +- `en` 是**真源与最终回退**;任何缺少 `zh-CN` key 的字符串一律回退英文,绝不允许显示原始 key。 +- 语言优先级(D5,已定): + ``` + 用户主动选择 + → 已确认的用户级 UI 设置(如后端支持,v1 默认不启用) + → cookie + localStorage + → 浏览器语言 + → en + ``` +- 语言偏好判定完全在客户端完成(静态导出约束,D4)。 + +### 1.2 首次进入 + +- 无任何已存偏好时:读取浏览器语言。若 `navigator.language` 以 `zh` 开头(`zh`、`zh-CN`、`zh-TW`、`zh-Hans` 等均视为中文系),则初始为 `zh-CN`;否则为 `en`。 +- 首次进入**不强制弹出语言选择对话框**。以浏览器语言为默认即可;用户可随时通过切换器更改。 +- 首次进入后(即一旦存在已存偏好),优先使用已存偏好,不再读取浏览器语言。 + +### 1.3 手动切换 + +- 切换**即时生效**,无需刷新、无需整页跳转;当前页面所有文案立即切换。 +- 切换时**保留表单状态、滚动位置、导航展开状态**,不允许因语言切换重置表单或触发异常请求(v1 完成定义第 6 条)。 +- 切换后立即写入 cookie + localStorage(行为见 `LANGUAGE_SWITCHER_SPEC.md`)。 + +### 1.4 整页跳转与回跳(Login/SSO/MCP OAuth) + +详细行为与降级见 `LOCALE_NAVIGATION_BEHAVIOR.md`。摘要: + +- Login 提交、SSO 登录、MCP OAuth 授权可能发生整页跳转(起跳自同源页面,回跳到同源页面)。 +- 跳转返回后必须从**同源 cookie 或 localStorage** 恢复语言,语言不应回落到英文。 +- 若同源存储不可用(隐私模式、禁 cookie),允许回退英文,但不允许出现语言"闪烁"到错误语言的体验缺陷。 + +### 1.5 构建期元数据 + +- v1 构建期 `<title>` 与 metadata description **保持英文**(D8),不做多语言 SEO。功能 Agent 不得被动扩大此范围。 + +--- + +## 2. 中文文案风格规范 + +目标:**专业、自然、克制、无翻译腔**。面向开发者/运维人员(Dashboard 用户以工程师为主),用词应准确、简洁,避免口语化和过度文学化。 + +### 2.1 通用原则 + +1. **以用户动作和对象为先**。按钮用"动词 + 宾语"最短形式(见 2.2)。 +2. **不用翻译腔**:避免"为了……我们将会……"、"请注意"、"请务必"等冗余;避免英文逐字直译的语序污染。 +3. **控制长度**:中文比英文短,但信息密度高。菜单项/按钮 ≤ 6 字为佳,超过 8 字注意换行与截断(见 §3)。 +4. **术语一致**:一律采用 `GLOSSARY_EN_ZH.md` 中的词条,禁止同义词漂移(如"Spend"一会翻"花费"一会翻"支出")。 +5. **敬语从简**:界面动作祈使句即可,不加"您"的过度堆叠;保留必要礼貌但不每一句都加。 +6. **标点**:中文使用全角标点(`,`、`。`、`:`、`?`),英文/数字/型号与中文混排时两侧不加空格;数字、单位用半角(`10 次` 中空格与否按项目 UI 惯例统一)。 +7. **占位符与插值**:不译 `{{variable}}`、`{{count}}` 内的变量名;品牌名 LiteLLM、OpenAI 等不译。 + +### 2.2 按钮文案 + +- 格式:**动词(+ 宾语)**,祈使句。 +- 通用按钮术语(复用 `GLOSSARY_EN_ZH.md` + `common.json`): + | EN | ZH-CN | + |---|---| + | Save | 保存 | + | Cancel | 取消 | + | Delete | 删除 | + | Create | 创建 | + | Add | 添加 | + | Edit | 编辑 | + | Update | 更新 | + | Enable / Disable | 启用 / 停用 | + | New Virtual Key | 新建虚拟密钥 | + | Generate | 生成 | + | Copy | 复制 | + | Close | 关闭 | + | Confirm | 确认 | + - 含受保护动作(删除、重置、清空)的按钮在确认弹窗中保持同一译名,不要换成同义"移除/清除"造成语义漂移。 + +### 2.3 表单文案 + +- **Label**:名词性短语,简洁。如 `Deployment Name` → `部署名称`、`Base URL` → `基础 URL`(URL 保留英文,见 §4)。 +- **Placeholder**:用"请输入 / 请选择 / 例如"引导的示例或提示。如 `Enter model ID` → `输入模型 ID`;`Select team` → `选择团队`。 +- **校验提示**:指出问题 + 期望,避免命令式指责。 + - 示例:`This field is required` → `此项为必填`;`Key must be at least 8 characters` → `密钥至少需要 8 个字符`。 +- **可选/必填**:`Required` → `必填`;可选字段不要写"可选"二字堆满表单,仅在视觉区分或用 `(可选)` 后缀在 label 上。 +- **分段标题/说明**:`form.helper` 类说明文本可译,但保留关键英文专有名词(API、URL、SSO)。 + +### 2.4 错误提示 + +- **技术性错误**(后端返回、非 4xx 校验错误):尽量提供"发生了什么 + 可做什么"两层。 +- **不可直翻后端消息**:后端英语错误原文**不翻译**(见 §4)。即使附带中文说明,也应保留原文以便排障。 +- 措辞平实。示例: + - `Unable to load models.` → `无法加载模型。` + - `Your session has expired.` → `会话已过期。` + - `Failed to create Virtual Key.` → `创建虚拟密钥失败。` + - `Insufficient permission.` → `权限不足。`(不写"您的权限不够") +- 避免把错误写成"出错了"这类无信息量提示;能带对象就带对象(`删除团队失败`)。 + +### 2.5 空状态(Empty State) + +- 空状态 = 说明 + 一句引导(一个主导 CTA)。 +- 避免"这里空空如也"这种口语;用客观陈述 + 引导。 + - 示例:`No Virtual Keys yet. Create one to get started.` → `暂无虚拟密钥。点击"新建虚拟密钥"开始使用。` + - `No usage data for the selected period.` → `所选时间段内暂无用量数据。` +- 引导动词与真实按钮文字保持一致("新建虚拟密钥")。 + +### 2.6 成功提示 / 通知(Toast) + +- 过去式主谓短句。如 `Virtual Key created.` → `虚拟密钥已创建。`、`Settings saved.` → `设置已保存。` +- 需要引用对象名称/ID 时用插值:`Model "{{name}}" saved.` → `模型"{{name}}"已保存。` + +--- + +## 3. 中文长度对布局的影响与处理 + +中文单字约占英文一个字符 ~2 倍视觉宽度,但信息密度高,实际渲染宽度通常**更短或相当**。真正的风险是: + +1. 英文长复合词被较长的中文词组替换; +2. 筛选器、表格列头、弹窗标题空间不足导致换行/溢出。 + +### 3.1 导航(Leftnav / Navbar / 面包屑) + +- 菜单项普遍 ≤ 6 字(如 `Virtual Keys`→`虚拟密钥`、`Models + Endpoints`→`模型与端点`),通常放得下。 +- 侧边栏已具备 `truncate`(`flex-1 truncate`),中文过长时应保持单行截断,并补 `title` 展示全称。 +- 分组标签(AI GATEWAY 等)v1 也翻译;groupLabel 是配置常量,需随 `menuGroups` 一起 I18N 化,而非仅改渲染层。 +- 面包屑 section/title 同样取翻译,需与导航使用同一套 key,避免两处不一致。 + +### 3.2 表格 + +- 列头名词、数值列(用量/成本/花费用数字格式化,见 `V1_TRANSLATION_SCOPE.md` 插值注意事项)。 +- 表格单元格回退策略:**列头可换行,单元格优先截断 + `title` 全称**;避免单元格内文字撑爆列宽。 +- 数据列(模型 ID、团队名、时间戳)」多为数据,不翻译,按 §4 处理。 + +### 3.3 弹窗 / 抽屉(Modal / Drawer) + +- 标题 ≤ 20 字;超长标题强制换行并提供 `line-clamp` 或在布置上预留两行。 +- 按钮组"取消/保存"不加长词缀,保持最短形式。 +- 弹窗内说明文本允许自然换行,但禁止水平溢出。 + +### 3.4 全体回退策略(截断 / 换行 / 省略号) + +| 场景 | 策略 | +|---|---| +| 导航菜单单行 | `truncate`(省略号) + `title` 全称 | +| 表格单元格 | 截断 + `title`;数值列右对齐 | +| 弹窗标题 | 允许换行(白空间关键字 `[normal, pre-wrap]`),必要时 `line-clamp-2` | +| 按钮 | 不换行;过长时缩短译名,不拼接 | +| 标签/Badge(Beta、New) | 不翻译品牌徽标;`v{major.minor}` 保持 | + +--- + +## 4. 不可翻译内容清单 + +以下内容**明确不翻译**(作为资源值原样保留或直接放数据字段,不进翻译 key)。Agent 5/6 必须遵守,Review 时逐条核查: + +1. **模型名 / Model ID / Deployment 名 / Provider 名**:`gpt-4o`、`claude-3-5-sonnet`、`openai`、`bedrock` 等;用户自定义的 Deployment/模型名也是数据,不译。 +2. **API 字段名 / 参数名**:`key`、`model`、`spend`、`tpm_limit`、`rpm_limit`、`metadata`、`created_at`;接口返回的键与 JSON 结构不译。 +3. **请求/响应体、日志原文**:后端返回的原始日志、错误消息、堆栈(stack trace)、trace 内容保持原样。 +4. **代码示例与终端命令**:curl 片段、Python/JS 代码、API 请求示例不译;但代码**上方/下方的说明文字**可译。 +5. **URL、路径、文件名**:`https://docs.litellm.ai`、`/api/`、`config.yaml`、`.env` 等。 +6. **广泛公认的英文技术缩写**:API、URL、SSO、MCP、OAuth、REST、HTTP、SQL、ID、JSON、SDK。 +7. **品牌名**:LiteLLM、OpenAI、Anthropic、Virtual Key(作为产品专名,见下表备注)、Guardrails 的专有规则名。 +8. **数值、日期、货币、单位**:数字格式化走 i18n 的 number/date/currency,不手工拼翻译;`{{count}}` 由计数插值负责。 +9. **角色名 / 权限标识**:`admin`、`internal_user`、`team_admin` 等代码值保持英文(仅其人类可读显示名按术语表翻译)。 +10. **徽标与 Beta/New 标记**:`Beta`、`New` 徽标不译(视觉因子,保持英文原样)。 + +> 原则:**翻译的是界面文案(UI string),不是数据(data string)与协议内容(protocol content)**。无法安全区分时,优先看它是否来自后端/用户数据 —— 是则不译。 + +--- + +## 5. zh-CN 计数与量词策略 + +遵循 D9:**不建立形态复数分支**,用"计数插值 + 量词"。i18next 复数字符串里 `_one/_other` 仅用于英文;`zh-CN` 用一个字符串承载 `{{count}}` + 量词即可。 + +量词选取规则: + +- 可数离散对象:`个`(模型、密钥、请求、团队、用户、条目)。 +- 键值/集合类(如导入项):可用 `个` 或直接按对象数合身处理。 +- 时间/次数:`次`(请求次数、重试次数)。 +- 金额/用量单位化:不写量词,直接 `{{count}}` + 单位(`{{count}} 请求`、`{{count}} 个请求`,口语一致性以术语表为准)。 +- `0` 时量词仍正常(`0 个请求` 在中文是自然的,不需要单独复数分支)。 + +### 5.1 具体示例 + +| 场景 | EN(_one/_other) | ZH-CN(单字符串) | +|---|---|---| +| 请求数 | `{{count}} request` / `{{count}} requests` | `{{count}} 个请求` | +| 选中模型 | `{{count}} model selected` / selected | `已选择 {{count}} 个模型` | +| 待办项 | `{{count}} item` / items | `{{count}} 个条目` | +| 无选中 | — | `已选择 0 个模型`(中文自然,无需分支) | +| 重试 | `Retry ({{count}})` | `重试({{count}} 次)` | + +### 5.2 测试要求 + +计数测试至少覆盖 `count=0`、`count=1`、`count=2`(方案 §3.4)。对 `zh-CN` 断言渲染结果等于"插值 + 量词"形态(如 `1 个请求`、`2 个请求`),**不**断言存在 `_one/_other` 分支。 + +--- + +## 6. 英文原句 → 规范中文译文示例 + +给开发 Agent 5/6 的参照样本(来自 v1 高频界面)。 + +| # | EN(原句) | ZH-CN(规范译文) | 说明 | +|---|---|---|---| +| 1 | `Create new virtual key` | 新建虚拟密钥 | 动词+宾语,最短祈使形式 | +| 2 | `No Virtual Keys found. Click "New Virtual Key" to create your first one.` | 暂无虚拟密钥。点击"新建虚拟密钥"创建你的第一个密钥。 | 空状态客观陈述 + 引导 CTA,CTA 文字与真实按钮一致 | +| 3 | `This will permanently delete this model and cannot be undone.` | 此操作将永久删除该模型,且无法撤销。 | 机构条理清晰,动作对象明确 | +| 4 | `You've spent $12.34 on Usage this month.` | 本月用量已花费 $12.34。 | 过去式主谓短句,接数值/货币(货币由 i18n 格式化) | +| 5 | `Drop any model to see how it compares.` | 拖入任意模型查看对比结果。 | 祈使引导,无翻译腔 | +| 6 | `Your changes have not been saved.` | 更改尚未保存。 | 客观陈述,不指责 | + +> 译名对照一律以 `GLOSSARY_EN_ZH.md` 为准,本示例中的词汇(虚拟密钥、用量、花费、拖入、对比)与术语表保持一致。 + +--- + +## 7. 可访问性(a11y)文案规范 + +- 所有 `aria-label`、`title`、`alt`、placeholder 中的用户可见文案**同样翻译**(方案 §5.5),与可见文本用同一 key。 + - 例:当前 `aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}` → `展开侧边栏` / `收起侧边栏`。 +- 语言切换器自身提供 `aria-label`(见 `LANGUAGE_SWITCHER_SPEC.md`)。 +- 图标按钮(无可见文字)必须本地化 `aria-label`;纯装饰图标保持 `aria-hidden`。 +- 保留英文专有名词的缩写不影响屏幕阅读器核心语义。 + +--- + +## 8. 对开发 Agent 的强制要求(Agent 5/6) + +1. **只翻 UI 文案**,不翻 §4 清单内容(模型名、API 字段、日志、代码、URL、缩写、品牌)。 +2. **key 与字典一致性**:中英文 key 集合必须一致;缺 zh-CN 时回退 en,不回退失败显示 key。 +3. **写 key 不写句子到代码**:用语义 key(`navigation:item.virtualKeys`),不把英文句子当 key。 +4. `<Trans>` 用于含链接/强调/嵌套,`t()` 用于纯字符串;使用 `<Trans>` 前确保资源就绪(ready 门禁,避免首帧闪 key)。 +5. **同一译名贯穿全局**:按钮、空状态、确认框、Toast 对同一动作/对象用词必须一致(对照术语表)。 +6. **数值/日期/货币用 i18n 格式化**,不硬编码。 +7. **翻译 a11y 文案**(aria-label/title/placeholder)。 +8. **不改变业务行为**:翻译不改路由、不改校验逻辑、不改提交逻辑,仅替换展示文案。 +9. 交付时在 `V1_SCOPE_MANIFEST.md`(Agent 0 维护)对应行填 EN/ZH-CN 文案盘点;译名异常情况上报 Agent 2 复核。 + +--- + +## 9. 术语表引用 + +术语定义与单一词条见 `GLOSSARY_EN_ZH.md`。若本规范内个别短语与术语表冲突,以 `GLOSSARY_EN_ZH.md` 为准,并提请 Agent 2 修订。 diff --git a/docs/i18n/MASTER_PLAN.md b/docs/i18n/MASTER_PLAN.md new file mode 100644 index 00000000000..a88b1f5808d --- /dev/null +++ b/docs/i18n/MASTER_PLAN.md @@ -0,0 +1,30 @@ +# LiteLLM Dashboard 中英文国际化 — MASTER_PLAN + +> 总控:Agent 0(`i18n-lead-reviewer`) · 由当前主智能体承担 +> 基线:`I18N_MULTI_AGENT_PLAN.md` v1.4(评审通过,可执行) +> 目标:Dashboard 前端 UI 支持 `en` / `zh-CN`,导航、登录、Models、API Keys、Usage、Cost、Budgets 优先中文化。 + +## 波次总览 + +| 波次 | 内容 | 涉及角色 | 门禁 | +|---|---|---|---| +| Wave 0 | 基线评审 + 并行设计 | A0 + A1/A2/A3 | G0 | +| Wave 1 | 平台能力与测试基础 | A0 + A4/A5(只读)/A7 | G1 | +| Wave 2 | 第一批功能并行开发 | A0 + A5/A6/A7 | G2 | +| Wave 3 | 第二批功能并行开发 | A0 + A6A/A6B/A7 | G3 | +| Wave 4A/4B | 集成验收 + 定向缺陷修复 | A0 + A2/A7/A8(及开发 Owner) | G4 | + +## 并发约束 +同刻最多 4 个智能体(1 总控 Agent 0 + 最多 3 执行子代理)。子代理按波次由 Agent 0 拉起,完成即回收。 + +## 当前状态 +(由 Agent 0 在每波次更新) + +- [ ] Wave 0 进行中 +- [ ] Wave 1 待开始 +- [ ] Wave 2 待开始 +- [ ] Wave 3 待开始 +- [ ] Wave 4 待开始 + +## 完成定义 +见 `I18N_MULTI_AGENT_PLAN.md` §11(v1 完成定义 15 条)。 diff --git a/docs/i18n/POC_REPORT.md b/docs/i18n/POC_REPORT.md new file mode 100644 index 00000000000..98bb5e2b97c --- /dev/null +++ b/docs/i18n/POC_REPORT.md @@ -0,0 +1,100 @@ +# LiteLLM Dashboard i18n — PoC 验证清单与执行步骤(POC_REPORT) + +> 角色:Agent 1(`i18n-architect`)编写 PoC 必验项;Wave 1 的 Agent 4(平台)与 Agent 7(测试)照此执行并把证据回填到「结果 / 证据」栏。 +> 约定:PoC-* 为「必验项」,每项都必须给出**方法**、**预期证据**、**结果**(通过/失败/未执行)。 +> 本机(Agent 1 仅只读)已就地验证的项,标记 **「本机已验证」**;其余标记 **「待执行」**(执行者为 A4/A7)。 + +> 目标:在改动最少的前提下,对 `I18N_TECH_DESIGN.md` 的**关键设计假设**给出可复现证据,支撑 G1 门禁(ADR-i18n-01/03/04/05 转 Accepted)。 +> 前置:Wave 1 平台基线由 A4 建立(`src/i18n/**` + `src/locales` 骨架 + 依赖写入,遵循 D12/FILE_OWNERSHIP)。 + +--- + +## 0. 环境前提(A4 先落实) + +- 依赖:`i18next@^26.4.2` + `react-i18next@^17.0.13`(写入后运行 `npm ci` 于独立 worktree,禁止并行锁分叉——D12/v1.4)。 +- `next.config.mjs`:**不改**(静态导出已满足)。 +- 仅改动 `src/i18n/**`、`src/locales/**`、`src/app/layout.tsx`(挂 Provider)、语言切换器。 + +--- + +## PoC-1:静态导出构建成功 && i18next 依赖可解析(G1 前置) + +- **方法**:A4 在平台 worktree `npm run build`(`output:"export"`)。检查:构建无因引入 i18next 产生的解析/打包错误;`out/` 正常产出;`turbopack` 能打包 json 资源。 +- **预期证据**:`next build` 成功,`out/` 目录生成,无「Cannot resolve i18next/react-i18next」、无 static-export 舞台报错;`npm ls i18next react-i18next` 无 peer 冲突。 +- **结果**:【待执行】 + +## PoC-2:首屏策略(就绪门禁 + `<html lang>` 同步)行为 + +- **方法**:默认浏览器语言 en:加载页面,在首帧与 JS 就绪后截图/断言。 + 1. en 用户:首帧即为中文 UI 不存在(应显示英文 UI),`<html lang="en">` 保持。 + 2. zh-CN 偏好用户(预置 `dashboard.locale=zh-CN` 再刷新):JS 就绪前不渲染业务 `t()` 内容(就绪态/空),就绪后一次性渲染中文,且 `document.documentElement.lang === 'zh-CN'`。 +- **预期证据**:截图对比 + DOM 断言:①en 首帧无 key/英文句闪烁;②zh 就绪前无业务文案、就绪后 `lang` 正确;③测量就绪门禁额外延时(performance 日志)记录数值,供评审接受性判断。 +- **结果**:【待执行】 + +## PoC-3:`t()` / `<Trans>` 首帧不暴露原始 key(资源就绪门禁) + +- **方法**:在导航/登录样面的一个组件用 `t('common:title')` 与一个 `<Trans>`。用 Playwright 在**首帧**(未就绪)与就绪后采样 DOM 文本。 +- **预期证据**:首帧 DOM 中**不含** `common:title`、不含未翻译英文句子(即不以 key=原文);就绪后显示译文。如有 `returnNull`,首帧对应节点为空而非显示 key。 +- **结果**:【待执行】 + +## PoC-4:缺 key 英文回退(D2) + +- **方法**:在 zh-CN 资源中删掉某个 en 有的 key(临时),UI 切 zh-CN,观察该字符串。 +- **预期证据**:该串回退英文(`fallbackLng:'en'`),**不**崩、不显示 key、控制台(dev)有 missingKey warning。 +- **结果**:【待执行】 + +## PoC-5:刷新后语言偏好保留(D6/D5) + +- **方法**:切 zh-CN → 刷新 → 断言仍 zh-CN;清空 cookie 但留 localStorage → 刷新 → 仍 zh-CN(双层读取);两者都清 → 回退浏览器语言/en。 +- **预期证据**:各分支刷新后语言正确;`dashboard.locale` cookie 存在且 `SameSite=Lax`。 +- **结果**:【待执行】 + +## PoC-6:英文为首帧默认、无偏好时按浏览器语言(D5/D2) + +- **方法**:无任何偏好 cookie/localStorage;浏览器 `navigator.language` 分别设 en、zh-CN、zh、zh-Hans、fr。 +- **预期证据**:en→英文;zh/zh-Hans→zh-CN;fr(不支持)→英文。首帧英文合法,无闪烁。 +- **结果**:【待执行】 + +## PoC-7:整页跳转语言恢复(Login/SSO/MCP OAuth 回跳,D10) + +- **方法**:在 `(dashboard)` 中切 zh-CN → 触发到 `/login` 或模拟 SSO/OAuth 全页跳转(改变 URL/全量 reload)→ 返回原页。 +- **预期证据**:回跳后仍 zh-CN(从同源 cookie 恢复),`<html lang>` 一致,无 key 闪烁;未登录直登场景与 OAuth 回跳分别覆盖。 +- **结果**:【待执行】 + +## PoC-8:中文量词/计数(D9)在真实组件表现 + +- **方法**:在带 `{{count}}` 的计数展示(如 usage 表格)切 zh-CN,覆盖 count=0/1/2。 +- **预期证据**:输出如「0 个 / 1 个 / 2 个」,无英文复数 s;量词与数为「数+量」顺序正确出现。 +- **结果**:【待执行】 + +## PoC-9:toast / 构建产物不含未翻译硬编码(回归) + +- **方法**:扫描构建后产物与关键页面文本,确认没有「key 串」残留为可见 UI 文本。 +- **预期证据**:`grep` 产物无 `\w+:\w+\.` 形式的 key 作为文本暴露;关键页面无英文句被用户看到(除 `title`/meta 英文外)。 +- **结果**:【待执行】 + +--- + +## 本机已验证项(Agent 1,只读,2026-09-09) + +| # | 项 | 验证方式 | 结果 | +|---|---|---|---| +| 1 | `react-i18next@17.0.13` 兼容 React 19 / TS 5 | `npm view react-i18next peerDependencies` → `react>=16.8.0`, `i18next>=26.2.0`, `ts ^5\|\|^6\|\|^7`;项目 React 19.2.8 / TS 5.9.3 满足 | 通过 | +| 2 | `i18next@26.4.2` 无冲突 peer | `npm view i18next@latest peerDependencies` → 仅 `typescript` | 通过 | +| 3 | 根 Provider 链结构 | 读 `src/app/layout.tsx`:`ThemeProvider→NuqsAdapter→ReactQueryProvider→AuthProvider`;`<html lang="en" suppressHydrationWarning>`;metadata 英文 | 确认 | +| 4 | `src/i18n` / `src/locales` 当前不存在(基线干净) | `ls` | 确认(Wave0 从零建) | +| 5 | 路由组 layout 分布 | 根 / `(dashboard)` / `chat` / `connect` 各有一 `layout.tsx`;`(dashboard)` 为 use client 且另含 SidebarProvider/ThemeContext/PluginModeContext | 确认(接缝见设计 §8) | +| 6 | 后端无 `UI settings.language` | `UISettings` 白名单 `ALLOWED_UI_SETTINGS_FIELDS` 不含 `language`;`/get/ui_settings` 由 `LiteLLM_UISettings` 表支撑 | 确认(→ADR-i18n-07) | +| 7 | next export 配置 | `next.config.mjs`: `output:"export"`, `trailingSlash:true`, `images.unoptimized` | 确认(静态导出约束成立) | + +## 待执行(Wave 1 A4/A7 回填) + +- PoC-1 .. PoC-9 均【待执行】;其中 PoC-1(构建+依赖)、PoC-2/3(首屏与门禁)、PoC-5(刷新)、PoC-7(跳转恢复)为 **G1 关键门禁**,优先完成并回填证据文件(证据文件命名遵从 v1.4 §11 具名证据约定,由 Agent 3/7 落地)。 + +--- + +## 建议的 PoC 测试层级归属(供 Agent 3 细化) + +- 单测(unit):`localePreferences.ts`(D5 优先级)、`detectLocale.ts`(`zh`→`zh-CN`)、`i18n.ts` 实例化。 +- 集成:`I18nProvider` 就绪门禁渲染(PoC-3)、Layout 挂 Provider 后 `useTranslation` 可用。 +- E2E(`tests/e2e/ui/`,Playwright 对 live proxy):PoC-2/5/6/7(首屏、刷新、浏览器语言、整页回跳)。 diff --git a/docs/i18n/REGRESSION_MATRIX.md b/docs/i18n/REGRESSION_MATRIX.md new file mode 100644 index 00000000000..49b794d1c0c --- /dev/null +++ b/docs/i18n/REGRESSION_MATRIX.md @@ -0,0 +1,58 @@ +# i18n 回归测试矩阵(REGRESSION_MATRIX.md) + +> 角色:Agent 3 `qa-architect`;执行:Agent 7 `i18n-qa`(Wave 4A 完整回归) +> 依据:`I18N_MULTI_AGENT_PLAN.md` §11 完成定义、§10 测试重点;`TEST_CASES.md` +> 用法:按"页面 × 关键行为"逐格勾选。行为列引用 TC 编号;页面以 v1 范围为准。每格填:`✅ 通过 / ❌ 失败(#缺陷) / ➖ 不适用 / (m) 人工`。 + +## 行为列(横轴)编号 + +| 列 | 行为 | 主要 TC | 层级 | +|---|---|---|---| +| B1 | 默认语言 en | TC-01/02 | e2e/unit | +| B2 | 切换即时生效 + `<html lang>` | TC-03/08 | e2e/component | +| B3 | 刷新保持 | TC-04/05 | e2e/unit | +| B4 | 缺 key 回退英文、不显示原始 key | TC-06/07 | e2e/component | +| B5 | 动态数量/日期/数字/货币本地化(含 count=0/1/2) | TC-09~13 | unit | +| B6 | 模型名/API 字段/日志/代码示例不误翻 | TC-20/21 | integration | +| B7 | a11y 文案本地化(aria-label/title/placeholder/error) | TC-23 | component/integration | +| B8 | 中文布局不溢出/截断/遮挡 | TC-24 | integration/e2e+(m) | +| B9 | 切换语言不清空表单/不触发异常请求 | TC-14/15 | integration/e2e | +| B10 | 整页跳转(Login/SSO/MCP OAuth)回跳语言保持 | TC-16~19 | e2e | + +## 矩阵(页面 × 行为) + +| 页面/区域 | Owner | B1 | B2 | B3 | B4 | B5 | B6 | B7 | B8 | B9 | B10 | Review | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| Navbar | Agent 5 | | | | | | | | | | ➖ | | +| Leftnav | Agent 5 | | | | | | | | | | ➖ | | +| 用户菜单 / 全局壳层 | Agent 5 | | | | | | | | | | ➖ | | +| 语言切换器 | Agent 5 | | | | | | | | | ➖ | | | +| Login | Agent 5 | | | | | | | | | | | | +| Onboarding | Agent 5 | | | | | | | | | | | | +| Connect | Agent 5 | | | | | | | | | | | | +| MCP OAuth | Agent 5 | | | | | | | | | | | | +| Models and Endpoints | Agent 6 | | | | | | | | | | | | +| API Keys | Agent 6 | | | | | | | | | | | | +| Usage | Agent 6A | | | | | | | | | | | | +| Cost Tracking | Agent 6A | | | | | | | | | | | | +| Budgets | Agent 6B | | | | | | | | | | | | +| 全局:构建期 `<title>`/meta 英文 | Agent 4/8 | | | | | ➖ | | | | ➖ | ➖ | | + +## 平台层回归(不绑定单页面) + +| 项目 | 说明 | TC | 结果 | +|---|---|---|---| +| 默认语言 en(无偏好) | 全站 | TC-01 | | +| `<html lang>` 一致 | 全局 | TC-08 | | +| 刷新保持 | 全局 | TC-04 | | +| 缺 key 英文回退 | 全局 | TC-06 | | +| 不显示原始 key(首帧/全程) | 全局 | TC-07/26 | | +| 静态产物 key 泄漏 / `<title>` 英文 | `out/` | TC-22/26 | | +| en/zh key 集合一致 | 全部 namespace | TC-25 | | +| 硬编码扫描报告 | 源码 | T-03 | | + +## 门禁映射 + +- 本矩阵在 **Wave 4A** 全量执行(Agent 7 完成 + Agent 8 汇总 + Agent 2 体验复核 + Agent 0 终审)。 +- 对应完成定义(§11):支持双语言(1)、切换即时+刷新保持(2)、v1 页面中文化(3)、回退不露 key(4)、中文无严重截断重叠(5)、切换不清表单(6)、整页跳转保持(7)、`<title>`/meta 英文(8)、模型名/API 不误翻(9)、相关测试通过(10)、lint/format/build(11)。 +- 任何 `❌` 须挂缺陷进 `TASK_BOARD.md`,P0/P1 清零后才满足 G4。 diff --git a/docs/i18n/TASK_BOARD.md b/docs/i18n/TASK_BOARD.md new file mode 100644 index 00000000000..58e722af1f2 --- /dev/null +++ b/docs/i18n/TASK_BOARD.md @@ -0,0 +1,57 @@ +# LiteLLM Dashboard i18n — TASK_BOARD(多智能体进展看板) + +> 维护者:Agent 0。**每启动/交付/Review 一个 Agent 即更新本表**。这是你查看"谁在工作、进展到哪"的单点真相。 +> 状态取值:待命 / 进行中 / 待 Review / 已完成 / 被阻塞 + +## Wave 0 — 并行设计(G0 门禁前) + +| Agent | 名称 | 状态 | worktree/分支 | 交付物 | 最后更新 | +|---|---|---|---|---|---| +| A1 | i18n-architect | **已完成 ✅(G0 Review 通过)** | (纯设计) | I18N_TECH_DESIGN / I18N_ADR / POC_REPORT / TECH_RISKS | 2026-09-09 G0 | +| A2 | localization-designer | **已完成 ✅(G0 Review 通过)** | (纯设计) | LOCALIZATION_SPEC / GLOSSARY_EN_ZH(63条) / LANGUAGE_SWITCHER_SPEC / V1_TRANSLATION_SCOPE / LOCALE_NAVIGATION_BEHAVIOR | 2026-09-09 G0 | +| A3 | qa-architect | **已完成 ✅(G0 Review 通过)** | (纯设计) | I18N_TEST_PLAN / TEST_CASES(26) / TEST_TOOLS / REGRESSION_MATRIX | 2026-09-09 G0 | + +**G0 门禁结论(Agent 0,2026-09-09):APPROVED** +- 首屏策略已选定「就绪门禁 + `<html lang>` 同步」(ADR-04,非"再评估")。 +- 后端无 `UI settings.language`(ADR-07 Accepted)。 +- 构建期 `<title>`/meta 保持英文边界已记录(ADR-06 Accepted)。 +- 整页跳转语言保持规则已写入本地化与测试方案(LOCALE_NAVIGATION_BEHAVIOR + TEST_CASES TC-16..18)。 +- E2E 基建缺口已定:选 **a(Wave 1 补齐 Playwright)**,见 DECISIONS P3/P4。 +- **G0 Review 发现 1 个需修正点(P5)**:语言偏好存储键名跨文档不一致(`dashboard.locale` vs `litellm.locale`)。**已定统一为 `litellm.locale`**,由 A4 以单一常量实现,A5/6 不直接操作存储。 +- G0 Review 确认 POC_REPORT 9 项必验项已具备(待 Wave 1 A4/A7 回填证据)。 + +## Wave 1 — 平台能力与测试基础 + +| Agent | 名称 | 状态 | worktree/分支 | 交付物 | 最后更新 | +|---|---|---|---|---|---| +| A4 | i18n-platform-developer | 待命 | i18n/w1-agent4-platform | src/i18n/** 平台代码 + PLATFORM_VALIDATION_REPORT | - | +| A5 | i18n-shell-auth-developer | 待命(Wave1 仅只读盘点) | — | key 拟定清单(不写码) | - | +| A7 | i18n-qa | 待命 | i18n/w1-agent7-qa | 平台测试 + key 一致性工具 | - | + +## Wave 2 — 第一批功能 + +| Agent | 名称 | 状态 | worktree/分支 | 交付物 | 最后更新 | +|---|---|---|---|---|---| +| A5 | i18n-shell-auth-developer | 待命 | i18n/w2-agent5-shell | 壳层 + common/navigation/auth namespace | - | +| A6 | i18n-feature-developer | 待命 | i18n/w2-agent6-models | Models + API Keys | - | +| A7 | i18n-qa | 待命 | i18n/w2-agent7-qa | 持续测试 | - | + +## Wave 3 — 第二批功能 + +| Agent | 名称 | 状态 | worktree/分支 | 交付物 | 最后更新 | +|---|---|---|---|---|---| +| A6A | i18n-feature-developer (Usage/Cost) | 待命 | i18n/w3-agent6a-usage | Usage + Cost Tracking | - | +| A6B | i18n-feature-developer (Budgets) | 待命 | i18n/w3-agent6b-budgets | Budgets | - | +| A7 | i18n-qa | 待命 | i18n/w3-agent7-qa | 回归 | - | + +## Wave 4 — 集成验收 + +| Agent | 名称 | 状态 | 交付物 | 最后更新 | +|---|---|---|---|---| +| A2 | localization-designer | 待命 | 术语/中文体验复核 | - | +| A7 | i18n-qa | 待命 | 完整回归 + 质量报告 | - | +| A8 | i18n-integration-release | 待命 | 发布/回滚清单 | - | + +## 缺陷队列 +(Wave 4B 按 P0 → P1 → 阻塞门禁 P2 → 其他 P2 排序) +(空) diff --git a/docs/i18n/TECH_RISKS.md b/docs/i18n/TECH_RISKS.md new file mode 100644 index 00000000000..74b714f2618 --- /dev/null +++ b/docs/i18n/TECH_RISKS.md @@ -0,0 +1,109 @@ +# LiteLLM Dashboard i18n — 技术风险清单(TECH_RISKS) + +> 角色:Agent 1(`i18n-architect`)· Wave 0 +> 编号 R1..Rn。每条含:风险 / 影响 / 缓解 / Owner。Owner 为「最需要跟踪/缓解」的角色,不一定是唯一。 +> 关联:`I18N_TECH_DESIGN.md`、`I18N_ADR.md`、`POC_REPORT.md`。 + +--- + +## R1:静态导出下首屏语言闪烁 / 错误语言闪现 + +- **风险**:切 zh-CN 用户首帧可能短暂显示英文或暴露 key,体验倒退。D7 要求不得停留在「再评估」。 +- **影响**:高(首屏是 G0/G1 门禁项;直接违背 §3.1)。 +- **缓解**:已选定「语言就绪门禁 + 挂载后同步 `<html lang>`」(ADR-i18n-04);就绪前不渲染业务 `t()`/`<Trans>`(ADR-i18n-03)。PoC-2/3 验证并量化门禁延时。 +- **Owner**:A4(实现)→ A7(验证)。 + +## R2:`react-i18next` / `i18next` 与 React 19 / Next 16 的运行时兼容性未实测 + +- **风险**:`npm view` peer 通过,但实际打包(turbopack/next export)与 React 19 可能有边界问题(如 `useSyncExternalStore`、HMR)。 +- **影响**:中(构建失败或运行时异常会阻断 Wave 1)。 +- **缓解**:PoC-1 先做最小依赖安装 + `next build`;锁定 `^17`/`^26` 稳定大版本;若发现问题回退 17/26 的补丁小版本并记录。 +- **Owner**:A4(先做)→ A7(复验)。 + +## R3:`<Trans>` 因资源未就绪而瞬态暴露占位/key + +- **风险**:若个别组件绕开统一门禁,`<Trans>` 可能先渲染占位符再替换。 +- **影响**:中高(违背 §3.1,且 `<Trans>` 难以用简单 `t()` 兜底)。 +- **缓解**:统一顶层门禁覆盖全局子树;组件 `ready` 兜底;CI 键集合一致性检查(R9)+ 代码评审拦截绕过。 +- **Owner**:A4(平台)→ A5/A6(使用)→ A7(回归)。 + +## R4:语言偏好 cookie/localStorage 不一致或跨跳转丢失 + +- **风险**:cookie 与 localStorage 双写可能不一致;`SameSite` 过严导致 SSO/整页跳转回带失败,或过宽带来安全面。 +- **影响**:中(刷新/回跳语言恢复失败,D10 不满足)。 +- **缓解**:以显式写入顺序为准的收敛规则(ADR-i18n-05);`SameSite=Lax` + 生产 `Secure`;PoC-5/7 覆盖 cookie 清空/localStorage 留存、整页回跳各分支。 +- **Owner**:A4 → A7。 + +## R5:依赖文件被多 Agent 并行写坏(锁分叉) + +- **风险**:`package.json`/lock 在多 worktree 并行 `npm install/ci` 产生分叉,G1 集成冲突。 +- **影响**:高(阻塞集成、浪费波次)。 +- **缓解**:D12 单一写入者(默认仅 A4);下游从 G1 基线 `npm ci` 且不写 lock;v1.4 变更摘要已固化。 +- **Owner**:A4(执行)→ A0(review)。 + +## R6:功能 Agent 与平台共享单点(`src/i18n/**`、注册表、公共 JSON)写冲突 + +- **风险**:多人同时改初始化代码/注册表/大 JSON。 +- **影响**:中(合并冲突、注册表被破坏导致类型/加载异常)。 +- **缓解**:FILE_OWNERSHIP 规则:`src/i18n/**` 与骨架归 A4 永久独占;新增 namespace 走 A0 派单由 A4 注册;功能 Agent 只写自己 namespace。 +- **Owner**:A0(调度)→ A4(执行)。 + +## R7:en/zh 字典键集合漂移(缺 key/多 key) + +- **风险**:中文更新新增 key 但 en 未同步(或反之),导致运行期 missingKey 回退/多出 key。 +- **影响**:中(D2 要求 en 为真源、zh 缺 key 回退;键集合须一致 §3.4)。 +- **缓解**:CI 增加「en vs zh-CN 键集合 diff」校验(可在 `test:types` 或独立脚本,纳入 Agent 7 质量门禁);`CustomTypeOptions` 类型约束 + 开发模式 missingKey warning。 +- **Owner**:A7(CI/校验)→ A4(类型)→ 各功能 Agent(补 key)。 + +## R8:cookie 安全与隐私(`SameSite`、`Secure`、XSS 可读) + +- **风险**:语言非秘密,但 cookie 可被 XSS 读取;`SameSite=None` 若被误用扩大攻击面。 +- **影响**:低-中(隐私面小;但需遵守 CLAUDE.md「勿把 token 放 localStorage」精神,语言偏好非敏感)。 +- **缓解**:语言偏好非敏感可放心;cookie `SameSite=Lax; path=/;`,生产 `Secure`;绝不用此 cookie 存令牌;仅当 SSO 第三方域确需带 cookie 时,再评估 `None` 并单独评审。 +- **Owner**:A4。 + +## R9:硬编码文案漏网(中文环境下仍有英文句/key 可见) + +- **风险**:1400+ TSX,批量改造难免遗漏;产物残留 key 串或英文句。 +- **影响**:中(本地化不完整、观感差)。 +- **缓解**:扫描工具只报告候选(不改写,§3.4);PoC-9 对产物 grep 残留;纳入 v1 完成定义与 A7 回归;模块按 high-traffic 优先级(导航/登录/Models/API Keys/Usage/Cost/Budgets)。 +- **Owner**:A5/A6(改造)→ A7(扫描/回归)。 + +## R10:静态导出 + 多语言无 SEO,`<title>`/meta 恒为英文 + +- **风险**:搜索引擎与分享卡片恒为英文,非本地化。 +- **影响**:低(D8 明确 v1 不做多语言 SEO)。 +- **缓解**:接受为 v1 边界;文档记录未来可引入 per-locale 静态 `title`(ADR-i18n-06 后果)。 +- **Owner**:A0(确认范围)。 + +## R11:管理员全局语言设置未来引入(P1)可能改变优先级语义 + +- **风险**:后端未来新增 `UI settings.language` 字段,需决定「覆盖用户选择」语义,否则现有优先级迁移需返工。 +- **影响**:低(v1 无此字段,ADR-i18n-07 已封闭);中(若 v1.1 临时加入则会波及优先级链)。 +- **缓解**:预留 L5 判定位(I18N_TECH_DESIGN §3.2),把「用户显式」与「探测」分开存储,便于未来叠加管理员全局层而不混淆。 +- **Owner**:A1(设计预留)→ A0(接产品决策)。 + +## R12:中文文本在窄布局/表格溢出(文本长度不同导致 UI 破坏) + +- **风险**:中文普遍比英文紧凑,但长译句或 `{{count}}` 拼接可能导致表格/按钮溢出或换行异常。 +- **影响**:中(布局回归)。 +- **缓解**:Wave 1 起在 Login/Navbar/表格样例做中英文并排截图比对;纳入 Agent 3 测试与 A7 回归;`whitespace-nowrap`/截断类妥善处理。 +- **Owner**:A6(功能页)→ A8(集成验收)。 + +## R13:`.json` 资源体积随 namespace 增长、首包加大 + +- **风险**:所有 locale 静态打在一起可能增大 bundle;`turbopack` 打包 json 的方式需确认。 +- **影响**:低-中(Dashboard 体量下影响有限;但静态导出无 CDN 分片语言包,加载即全量)。 +- **缓解**:PoC-1 检查产物 json 是否被正确 tree-shake/分 chunk;v1 语言仅 2 个,体积可控;若未来多语言再评估代码分割。 +- **Owner**:A4 → A8。 + +--- + +## 风险热力(供 A0 优先级参考) + +| 等级 | 风险 | +|---|---| +| 高 | R1(首屏)、R5(锁分叉) | +| 中高 | R3(`<Trans>`)、R9(硬编码遗漏) | +| 中 | R2、R4、R6、R7、R12 | +| 低 | R8、R10、R11、R13 | diff --git a/docs/i18n/TEST_CASES.md b/docs/i18n/TEST_CASES.md new file mode 100644 index 00000000000..85cd627602c --- /dev/null +++ b/docs/i18n/TEST_CASES.md @@ -0,0 +1,257 @@ +# i18n 测试用例(TEST_CASES.md) + +> 角色:Agent 3 `qa-architect` +> 依据:`I18N_MULTI_AGENT_PLAN.md` §10 测试重点、§11 完成定义;`I18N_TEST_PLAN.md` +> 实施:Agent 7 `i18n-qa` +> 命名:层用 `unit`(node `*.test.ts`) / `component`(`*.test.tsx`) / `integration`(`*.integration.test.tsx`) / `e2e`(Playwright `tests/e2e/ui/`);`*` = 自动化,`(m)` = 人工辅助 + +每个用例字段:编号、层级、描述、前置、步骤、断言、自动化与否。 + +--- + +## A. 语言初始化与持久化 + +### TC-01 默认语言为英文 +- 层级:`e2e` +- 描述:首次访问(无任何语言偏好)界面与 `<html lang>` 均为英文 `en`。 +- 前置:清空语言偏好存储(cookie/localStorage)、浏览器无中文偏好;静态导出 `out/` 已构建。 +- 步骤:1. 无偏好打开根路由。2. 读取 `<html lang>` 与可见文案。 +- 断言:`<html lang="en">`;界面文案为英文;不出现原始 key。 +- 自动化:* + +### TC-02 默认语言英文(逻辑合并,无偏好) +- 层级:`unit`(`*.test.ts`) +- 描述:语言选择合并函数在"无偏好 + 浏览器英文"时归 `en`。 +- 前置:逻辑纯函数已抽出。 +- 步骤:调用合并函数,输入空偏好与 "en"。 +- 断言:返回 `"en"`。 +- 自动化:* + +### TC-03 切换语言即时生效 +- 层级:`component` + `e2e` +- 描述:切换 `en → zh-CN`,页面文本与 `<html lang>` 即时更新,无需刷新。 +- 前置:语言切换器与页面在 Provider 下渲染;资源就绪。 +- 步骤:component:渲染带 `I18nProvider`(可 stub 网络)的页面 → 点击切换器选中文 → 断言关键文本与 lang。e2e:真实浏览器同操作。 +- 断言:Navbar/Leftnav 等中文化文本出现;`<html lang="zh-CN">`;无原始 key、无刷新。 +- 自动化:* + +### TC-04 刷新后语言保持 +- 层级:`e2e` +- 描述:切到中文后刷新页面,语言仍为中文。 +- 前置:偏好写入介质(cookie/localStorage,依架构决策)。 +- 步骤:1. 切成中文。2. `page.reload()`。3. 读 `<html lang>` 与可见文本。 +- 断言:`<html lang="zh-CN">`,界面仍为中文。 +- 自动化:* + +### TC-05 语言偏好在同源介质持久化(单元) +- 层级:`unit` +- 描述:偏好读写到可注入 storage/cookie 抽象,序列化往返一致。 +- 前置:偏好读写逻辑独立可测。 +- 步骤:写入 `"zh-CN"` → 重新读取。 +- 断言:读回 `"zh-CN"`;存储键稳定、无多余数据。 +- 自动化:* + +## B. 回退与 key 遮蔽 + +### TC-06 缺失中文时回退英文 +- 层级:`component` + `e2e` +- 描述:zh 字典缺某 key 时,渲染英文而非原始 key。 +- 前置:中文态 + 注入缺失 key 的字典子集(或 e2e 用构建产物中的真实缺口做抽样)。 +- 步骤:切换到中文,访问缺失 key 所属文案。 +- 断言:显示对应英文译文;**不**显示 `ns:key` 原始 key。 +- 自动化:* + +### TC-07 不显示原始翻译 key(首帧/全程) +- 层级:`e2e`(首帧)+ `component`(全程) +- 描述:无论资源加载时序,任何时刻帧内都不泄漏原始 key 文本(`ns:...`)。 +- 前置:资源同步就绪或就绪门禁生效;`<Trans>`/`t()` 就绪后渲染。 +- 步骤:e2e 首次导航截图 + 快速截图序列,扫描页面可见文本。 +- 断言:页面文本不包含形如 `namespace:key` 或 `key.xxx` 的原始 key 串。 +- 自动化:* + +### TC-08 `<html lang>` 与当前语言一致 +- 层级:`component` + `e2e` +- 描述:`<html lang>` 始终等于当前语言(en/zh-CN)。 +- 前置:`<html lang>` 客户端同步已实现。 +- 步骤:分别在 en、zh、切换后、刷新后读取 `document.documentElement.lang`。 +- 断言:en→`"en"`,zh→`"zh-CN"`;切换与刷新后保持一致。 +- 自动化:* + +## C. 动态数量 / 日期 / 数字 / 货币 + +### TC-09 中文数量插值 count=0 / 1 / 2(D9) +- 层级:`unit`(核心)+ `component`(渲染) +- 描述:中文用计数插值与量词:`0 个`、`1 个`、`2 个`/`{{count}} 个`;不机械复制英文复数分支。 +- 前置:量词插值组件/函数已实现,`{{count}}` 语义。 +- 步骤:依次以 `count=0`、`count=1`、`count=2` 渲染/调用,中文态。 +- 断言:输出分别为 `0 个请求`、`1 个请求`、`2 个请求`(按术语表措辞);无英文 one/other 分支语义。 +- 自动化:* + +### TC-10 英文数量复数 +- 层级:`unit` +- 描述:英文按 i18next/CLDR `one`/`other` 处理:`1 request` vs `2 requests`。 +- 前置:英文资源含复数分支。 +- 步骤:`count=1`、`count=2` 渲染。 +- 断言:`1 request`、`2 requests`。 +- 自动化:* + +### TC-11 日期本地化 +- 层级:`unit` +- 描述:日期按当前语言格式化(e.g. `Sep 9, 2026` vs 中文格式),时区/时刻固定。 +- 前置:固定时间源、固定 locale;格式化逻辑独立。 +- 步骤:同一时间戳在 en、zh 下格式化。 +- 断言:格式随 locale 变化且正确;无 `NaN`/原始时间戳泄漏。 +- 自动化:* + +### TC-12 数字本地化 +- 层级:`unit` +- 描述:千分位/小数点随 locale(en `1,234.5` vs zh 千分位),金额单位不重复。 +- 前置:格式化函数独立。 +- 步骤:en、zh 各格式化同值。 +- 断言:分组/小数正确;不出现双重符号或单位。 +- 自动化:* + +### TC-13 货币本地化 +- 层级:`unit` +- 描述:货币符号与金额按 context/locale 呈现(成本/预算页)。 +- 前置:货币格式化上下文确定(币种来自数据)。 +- 步骤:en、zh 各格式化同金额。 +- 断言:符号与格式正确;数据币种不被误翻。 +- 自动化:* + +## D. 表单与交互状态 + +### TC-14 切换语言不清空表单 +- 层级:`integration` +- 描述:填写表单字段后切换语言,字段值与未提交状态保持,不触发无关请求。 +- 前置:集成树(真实组件 + Provider),网络边界 stub。 +- 步骤:填写 → 切语言 → 断言字段值与 `<form>` 状态保持。 +- 断言:输入值仍在、选择/勾选仍在;无提交/异常请求触发;无原始 key。 +- 自动化:* + +### TC-15 切换语言不清空表单(E2E 抽查) +- 层级:`e2e` +- 描述:真实浏览器下填写关键表单(如 Models)后切换语言,值保持。 +- 前置:静态导出站点可用。 +- 步骤:填写 → 切中文 → 断言。 +- 断言:值与交互状态保持。 +- 自动化:*(抽查核心表单) + +## E. 整页跳转语言保持 + +### TC-16 Login 整页跳转回跳后语言保持 +- 层级:`e2e` +- 描述:切成中文 → 走 Login(整页跳转)→ 成功回跳 → 语言仍为中文。 +- 前置:Login 整页跳转与同源偏好恢复已 PoC/批准。 +- 步骤:设中文 → 登录跳转 → 回跳 → 读 `<html lang>`/文本。 +- 断言:中文保持或命中文档化降级方案(需 Agent 0 批准)。 +- 自动化:* + +### TC-17 SSO 整页跳转回跳后语言保持 +- 层级:`e2e` +- 描述:SSO 外部跳转并返回后语言偏好保持。 +- 前置:SSO 流程可测(测试账号/模拟)。 +- 步骤:设中文 → SSO → 返回 → 断言。 +- 断言:同 TC-16。 +- 自动化:* + +### TC-18 MCP OAuth 整页跳转回跳后语言保持 +- 层级:`e2e` +- 描述:MCP OAuth 授权跳转并返回后语言保持。 +- 前置:MCP OAuth 流程可测(mock proxy)。 +- 步骤:设中文 → OAuth → 返回 → 断言。 +- 断言:同 TC-16。 +- 自动化:* + +### TC-19 存储不可用时的降级 +- 层级:`unit` + `e2e` +- 描述:偏好存储不可用/被禁时降级到英文,不崩溃、不报错。 +- 前置:存储抽象可注入失败。 +- 步骤:注入读写失败 → 初始化 → 断言。 +- 断言:回退 `en`;无未捕获异常;不显示原始 key。 +- 自动化:* + +## F. 不误翻译保护 + +### TC-20 模型名不被翻译 +- 层级:`integration` +- 描述:Models 页面渲染的模型名/模型 id 在 en 与 zh 下逐字一致,不受语言切换影响。 +- 前置:stub 数据含大小写混合/带符号模型名。 +- 步骤:en、zh 各渲染并比对模型名文本。 +- 断言:模型名字符串完全一致;不被译成中文/改名。 +- 自动化:* + +### TC-21 API 字段 / 日志 / 代码示例不被翻译 +- 层级:`integration`(代码示例/字段)+ `unit`(日志原文) +- 描述:API 参数字段、请求体 key、日志原文、代码示例不参与翻译。 +- 前置:含上述内容的组件与最小字典。 +- 步骤:zh 下渲染包含代码/字段/日志的页面。 +- 断言:这些内容保持原样(ASCII/原文),与 en 一致。 +- 自动化:* + +## G. 构建期元数据与可访问性 + +### TC-22 构建期 `<title>` / meta description v1 保持英文 +- 层级:`e2e` +- 描述:静态导出 `out/index.html`(及各路由)的 `<title>` 与 `meta[name=description]` 在 v1 保持英文,未被功能 Agent 意外改写或半中文化。 +- 前置:`npm run build` 完成;检查静态产物。 +- 步骤:解析 `out/**/*.html` 的 `<title>`/description(或浏览器读取 `document.title`)。 +- 断言:为稳定英文文案;不含原始 key、不含半成品中文。 +- 自动化:* + +### TC-23 可访问性文案本地化(aria-label/title/placeholder) +- 层级:`component`(每组件)+ `integration`(成组页面) +- 描述:图标按钮、关闭按钮、tooltip、`title`、`placeholder`、校验 error 的 a11y 文本随语言本地化并有英文回退。 +- 前置:目标组件含该类文案。 +- 步骤:en、zh 各查询 `getByRole`/`getByLabel`/`toHaveAttribute('aria-label', …)`。 +- 断言:a11y 文案为对应语言;zh 缺失时回退英文;不显示原始 key;ARIA value 用 `toHaveAttribute` 断言(CLAUDE.md)。 +- 自动化:* + +### TC-24 中文布局不溢出(自动断言 + 人工走查) +- 层级:`integration`(自动溢出)/ `e2e`(窄屏)+ 人工 +- 描述:中文下导航/表格/弹窗/表单无横向溢出、无截断、无遮挡。 +- 前置:切换到中文。 +- 步骤:对关键容器断言 `scrollWidth <= clientWidth`、关键文本 `toBeVisible`;e2e 窄视口;人工跨语言走查。 +- 断言:无溢出/截断/遮挡;`V1_SCOPE_MANIFEST.md` UI 检查证据。 +- 自动化:*(溢出断言)+ (m)(视觉走查) + +## H. Key / 字典一致性 + +### TC-25 en/zh key 集合一致 +- 层级:`unit`(脚本级,作为静态校验)——实现为 CLI 工具(`TEST_TOOLS.md`);此处用例=脚本通过条件 +- 描述:en 与 zh 全部 namespace 的 key 集合、结构与插值变量一致。 +- 前置:脚本就绪、字典就绪。 +- 步骤:对每 namespace 运行一致性校验。 +- 断言:key 集对称一致;缺 key/多余 key/结构不一致/插值变量不一致时脚本非 0 退出并列差异。 +- 自动化:*(门禁/CI) + +## I. 首屏 / 静态导出 + +### TC-26 静态导出构建产物无原始 key 泄漏 +- 层级:`e2e`/静态产物检查 +- 描述:构建后的 `out/` 页面源码不含原始 `ns:key` 文本作为用户可见内容(排除 JS bundle 中的字典本身)。 +- 前置:`npm run build`。 +- 步骤:解析产物 HTML,过滤 script/style,扫描可见文本。 +- 断言:可见文本无原始 key 形式。 +- 自动化:* + +--- + +## 用例汇总 + +- 共 26 条(TC-01 ~ TC-26)。 +- 自动化纯自动:除 TC-24 视觉走查外全部为 `*`;TC-24 为自动化+人工混合。 +- 覆盖方案 §10 全部"测试重点": + - 默认语言:TC-01/02 + - 切换即时生效:TC-03 + - 刷新保持:TC-04/05 + - 缺 key 回退英文:TC-06 + - 不显示原始 key:TC-07/26 + - `<html lang>` 一致:TC-03/08 + - 中文不溢出:TC-24 + - 切换不清表单:TC-14/15 + - 整页跳转语言保持:TC-16/17/18/19 + - 动态数量/日期/数字/货币(含 count=0/1/2):TC-09~13 + - 模型名/API 字段不误翻:TC-20/21 + - `<title>`/meta v1 保持英文:TC-22 +- 另补充:a11y 文案(TC-23)、key 一致性(TC-25)。 diff --git a/docs/i18n/TEST_TOOLS.md b/docs/i18n/TEST_TOOLS.md new file mode 100644 index 00000000000..0b46833315c --- /dev/null +++ b/docs/i18n/TEST_TOOLS.md @@ -0,0 +1,88 @@ +# i18n 自动化测试任务清单与工具设计(TEST_TOOLS.md) + +> 角色:Agent 3 `qa-architect`;实施:Agent 7 `i18n-qa` +> 原则:扫描工具**只报告、不自动改写**(方案 §3.4/§11);测试只跑改动相关文件;Vitest 命令由 Agent 3 依配置确认(方案 §10) + +## 0. 运行说明(Agent 7 前置校准) + +- 定向 vitest:`cd ui/litellm-dashboard && npx vitest run --project <unit|component|integration> <path>`;type:`npm run test:types`。 +- 禁止无路径全量 `npm run test`(380 文件、CI 才跑全量)。 +- 工程检查:`npm run lint`、`npm run format:check`、`npm run build`。 +- **E2E Playwright 基础设施当前不存在**(无 `playwright.config.*`、无 `@playwright/test`、无 `tests/e2e/ui/`)。Agent 7 在 Wave 1 搭建;涉及 `package.json`/锁文件变更时必须由 Agent 0 指派唯一 Owner(方案 §6.1)。 + +## 1. 自动化任务清单 + +| 编号 | 任务 | 形态 | 何时运行 | 对应用例 | +|---|---|---|---|---| +| T-01 | 搭建 Playwright 基础(config、`tests/e2e/ui/`、live proxy fixture) | E2E 基建 | Wave 1 | TC-01~04,06~08,16~18,22,26 | +| T-02 | key 集合一致性校验脚本 | CLI(node) | G3 门禁/CI、每次字典变更 | TC-25 | +| T-03 | 硬编码字符串扫描脚本(只报告) | CLI(node) | 每波次、G3/G4 | — | +| T-04 | 语言初始化/偏好/格式化逻辑单元测试 | `*.test.ts`(unit) | 平台 Wave 1 | TC-02,05,09~13,19 | +| T-05 | 语言切换器/量词/单组件渲染测试 | `*.test.tsx`(component) | 各功能波 | TC-03,09,10,23 | +| T-06 | Provider+页面集成测试(切换、表单保持、a11y、误翻译) | `*.integration.test.tsx` | 各功能波 | TC-03,14,20,21,23,24 | +| T-07 | 静态导出产物 key 泄漏/`<title>` 英文检查 | 构建后脚本/E2E | G1、G4 | TC-22,26 | +| T-08 | 中文布局溢出自动化断言(+人工走查入口) | `integration`/`e2e` | 各功能波、Wave 4A | TC-24 | +| T-09 | `V1_SCOPE_MANIFEST.md` 测试/UI 检查证据回填 | 人工+半自动 | G2/G3/G4 | — | +| T-10 | 完整回归矩阵执行并出报告 | Playwright+vitest | Wave 4A | REGRESSION_MATRIX | + +## 2. key 集合一致性校验脚本(T-02) + +**目标**:证明 en/zh(及新 locale)key 集、结构与插值变量一致;组件引用的 key 均存在。 + +**设计**: +- 输入:`src/locales/en/**/*.json`、`src/locales/zh-CN/**/*.json`。 +- 对每个 namespace 比对:key 集合(递归扁平化,含冒号前缀)对称差、嵌套结构形状、每叶子的 `{{var}}` 插值变量集合。 +- 组件引用校验:AST 扫描 `t('ns:key')`、`useTranslation('ns')`、`<Trans>` 与静态字典,找出引用但未声明的 key(悬空引用)。 +- 输出:无差异 → 退出 0;有差异 → 列出缺失/多余 key、结构/插值差异、悬空引用清单,退出非 0。 +- 挂接:G3 门禁"中英文 key 集一致";可加入 CI lint 前置。 + +**命令形态**: +```bash +node scripts/i18n/check-keys.mjs # 或通过 vitest 一个用例驱动(TC-25) +``` + +## 3. 硬编码字符串扫描脚本(T-03)——只报告、不自动改写 + +**目标**:发现"漏改为 `t()` 的用户可见文案",辅助人工补翻译。**禁止自动生成 key、禁止自动改写代码**。 + +**设计**(对 TSX/TS 源码 AST): +- JSX 文案:非空白/非注释/非纯符号的 `JSXText`。 +- 属性字面量:`aria-label`、`title`、`placeholder`、`alt` 中指向文案的字符串常量。 +- 未国际化入口:`toast(...)`、`useMessage`、`notify(...)` 等(以项目现状校准 white-list)。 +- 排除白名单:模型名/API 字段引用(`model`、`langsmith` 等字典值来源)、路径、正则、CSS 类、`data-slot`、纯数字/符号、标识符、已 `t()` 包裹的。 +- 输出:文件、行号、候选文案、疑似命中形式(文案属性/JSX 文本/消息入口),按文件分组;**附带"仅报告"明确标注**,不写字典。 + +**报告消费**:Agent 7 输出人工 review 清单 → 产品/功能 Owner 决定是否改写;禁止工具自动化改写(方案 §3.4 硬约束)。 + +## 4. 中文布局检查:自动化 / 人工边界 + +**自动化(T-08)**: +- 组件/集成断言:中文渲染下对关键容器断言 `scrollWidth <= clientWidth`(无横向溢出)、关键文本 `toBeVisible`、无遮挡(元素间不重叠)。 +- E2E:在窄视口(如 1280、1024、768)下对 Navbar/Leftnav/表格/弹窗断言同样条件,并做关键帧截图。 +- 限制:视觉"美观/语义完整"无法纯自动化判定,自动只兜底"不溢出、不截断语义、可读可见"。 + +**人工(配合 Agent 2)**: +- 跨语言(en/zh)并列视觉走查,覆盖中文长字符串与窄屏。 +- 截图证据回填 `V1_SCOPE_MANIFEST.md` 的 `UI 检查` 栏。 +- `TEST_CASES.md` TC-24 标记为自动化+人工混合。 + +## 5. 静态导出产物检查(T-07) + +- 构建后扫描 `out/**/*.html`: + - 用户可见文本(剔除 script/style/内联 JSON 字典)不含 `namespace:key` 原始 key 形式(TC-26)。 + - `<title>` 与 `meta[name=description]` 为稳定英文、非原始 key、非半中文(TC-22)。 +- 挂接:G1(首屏/构建验证)与 G4(发布验收)。 + +## 6. E2E 基建要点(T-01) + +- 目录:`tests/e2e/ui/`(Playwright spec,与 vitest 分离)。 +- 目标:静态导出 `out/` 或 live proxy;语言切换/刷新/整页跳转(Login/SSO/MCP OAuth)的偏好恢复需可稳定复现(测试账号或 mock)。 +- `package.json` 变更(新增 `@playwright/test`)须经 Agent 0 指定唯一 Owner,遵守 §6.1。 + +## 7. Agent 7 建议实现优先级 + +1. **T-02 key 一致性校验**(成本低、门禁 G3 硬性、最易踩线)。 +2. **T-03 硬编码扫描**(只报告,贯穿所有波次,辅助功能 Agent 自查)。 +3. **T-01 E2E 基建 + 平台 smoke**(默认语言/切换/刷新/`<html lang>`/回退/不显示原始 key)。 +4. **T-07 静态产物 key 泄漏 + `<title>` 英文检查**(G1 门禁)。 +5. **T-04/T-05/T-06 各层测试**随功能波补齐,T-08 布局断言进入各模块验收,T-10 在 Wave 4A 全量回归。 diff --git a/docs/i18n/V1_TRANSLATION_SCOPE.md b/docs/i18n/V1_TRANSLATION_SCOPE.md new file mode 100644 index 00000000000..6aed8a3356f --- /dev/null +++ b/docs/i18n/V1_TRANSLATION_SCOPE.md @@ -0,0 +1,78 @@ +# v1 翻译范围清单(V1 Translation Scope) + +> 维护者:Agent 2(localization-designer) +> 状态:设计稿,待 G0 评审冻结;与 `I18N_MULTI_AGENT_PLAN.md` §1.2/§6.2 及 `DECISIONS.md` D8 对齐 +> 说明:本文档逐项列出 v1 各页面/组件"**需翻译**"或"**明确不翻译**"及原因,作为 Agent 5/6 改造与 Review 的依据。数据/协议内容是否翻译遵循 `LOCALIZATION_SPEC.md` §4。 + +--- + +## 1. v1 需翻译范围(逐项) + +### 1.1 全局壳层 + +| 组件/区域 | 需翻译? | 原因 / 说明 | +|---|---|---| +| `leftnav.tsx` 导航菜单项与分组标签 | 需翻译 | 全局入口;`menuGroups` 的 `groupLabel`、`label` 需 I18N 化(含分组标签如 AI Gateway/Observability 等)。`menuGroups` 由 `page_utils.ts` 消费,改造时保留导出结构,仅语义化 label | +| `leftnav.tsx` 面包屑(`getBreadcrumb`)/ Navbar 标题 | 需翻译 | 与导航一致,防止两处不一致 | +| `leftnav.tsx` aria-label(折叠/展开侧边栏) | 需翻译 | a11y 文案(`LOCALIZATION_SPEC.md` §7) | +| Navbar 用户菜单 / `SidebarAccountMenu` | 需翻译 | 显示名、菜单项、提示文案 | +| `navbar.tsx` 其余可见菜单与搜索占位 | 需翻译 | 依实际文案盘点 | +| 语言切换器(新组件) | 需翻译 | 自身 aria-label 与菜单项随语言渲染 | + +### 1.2 登录与引导 + +| 页面 | 需翻译? | 说明 | +|---|---|---| +| `src/app/login/LoginPage.tsx` | 需翻译 | 登录表单、提交、错误提示、SSO 入口 | +| `src/app/onboarding/`(OnboardingForm/FormBody/Loading/Error 等) | 需翻译 | 引导步骤、表单、加载/错误视图 | +| `src/app/connect/` | 需翻译 | Connect 页面 | +| MCP OAuth 相关回跳/授权提示 UI | 需翻译 | 见 `LOCALE_NAVIGATION_BEHAVIOR.md`(注意整页跳转语言恢复) | + +### 1.3 Models 与 API Keys + +| 页面/组件 | 需翻译? | 说明 | +|---|---|---| +| Models and Endpoints(`/models`)列表、创建/编辑表单、过滤、空态、校验 | 需翻译 | 静态与列表类文案;`model-hub`/AI Hub 候选页属低优先级,v1 不强求 | +| API Keys(`/api-keys`)列表、创建弹窗、删除确认、用量展示 | 需翻译 | 含确认框、空态、校验 | +| 相关复用组件(`key_value_input`、`copy button` 等) | 需翻译 | 复用组件文案由调用方传入或按需本地化 | + +### 1.4 Usage / Cost / Budget + +| 页面/组件 | 需翻译? | 说明 | +|---|---|---| +| Usage(`/usage`)筛选、表格头、图表说明、空态 | 需翻译 | 数值/货币用 i18n 格式化 | +| Cost Tracking(`/cost-tracking` / `cost-optimization`) | 需翻译 | 成本跟踪页面;数值格式化随 i18n | +| Budgets(`/budgets`)创建/编辑、限额设置、列表 | 需翻译 | 含"Budget Limit"等术语(见术语表) | + +--- + +## 2. 明确不翻译范围(逐项 + 原因) + +遵循 `LOCALIZATION_SPEC.md` §4 与方案 §1.2: + +| 项 | 不翻译原因 | +|---|---| +| 模型名 / Model ID / Deployment / Provider 名 | 用户/后端数据,非 UI 文案 | +| API 字段、请求/响应键、JSON 结构 | 协议内容 | +| 日志原文、错误消息、错误堆栈(stack trace) | 需原样排障,保留原文 | +| 代码示例、curl、终端命令、config.yaml、.env | 技术内容 | +| URL、路径、文件名 | 技术标识 | +| API / URL / SSO / MCP / OAuth / REST / ID / JSON 等缩写 | 通用技术缩写 | +| 品牌名 LiteLLM / OpenAI / Anthropic | 品牌 | +| 角色/权限代码值(admin、internal_user 等) | 代码值,仅显示名可翻译 | +| Beta / New 徽标 | 视觉因子,保留英文 | +| 构建期 `<title>` / metadata description | D8,v1 保持英文,不做多语言 SEO | +| Guardrails / Policies / Teams / Users / Orgs / Projects / Logs / Playground / Prompts 等整模块文案(未列入 v1 功能批次) | 属 v2+ 候选(方案 §5.7),v1 范围外不被动外扩 | +| Python SDK、API 文档、代码示例翻译 | 方案 §1.2 明确排除 | +| 后端 FastAPI 错误响应全面国际化 | 方案 §1.2 排除 | + +--- + +## 3. 与 §6.2 状态清单对应 + +本清单即 `V1_SCOPE_MANIFEST.md`(Agent 0 维护)中 v1 行的**文案盘点输入**。Agent 5/6 完成任务时在清单相应行填写 EN/ZH-CN 盘点,Agent 7 填测试/UI 检查证据,Agent 2 在 Wave 4A 做术语与中文体验复核。 + +## 4. 边界红线 + +- 未列入第 1 节的功能页面文案,v1 一律**不翻译**(保持英文),不要顺手外扩造成半成品。 +- 第 2 节不翻译项,任何 Agent 不得翻译;发现误翻即退回原 Owner,并作为缺陷记录。