Skip to content

纯文本 README(<pre>)完全不翻译:SKIP_SET 与 GitHub 补丁双重跳过,且无零结果提示 #27

Description

@Teeeeeeeerry

现象

GitHub 上以纯文本形式渲染的 README 完全不翻译 —— 整页一个字都不动。截图为 Linux kernel 仓库的 README(无扩展名的纯文本文件,GitHub 用等宽字体、保留空白的方式渲染,即包在 <pre> 里)。

正文全是散文式说明文字(Quick StartWho Are You?Find your role below: 等),不是代码,但一段都没被翻译。

根因

<pre> 被无条件整棵子树跳过,而且跳了两遍。

通用规则 —— src/dom/classify.ts:19SKIP_SET 的语义是「整棵子树跳过,不再深入」:

export const SKIP_SET = new Set([
  'html', 'body', 'script', 'style', 'noscript',
  'input', 'textarea', 'select', 'button',
  'code', 'pre',
]);

GitHub 域名补丁 —— src/dom/compat.ts:48 又显式跳了一次:

'pre, ' + // GitHub 的 pre 通常是代码块

这条注释本身是成立的 —— GitHub 上的 <pre> 通常确实是代码块。问题在于「通常」不是「总是」:纯文本 README、.txt、部分 .rst 在 GitHub 上都会被塞进 <pre>,内容是散文而非代码。

就算放开 pre,也还是翻不了

这一条很重要,决定了修复的复杂度。即便把 pre 从两处跳过规则里拿掉,这个 README 依然不会被翻译,因为还有三道独立的坎:

  1. pre 不在 DIRECT_SETsrc/dom/classify.ts:10),isTranslationUnit() 第一步就 return false,它永远不会成为翻译单元。
  2. 整个 README 是一个 <pre> 节点。就算把 pre 加进 DIRECT_SET,它也是「一整篇文档 = 一个翻译单元」,语义上没有意义。
  3. 长度上限会拦下它MAX_TEXT 3072 / MAX_HTML 4096(src/dom/classify.ts:34-35),这份 README 远超两者,shouldSkip() 直接拒收。

所以真正要解的不是「要不要跳过 pre」,而是**「如何把一大块预格式化文本切成有意义的翻译单元」**。

还有一层:<pre> 里的排版会被破坏

<pre> 的语义就是空白与换行原样保留。往里面插译文会带来两个问题:

  • render() 会把原文包进 .pt-origin、追加一个 display: block.pt-transsrc/styles/presets.css:5-8)。在 <pre> 内部插入块级元素会改变原本靠空白对齐的排版。
  • 截图里的 README 用了 ==== / ---- 下划线做标题、* 做项目符号,这类 ASCII 排版依赖等宽对齐。译文是中文(全角),插进去之后对齐必然错位。

这不是「不能做」,而是「做之前得先决定译文以什么形式呈现」。

修复方向

按代价从小到大:

A. 什么都不做,但让用户知道为什么(最小)

保持跳过,但在用户对整页点了翻译却零结果时给一个提示(例如 toast「本页没有可翻译的内容」)。目前的行为是静默无反应,用户无法区分「扩展坏了」和「这类内容不支持」。这一条独立成立,也适用于其它零结果场景,建议无论是否做 B/C 都先补上。

B. 按空白行切分预格式化文本(中等)

纯文本文档的段落边界就是空行。可以针对「不是代码的 pre」按连续空行切块,每块作为一个翻译单元。

难点在于判断「这个 pre 是不是代码」。可用的信号:GitHub 的语法高亮容器带有明确的类名和 <span> 着色结构,纯文本 pre 则是裸文本节点;文件名/路径也能作为线索(README 无扩展名 vs .c/.py)。这些都是 GitHub 特有的,落在 compat.ts 里比较合适 —— 但要注意该文件开头的自述「每加一条都意味着一处通用逻辑的缺陷,先问能不能改进通用规则」。

切块之后 render() 的插入方式也要重新设计,至少要避免破坏 <pre> 的空白语义(例如译文用 display: block 但不引入额外缩进,或者干脆换一种呈现方式)。

C. 通用的预格式化文本支持(最大)

把 B 的思路一般化,不限 GitHub。收益是 .txt 文档站、邮件列表归档(如 lore.kernel.org)等都能受益,代价是要设计一套「代码 vs 散文」的通用判据,误判成本高 —— 把代码翻译掉比不翻译更糟。

倾向 A 先做(独立、低风险、直接改善「静默无反应」这个最差体验),B/C 需要先定呈现方案再动手,不建议直接开做。

需要一并确认的点

  1. 代码块绝不能翻 —— 这是 SKIP_SETcode/pre 存在的原因,任何放开都必须保证代码仍然被跳过。判据宁可保守(漏翻散文)也不能激进(翻掉代码)。
  2. 行内 <code> —— SKIP_SETcodepre 是并列的,意味着散文段落中间的行内 <code> 整块也会被跳过。这对代码标识符是对的,但会不会导致含行内代码的正常段落出现问题,值得单独确认(compat.ts 的 GitHub 补丁里已经对行内 code 做了更细的判断,通用规则却没有)。
  3. 长度上限 —— 若 B 落地,切块后每块仍需满足 MAX_TEXT,超长块的处理要定。
  4. 长页面整页翻译等待过久:全量往返无渐进渲染,缓存查询串行,单次失败作废整批 #25 的关系 —— 大文档切成很多小块会显著增加请求数,应与 长页面整页翻译等待过久:全量往返无渐进渲染,缓存查询串行,单次失败作废整批 #25 的分批/渐进渲染一起考虑。

验证方式

  • 打开 Linux kernel README(纯文本 <pre>),确认要么正常分段翻译、要么给出明确提示
  • 打开任意含语法高亮代码块的 GitHub 页面,确认代码仍然不被翻译
  • 打开含行内 <code> 的普通 Markdown README,确认正文段落翻译不受影响
  • 整页翻译零结果时,确认有明确提示而非静默无反应

关联


另外,截图右侧边缘可以看到悬浮球只露出了一半,被视口边界切掉 —— 与 #26 记录的「位置无视口钳制」一致,可作为该 issue 的一个补充实例。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions