Skip to content

Commit 62e5794

Browse files
authored
Merge pull request #279 from zlxlabs/feat/vta-share-i18n-261023
支持公开分享页切换中英文
2 parents b0ee8b7 + 1658552 commit 62e5794

25 files changed

Lines changed: 703 additions & 75 deletions

File tree

‎README.en.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ Copy `config/config.example.jsonc` to `config/config.jsonc` and adjust at least
137137

138138
Set the top-level `"language"` value to `"zh-CN"` or `"en"`; it defaults to `"zh-CN"`, and any other value makes startup fail. This switches the Web interface, generated summaries/detailed notes/chapters, API error response `detail`, task failure reasons, and task-status response `message`.
139139

140+
Public result pages also accept `/view/{token}?lang=en` or `?lang=zh-CN` to switch only that visitor's interface. The page switcher, exports, and copied links carry the selected language. Existing titles, transcripts, and summaries stay in their original language, and the built-in deep-read prompt continues to follow the site's `language` setting.
141+
140142
It does not switch the fixed wording of WeCom/Feishu notifications or translate user content such as titles and transcript bodies. Exception text inserted into a failure reason remains unchanged, and previously stored failure reasons are not migrated or retranslated.
141143

142144
After changing the language, existing cached artifacts in the old language remain visible. When processed again, summaries, detailed notes and chapters are regenerated in the new language; the proofread transcript is not rerun because of the language change.

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ docker compose up -d --build
136136

137137
在配置文件顶层设置 `"language": "zh-CN"` 或 `"language": "en"`;缺省为 `"zh-CN"`,非法值会导致服务启动失败。该设置切换 Web 界面、总结/详细笔记/章节、API 错误响应的 `detail`、任务失败原因,以及任务状态响应的 `message`。
138138

139+
公开分享结果页也可通过 `/view/{token}?lang=en` 或 `?lang=zh-CN` 单独切换界面语言,页面里的切换、导出和复制链接会保留语言参数。访客只切换界面;既有标题、转录与总结正文保持原样,复制的内置深度阅读 Prompt 仍跟随站点 `language`。
140+
139141
该设置不会切换企业微信/飞书通知自身的固定文案,也不会翻译用户内容(如标题和转录正文)。失败原因中插入的异常原文保持原样;已存储的历史失败原因不会迁移或重新翻译。
140142

