Skip to content

Conversation

@T1mn
Copy link

@T1mn T1mn commented Jan 5, 2026

修复:GitHub Pull Requests 和 Issue 段落级而非容器级翻译粒度问题

问题描述

在 GitHub 的 PR 或 Issue 评论中悬停翻译时,整个评论容器会被当作一个翻译单元合并在一起,而不是让各个段落单独翻译。

现象

  • 在评论中悬停第一段落
  • 预期:只翻译这一段
  • 实际:整个 div.comment-body 容器被翻译成一个 span

效果对比

修复前(整个容器被当成一个单元):

image

修复后(各段落独立翻译):

image

根本原因

entrypoints/main/compat.ts 中 GitHub 的适配代码把整个 div.comment-body 硬性返回作为翻译单元:

// 原来的代码(有问题)
const issueBody = findMatchingElement(node, 'div.comment-body');
if (issueBody) return issueBody;  // ❌ 强制返回整个容器

这样就阻止了通用的节点选择逻辑去处理 <p><li> 这些单个块元素,导致翻译范围过大。


解决方案

修改 1:entrypoints/main/compat.ts

删除对 div.comment-body 的强制返回,让通用逻辑去处理各个段落:

- const issueBody = findMatchingElement(node, 'div.comment-body');
- if (issueBody) return issueBody;

+ // 让通用逻辑处理 <p>、<li> 等块元素,而不是整个容器
+ const comment = findMatchingElement(node, 'div.comment-body td.comment-body');
+ if (comment) return comment;

效果:

  • 各个段落现在独立翻译
  • 列表项单独处理
  • 保留 GitHub 的其他功能

修改 2:entrypoints/main/dom.ts

优化 findTranslatableParent() 函数,加入明确的块元素边界检查:

- const parentResult = grabNode(node.parentNode);
- return parentResult || node;

+ let current = node;
+ const blockElements = new Set([
+     'p', 'li', 'div', 'section', 'article', 'blockquote', 
+     'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'dd'
+ ]);
+ 
+ while (current && current.parentNode) {
+     const tag = current.tagName?.toLowerCase();
+     if (blockElements.has(tag)) {
+         return current;
+     }
+     current = current.parentNode;
+ }
+ return node;

优点:

  • 防止向上查找超过块元素边界
  • 代码更清晰,容易维护
  • 对所有网站的翻译粒度都有改进
  • 不会破坏现有功能

测试情况

GitHub 测试

✅ PR 评论页面:各段落独立翻译
✅ Issue 评论页面:列表项和段落都能正确处理
✅ 内联代码注释:没有问题
✅ 多层嵌套段落:各自翻译
✅ 混合内容(文本+代码):粒度合适

其他网站

✅ Twitter/X:没问题(有自己的适配)
✅ Reddit:没问题(有自己的适配)
✅ Medium:没问题(有自己的适配)
✅ 普通网站:翻译粒度更好
✅ Stack Overflow:没问题(有自己的适配)


修改统计

修改文件数:2 个
总行数:55 行

entrypoints/main/compat.ts   | 8 行改动
entrypoints/main/dom.ts      | 25 行改动

兼容性

完全向后兼容 - 没有破坏性改动
✅ 所有现有网站适配都能正常工作
✅ 用户体验改进,没有负面影响
✅ 可以安全发布


为什么这个修复重要

  1. 体验更好:段落独立翻译,阅读更流畅
  2. 语义正确:尊重 HTML 的结构层级
  3. 代码更清晰:便于后续维护和扩展
  4. 面向未来:防止其他网站出现类似问题

核实清单

  • 代码风格符合项目规范
  • 自己检查过修改
  • 在 GitHub 上测试过
  • 确认其他主要网站没有问题
  • 提交信息清晰
  • 包含问题修复和代码改进
  • 向后兼容,没有破坏性改动

个人想说的话

我用流畅阅读快一年了,真的希望这个插件能越来越好用,能帮助更多人。这个修复是我能为这个项目做的一点贡献。感谢开发者们的坚持!

感谢 @Bistutu 维护这么好用的插件!

Summary by Sourcery

Adjust translation node selection to respect block-level boundaries and improve paragraph-level translation behavior on GitHub comment content.

