|
| 1 | +# DESIGN-note:公开分享页中英切换与前端 i18n 补漏 |
| 2 | + |
| 3 | +## 目标 |
| 4 | + |
| 5 | +中文站点可以把同一份结果分享给英文读者;读者可在公开结果页选择中英文界面,原始标题、转录与既有总结内容仍保持生成时语言。 |
| 6 | + |
| 7 | +设计扩展依据:用户在 2026-10-23 明确确认“需要:补齐双语,并支持分享页切换”。此前单一全局 `language` 仅满足部署者统一选择语言,不覆盖同一部署的中英分享读者;原设计中“不按请求切换”的非目标由本次明确授权扩展。方案经 `consult.sh` 的 `claude-opus-5-5` 独立审阅,意见为只对公开分享页支持显式 `lang`,并先补两个已确认遗漏。 |
| 8 | + |
| 9 | +## 非目标 |
| 10 | + |
| 11 | +- 不让访客语言改写全局 `language`、API 错误、提交/历史页或 LLM 产出语言,不重生成或迁移历史内容。 |
| 12 | +- 不加入 cookie、`Accept-Language` 自动协商、访客设置服务端持久化或另一套 i18n 库。 |
| 13 | +- 不翻译标题、转录、摘要、章节、历史错误原文和管理员自定义 Prompt。 |
| 14 | +- 不接维护者私有 PWRS 服务或要求 `PWRS_REPORTER_TOKEN`;浏览器失败证据走本仓 workflow artifact。 |
| 15 | + |
| 16 | +## 为什么不是分区 / 删除 / 约定 |
| 17 | + |
| 18 | +- **分区**:按进程/实例区分语言无法让同一个分享 URL 被两个语言的读者使用;语言需随单个公开请求传播。保留站点配置作为默认与内容语言,各请求在公开页面上下文中覆盖界面语言。 |
| 19 | +- **删除**:不做访客切换会违背用户确认的同链接中英分享场景;不做 cookie/自动协商仍能用显式 URL 满足它。按请求渲染引入的唯一必要机制是请求级翻译上下文。 |
| 20 | +- **约定**:让读者手工改站点配置会影响其他读者及新生成内容;链接和切换控件必须显式携带语言。对没有语言参数的老链接维持现有站点默认行为。 |
| 21 | + |
| 22 | +## 方案要点与已否决方案 |
| 23 | + |
| 24 | +- **要点**: |
| 25 | + 1. 只让公开 `/view/{token}`(含处理中、失败、已清理等视图)、其 `?raw=` / `?page=` 输出、`/og/{token}.png` 和页面引用的 `/i18n.js` 接受 `lang=zh-CN|en`。参数缺省取站点 `language`;显式空值或不支持值返回 HTTP 400,不静默退回。显式语言响应设置 `Cache-Control: private, no-store`,不依赖未核实的代理 query cache key。 |
| 26 | + 2. 结果页提供中文 / English 切换链接,保留 token 与当前页面选择参数;结果页脚本词典、`<html lang>`、OG locale/image、复制的分享 URL 和全部站内 Raw/HTML 导出链接使用相同有效 UI 语言。手工分享的 `/view` URL 可把 `lang` 传给收件者。分享预览图片单独校验该参数并选择现有的按语言命名图片。 |
| 27 | + 3. 保持 `get_language()` 的站点配置含义。公开 view handler 在请求范围内建立并于 `finally` 恢复 UI 翻译语言上下文;Jinja `t()`、`html_lang()`、`og_locale()` 以及结果渲染器读该上下文。禁止逐请求改共享 `env.globals`。`asyncio.to_thread` 下游需用并发行为测试确认上下文传播,确保两个并发中英请求互不串语。 |
| 28 | + 4. `i18n.js` 对公开页通过带 `lang` 的 URL 取词典;静态首页和历史继续使用站点语言。API 参数错误沿用站点语言。原文内容与生成内容不进入 UI 翻译。 |
| 29 | + 5. 将默认深度阅读预设标签和指令纳入现有双语目录:中文模板逐字保留,生成的内置指令跟随站点 `language`;按钮外框标签跟随访客 UI 语言。通过明确标记内置来源区分,管理员自定义预设的名称和指令保持原样,禁止根据文本内容猜来源。切换页面语言不会改变复制给外部 LLM 的任务要求。 |
| 30 | + 6. 历史列表未完成任务的状态徽标改为复用已有 `web.history.status_success/processing/failed` 文案;逻辑仍用原枚举比较,不把文案当状态值。 |
| 31 | +- **已否决**:访客语言写进 cookie 或读 `Accept-Language`,会把 UI 状态引入共享缓存之外、扩大服务端状态来源;全站访客切换覆盖面超出真实读者入口;切换访客语言时改 Prompt,会让界面操作意外改变外部 LLM 输出。 |
| 32 | + |
| 33 | +## 关键不变式 |
| 34 | + |
| 35 | +1. [契约,待实现验证] 没有 `lang` 的 `/view`、导出和 OG 与当前全局语言行为一致;中文默认 UI 与内置中文 Prompt 字节保持既有值。代码:`src/video_transcript_api/i18n/__init__.py`、`src/video_transcript_api/api/routes/views.py`、`src/video_transcript_api/api/app.py`;断言:`tests/integration/test_local_upload_lifecycle.py`、`tests/unit/test_static_asset_versioning.py`。 |
| 36 | +2. [契约,待实现验证] 显式 `lang=en` 只改变公开页 UI、导出元数据和分享预览语言;原始标题、转录及总结正文内容不变。代码:`src/web/templates/base.html`、`src/web/templates/transcript.html`;断言:`e2e/tests/i18n.spec.ts`、`tests/integration/test_local_upload_lifecycle.py`。 |
| 37 | +3. [契约,待实现验证] 同一进程并发请求 `lang=zh-CN` 和 `lang=en` 时,各自响应、脚本目录与 OG 图片不串语;异常或请求结束后后续无参请求仍使用站点默认。代码:`src/video_transcript_api/i18n/__init__.py`、`src/video_transcript_api/api/routes/views.py`;断言:`tests/unit/i18n/test_i18n.py`、`tests/integration/test_local_upload_lifecycle.py`。 |
| 38 | +4. [契约,待实现验证] `lang=` 与 `lang=fr` 均为 400;缺参与显式空参可区分。不接受值不静默回落。代码:`src/video_transcript_api/api/routes/views.py`、`src/video_transcript_api/api/app.py`;断言:`tests/integration/test_local_upload_lifecycle.py`、`tests/unit/test_static_asset_versioning.py`、`e2e/tests/i18n.spec.ts`。 |
| 39 | +5. [契约,待实现验证] 深度阅读内置中文 Prompt 保持逐字不变,英文站点产生英文任务模板;访客只切 UI 后复制模板仍跟站点语言;自定义预设保持配置原文。代码:`src/video_transcript_api/api/context.py`、`src/web/templates/transcript.html`;断言:`tests/unit/test_deep_read_prompt_config.py`、`e2e/tests/i18n.spec.ts`。 |
| 40 | +6. [契约,待实现验证] 历史状态徽标在中英页面显示对应文案,API 状态枚举和过滤仍使用 canonical status。代码:`src/web/static/history.html`;断言:`e2e/tests/i18n.spec.ts`。 |
| 41 | + |
| 42 | +## 待验证前提 |
| 43 | + |
| 44 | +1. [代码契约,待并发回归验证] `ContextVar` 经 `asyncio.to_thread` 的传播由并发 HTTP 回归覆盖;公开路由不修改共享 Jinja globals。 |
| 45 | +2. [部署侧未验证] 代理 query cache key 无仓内证据;显式 `lang` 响应因此使用 `private, no-store`,未改代理层。 |
| 46 | + |
| 47 | +## 验收路径 |
| 48 | + |
| 49 | +1. 入口:本仓生产应用的公开 `/view/{token}`,使用阻断真 Chromium 回归。 |
| 50 | +2. 步骤:中文站点打开无参数分享页;点击 English 并复制分享链接;打开英文页,核对中英界面和保留原语言的正文;分别打开 Raw、HTML 导出及 OG 预览;并发打开两个语言;测试空/非法参数;在 history 页查看中英文状态徽标;在结果页核对真实默认 Prompt 的复制结果。 |
| 51 | +3. 预期:分享接收者的公开界面语言稳定;页面、复制链接、导出、OG一致;正文与任务语言不因界面切换改变;默认老链接不变。 |
| 52 | +4. 阻断层使用本仓 `.github/workflows/ui-browser.yml` 的真实 Chromium 回归及既有失败 artifact;无私有 PWRS 依赖。录屏不是行为验收。 |
0 commit comments