141143
切换语言后,已有缓存中的旧语言产物仍照常显示;再次处理时,总结、详细笔记和章节会按新语言重新生成,校对稿不会因此重跑。
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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 依赖。录屏不是行为验收。
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# 实施报告:公开分享页中英双语
2+
3+
本次按已批准设计实现公开分享页 UI 语言切换。访客可在中文、English 间切换;原转录/总结正文保持原文。显式语言沿公开 HTML、raw/page 导出、i18n 脚本和 OG 图片传递,错误语言返回 400。站点默认语言、Prompt 任务含义和用户自定义 Prompt 不变。
4+
5+
- H0:`fc2d8fd7628ccebfc955025e0b6eb7b1cff359e5`。
6+
- H1:`ef69bc5b25e10173215173fe30ce55e85c2620f1`。
7+
- 最终补测与审查记录在本次提交;最终 HEAD 由提交输出确认。
8+
- H1 发现的 API 闭集错误词条与 OG URL 断言问题已在 H1 修复;没有改 API 错误生成器或新增生产抽象。
9+
- 显式 `lang` 的共享缓存响应为 `private, no-store`;未改 CDN/代理配置、持久化数据或 schema。
10+
11+
## 验证记录
12+
13+
- `uv run --frozen pytest -q tests/unit/i18n/test_i18n.py tests/unit/test_deep_read_prompt_config.py`:20 passed。
14+
- 扩展 i18n/deep-read/cache/route 窄测:通过;H1 修复的 3 个原失败节点 + catalog/deep-read:24 passed。
15+
- `npm run test:web`:188 passed。
16+
- `npm run test:browser`:33 passed;包含真实 Chromium、复制 payload、raw/page 文件字节和响应语言切换。
17+
- H1 完整 `make test`(代码提交 H1):退出 2,唯一失败是 `tests/unit/web/test_frontend_nav.py` 的旧 URL 断言及缺少 `ui_language` fixture。修正后最终窄测 44 passed;没有因纯测试 fixture 更新再跑完整本地套件。详见 `/tmp/vta-share-i18n-261023-h1-make-test-summary.txt`。
18+
- H0 完整套件运行在约 21% 时收到 SIGINT,退出 130;不计为完整验证。
19+
- 隔离 worktree 红验:将 `/view` 显式语言响应缓存头变成 `public, max-age=3600` 后,真实 route 集成用例在 raw 响应头断言失败:期望 `private, no-store`,实际 `public, max-age=3600`。
20+
- H2 OCR 续修:旧模板 gate 下真实 resolver 的 processing/failed/file_cleaned 三状态用例均在切换链接断言红;修后 route、Content-Type/原始 UTF-8 字节及静态 i18n 路由 `17 passed`,Chromium 相关语言切换用例 `1 passed`。
21+
- 最终提交后由 CI 对最终 HEAD 执行完整套件;未在本轮合并或部署。
22+
23+
### 新增测试判据
24+
25+
- `tests/unit/i18n/test_i18n.py`:改坏 `src/video_transcript_api/i18n/__init__.py` 的语言解析/请求局部翻译会破坏语言白名单与隔离断言;对应设计:仅允许 `zh-CN|en`,访客 UI 语言不得改变站点 Prompt 语言。
26+
- `tests/unit/test_deep_read_prompt_config.py`:改坏 `src/video_transcript_api/api/context.py` 的内置默认 label/template 选择会破坏中英默认 Prompt 断言;对应设计:站点语言控制任务指令,自定义 preset 原样保留。
27+
- `tests/unit/test_static_asset_versioning.py`:改坏 `src/video_transcript_api/api/app.py` 的 `/i18n.js` 显式语言解析、UTF-8 Content-Type 或正文会破坏真实响应断言;对应设计:非法/空语言 400,错误消息由站点语言编码。
28+
- `tests/integration/test_local_upload_lifecycle.py`:旧 `view_token` gate 已使真实 resolver 的三种状态页切换链接断言红;同一 HTTP 契约检查原始状态回读、切换 query、invalid view/OG 的 Content-Type 与 UTF-8 字节,以及 raw 缓存头变异红;对应设计:状态页可切换,错误消息字节正确。
29+
- `tests/og_card/test_view_meta.py`:改坏 `src/video_transcript_api/api/routes/views.py` 的 OG image URL 语言传播会破坏 `lang=zh-CN` 查询断言;对应设计:OG meta 与公开页面同语。
30+
- `tests/unit/web/test_frontend_nav.py`:改坏 `src/web/templates/transcript.html` 的 raw URL 语言参数会破坏复制/导出 URL 断言;对应设计:模板链接保留当前 UI 语言。
31+
- `e2e/tests/i18n.spec.ts`:改坏 `src/video_transcript_api/api/routes/views.py` 的请求语言切换或客户端复制 payload 会破坏真实浏览器可见断言;对应设计:切换页、按钮和提示同语且复制最终 URL。
32+
- `e2e/tests/pages.spec.ts`:改坏 `src/video_transcript_api/api/routes/views.py` 的 page 导出语言传播会破坏浏览器 page 查询断言;对应设计:`?page` 导出随 `lang`。
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# R1 独立审查结论
2+
3+
- 审查范围:base `b0ee8b7f811de31c93e261762165b7ae6e893280`..H0 `fc2d8fd7628ccebfc955025e0b6eb7b1cff359e5`;增量 H0..H1 `ef69bc5b25e10173215173fe30ce55e85c2620f1`。
4+
failure-visibility: p2-only
5+
- 未发现新增 P1 或生产功能阻断。审查覆盖公开页各状态、raw/page/script/OG、并发 `to_thread`、异常复位、clipboard、默认与自定义 Prompt、helper 消费方;未发现过度设计或新增 fallback/双路径。
6+
- H0 的 API 错误词条闭集问题和 OG 断言问题已由 H1 最小修复;增量未新增抽象。
7+
- 原剩余 P2:缺少真实 `DialogRenderer` 说话人 badge 的路由级语言断言。最终补充测试 fixture 使用结构化校对产物,在 `?lang=en` 检查 `Unconfirmed` 且保留原说话人名;该补测为 H1 之后增量,未纳入 R1 原审范围。
8+
- 另一个接受项:字体/模板异常通用 500 没有 `no-store`;正文无语言差异,保持 fail-fast,不新增通用 catch。
9+
- Reviewer 未运行测试;行为证据来自实现与 CI。结论不阻断交付。
10+
- H2 OCR 续修发生在 R1 之后;本 verdict 不代表该增量已审,后续主审负责复核。

0 commit comments

Comments
 (0)