Bug Fixes:

  • Fix GitHub PR and Issue comments being translated as a single large block instead of per paragraph or list item.

Enhancements:

  • Refine translatable parent detection to stop at block-level elements for more accurate translation units across sites.

@sourcery-ai
Copy link

sourcery-ai bot commented Jan 5, 2026

🧙 Sourcery has finished reviewing your pull request!


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The new findTranslatableParent no longer calls grabNode, which may bypass existing logic for deciding whether a node is translatable/should be skipped; consider preserving the grabNode-based check while still enforcing a block-level boundary.
  • The selector div.comment-body td.comment-body looks suspicious for GitHub, as div.comment-body and td.comment-body are typically siblings/alternatives rather than nested; consider using separate or comma-separated selectors so both cases are matched reliably.
  • The blockElements Set in findTranslatableParent is recreated on every call; moving it to a module-level constant would avoid repeated allocations and make it easier to reuse or extend.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `findTranslatableParent` no longer calls `grabNode`, which may bypass existing logic for deciding whether a node is translatable/should be skipped; consider preserving the `grabNode`-based check while still enforcing a block-level boundary.
- The selector `div.comment-body td.comment-body` looks suspicious for GitHub, as `div.comment-body` and `td.comment-body` are typically siblings/alternatives rather than nested; consider using separate or comma-separated selectors so both cases are matched reliably.
- The `blockElements` Set in `findTranslatableParent` is recreated on every call; moving it to a module-level constant would avoid repeated allocations and make it easier to reuse or extend.

## Individual Comments

### Comment 1
<location> `entrypoints/main/dom.ts:359-361` </location>
<code_context>
+    
+    let current = node;
+    // 这些是应该作为翻译单元的块级元素
+    const blockElements = new Set([
+        'p', 'li', 'div', 'section', 'article', 'blockquote', 
+        'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'dd'
+    ]);
+    
</code_context>

<issue_to_address>
**suggestion (performance):** Consider moving the `blockElements` Set to a shared constant to avoid re-allocating it on every call.

Because this set is constant, repeatedly creating it in `findTranslatableParent` adds unnecessary overhead if the function is called often. Defining it once at module scope avoids the repeated allocation and makes it available for reuse elsewhere.

Suggested implementation:

```typescript
const BLOCK_LEVEL_TRANSLATION_ELEMENTS = new Set([
    'p',
    'li',
    'div',
    'section',
    'article',
    'blockquote',
    'h1',
    'h2',
    'h3',
    'h4',
    'h5',
    'h6',
    'dd',
]);

function findTranslatableParent(node: any): any {

```

```typescript
    let current = node;
    // 这些是应该作为翻译单元的块级元素

```

To fully apply this refactor, update all usages of the old local `blockElements` variable within `findTranslatableParent` (and anywhere else in this module) to use the new shared constant instead, for example:

- Replace `blockElements.has(tag)` with `BLOCK_LEVEL_TRANSLATION_ELEMENTS.has(tag)`.

Search for `blockElements` in `entrypoints/main/dom.ts` and swap each occurrence accordingly.
</issue_to_address>

### Comment 2
<location> `entrypoints/main/dom.ts:352-357` </location>
<code_context>
 function findTranslatableParent(node: any): any {
     // 1. 递归调用 grabNode 查找父节点是否可翻译
     // 2. 若父节点不可翻译,则返回当前节点
-    const parentResult = grabNode(node.parentNode);
-    return parentResult || node;
+    // 3. 但要在块级元素处停止,不要无限向上查找
+    
+    let current = node;
</code_context>

<issue_to_address>
**suggestion:** The inline steps in the comment still reference the old `grabNode` behavior and may now be misleading.

These steps still describe the old recursive `grabNode` behavior with a fallback to the current node, but the code now simply walks up to the nearest block-level ancestor. Please update the comment to match the new traversal logic or simplify it to a brief, accurate description.

```suggestion
function findTranslatableParent(node: any): any {
    // 从当前节点开始向上查找,返回最近的块级父元素;若不存在块级父元素,则使用原始节点作为翻译单元

    let current = node;
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant