Skip to content

feat: 新增视频字幕翻译功能,支持 YouTube 平台#204

Open
airhunter wants to merge 15 commits intoBistutu:mainfrom
airhunter:feature/youtube-support
Open

feat: 新增视频字幕翻译功能,支持 YouTube 平台#204
airhunter wants to merge 15 commits intoBistutu:mainfrom
airhunter:feature/youtube-support

Conversation

@airhunter
Copy link
Copy Markdown

@airhunter airhunter commented Apr 1, 2026

功能概述

新增视频字幕翻译功能,在 YouTube 等平台开启原生字幕后,
自动拦截字幕数据、翻译并以双语形式叠加显示在视频上。

新增文件

  • entrypoints/video/manager.ts — 核心调度:拦截消息、解析、翻译、渲染
  • entrypoints/video/overlay.ts — 字幕浮层渲染,RAF 时间轴同步 + 二分搜索
  • entrypoints/video/parser.ts — 支持 YouTube XML、JSON3、WebVTT 三种格式
  • entrypoints/video/platforms.ts — 平台配置(域名、字幕 URL 规则)
  • public/video-subtitle-inject.js — MAIN world 注入脚本,Hook XHR/fetch 拦截字幕请求

修改文件

  • components/Main.vue — 新增视频字幕翻译开关(默认开启)
  • entrypoints/content.ts — 初始化字幕翻译模块
  • entrypoints/utils/model.ts — 新增 enableVideoSubtitle 配置项
  • wxt.config.ts — 注册 MAIN world content script,限定支持平台域名

技术方案

字幕拦截
通过 MAIN world content script 在 document_start 注入,绕过页面 CSP 限制,
Hook XHR/fetch 拦截字幕请求,通过 postMessage 传递给 content script。

翻译质量
YouTube ASR 字幕无标点、全小写,使用时间间隔(< 600ms)作为断句信号,
将连续的碎片 cue 合并为完整句子组后翻译,解决"united states"等跨 cue 短语被切断的问题。
同时引入 carry-over 机制,防止句尾碎片(如"but the seeds")因词数上限而被孤立。

翻译调用
每批 5 组,附带前文上下文,字幕专用提示词告知模型允许跨行借用语义,
译文回填到组内所有 cue,组内 cue 共享同一译文。

渲染
requestAnimationFrame 驱动时间轴同步,二分搜索定位当前 cue,
使用 createElement + textContent 安全写入 DOM。
YouTube 播放器工具栏注入快捷开关按钮,支持实时切换显示/隐藏。

安全

  • 所有 DOM 写入改用 createElement + textContent,消除 XSS 风险
  • SVG 图标改用 createElementNS 构建,移除 innerHTML
  • MAIN world 脚本注入范围限定为已支持的平台域名,不注入无关站点

Summary by Sourcery

Add a video subtitle translation pipeline for supported video platforms and wire it into the extension configuration and content scripts.

New Features:

  • Introduce configurable video subtitle translation, enabled by default and controllable via the main UI switch.
  • Implement a video subtitle manager that intercepts subtitle network traffic, parses multiple subtitle formats, batches translations, and renders bilingual overlays synchronized with video playback.
  • Add a YouTube-specific in-player toggle button to quickly enable or disable translated subtitles.
  • Provide a main-world injected script to hook XHR/fetch subtitle requests and communicate them to the content script, with dynamic subtitle URL pattern configuration.
  • Define per-platform configurations for video elements, containers, and subtitle URL patterns, including a generic WebVTT fallback.

Enhancements:

  • Update extension manifest and WXT config to register a main-world content script for subtitle interception on selected video-learning domains.
  • Refine DOM manipulation for newly added UI and overlay elements to avoid innerHTML and reduce XSS risk by using element creation APIs.

airhunter and others added 14 commits April 1, 2026 16:49
- 添加视频字幕翻译开关(默认开启)
- 在 content.ts 中初始化视频字幕翻译模块
- 在 Config 中新增 enableVideoSubtitle 配置项
- 通过 manifest content_scripts 注入 MAIN world 脚本以绕过 YouTube CSP 限制

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
安全修复(XSS):
- overlay.ts:用 createElement + textContent 替换 innerHTML,彻底消除 XSS 风险
- manager.ts:buildBtnSvg 改为返回 SVGElement(createElementNS),不再使用 innerHTML

性能优化:
- overlay.ts:findCue 由线性扫描改为二分搜索,大幅降低长视频每帧开销

注入范围收窄:
- wxt.config.ts:MAIN world 脚本的 matches 从 <all_urls> 缩小到已支持的具体平台域名

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
关闭翻译时调用 restoreNativeSubtitle(),开启时调用 hideNativeSubtitle(),
避免用户关闭翻译后原生字幕仍处于 display:none 导致无字幕可看。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mount() 修改 static 元素为 position:relative 前,先保存其原始内联
position 值;cleanup() 时还原,避免残留样式影响宿主页面布局。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- manager.ts:批量翻译改用 [N] 编号标记替代 ⌿ 分隔符,
  避免翻译 API 破坏分隔符导致拆分错位或翻译缺失
- overlay.ts:findCue 支持 overlap cue,
  同一时间点有多条时取最后开始的一条,
  兼容 YouTube 滚动字幕(前一行残留 + 新行同时存在)的场景

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
新增 mergeSentenceGroups():将连续碎片 cue(不以句末标点结尾、
下一条首字母小写)合并为完整句子后再送翻译,译文回填到组内
所有 cue,使碎片时间段也能显示完整句子的译文。

判断逻辑:
- 以 .!?。!?… 结尾 → 完整句,断开
- 下一条首字母大写 → 新句,断开
- 超过 MAX_GROUP_SIZE(6) 条 → 强制断开

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
达到 MAX_GROUP_SIZE 时若末尾 cue 仍是碎片(无句末标点),
将其"进位"到下一组开头,确保与续句合并后一起翻译。

修复前:["...but the seeds"] | ["of animosity..."] 各自翻译
修复后:["..."] | ["but the seeds" + "of animosity..."] 合并翻译

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 每批翻译前加 subtitle 专用指令,告知模型这是字幕碎片、
  需结合相邻行保持语义连贯、行数必须一一对应
- 带入上一批最后一组的结尾 12 个词作为 [previous context],
  让模型理解跨批边界的句子续接,避免首行翻译因缺乏前文而语序错乱

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
移除 SentenceGroup、mergeSentenceGroups 及相关常量,
每条 cue 直接对应一条译文,英中字幕严格一一对应。
跨批边界的连贯性由 [previous context] 前缀和字幕专用
指令交给模型处理,代码逻辑大幅简化。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. parser.ts:parseYouTubeXML 新增 mergeOverlappingCues(),
   合并时间重叠的相邻 cue(YouTube 滚动字幕特性),
   使碎片句在解析阶段就拼成完整句子

2. manager.ts:恢复 mergeSentenceGroups() 句子合并逻辑,
   MAX_GROUP_SIZE 由 6 调大至 8,减少强制截断频率

3. manager.ts:BATCH_SIZE 由 15 降至 5,
   每批上下文更集中,翻译连贯性更好

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
移除 sentence merging,改为每批 5 条逐条翻译:
- 每批在正文前后各附 CONTEXT_SIZE=2 条 [context before/after]
- 提示词明确允许模型跨行借用语义处理碎片句
- 英中 cue 严格一一对应,无对齐问题

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
新增 mergeByTimeGap(),按说话停顿时长决定句子边界:
- 相邻 cue 间隔 < 1500ms → 同一句,合并
- 间隔 ≥ 1500ms 或超过 19 词 → 新句,断开

相比文本特征(标点/大写),时间信号对无标点全小写的
YouTube ASR 字幕同样有效,且能正确合并跨 cue 词组
(如 "the united" + "states")。

参考:沉浸式翻译 ytAsrConfig mergeConfig 同款策略。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MERGE_GAP_MS 从 1500 降至 600ms,避免不同句子因短暂停顿
  被合并成一大段导致翻译语义错乱
- MAX_WORDS 触发时若下一条仍是小间隔,将末尾 cue 进位到下一组,
  防止碎片句(如 "but the seeds")被孤立翻译

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Apr 1, 2026

Reviewer's Guide

Implements a new video subtitle translation pipeline for YouTube and generic VTT-based platforms by injecting a MAIN-world network hook script, parsing multiple subtitle formats, grouping and batch-translating cues, and rendering a bilingual overlay synchronized to the video timeline, with a UI toggle and configuration flag to control the feature.

Sequence diagram for video subtitle interception and translation pipeline

sequenceDiagram
    actor User
    participant YouTubePage
    participant MainWorldScript as MainWorldScript_video_subtitle_inject
    participant ContentScript as ContentScript
    participant VideoManager as VideoSubtitleManager
    participant SubtitleParser as SubtitleParser
    participant Overlay as SubtitleOverlay
    participant TranslateApi as TranslateApi
    participant Config as ConfigStore

    User->>YouTubePage: Enable native subtitles

    ContentScript->>Config: Read enableVideoSubtitle
    Config-->>ContentScript: enableVideoSubtitle=true
    ContentScript->>VideoManager: initVideoSubtitle()
    VideoManager->>MainWorldScript: window.postMessage config(patterns)
    VideoManager->>VideoManager: attachMessageListener()
    VideoManager->>VideoManager: watchNavigation()

    Note over MainWorldScript,YouTubePage: Network interception in MAIN world
    YouTubePage->>MainWorldScript: XHR or fetch to subtitle URL
    MainWorldScript->>MainWorldScript: isSubtitleUrl(url)
    MainWorldScript-->>YouTubePage: Proceed with request
    YouTubePage-->>MainWorldScript: Response with subtitle data
    MainWorldScript->>MainWorldScript: Cache lastCapture
    MainWorldScript->>ContentScript: window.postMessage subtitle-captured(url,data)

    ContentScript->>VideoManager: window message event
    VideoManager->>VideoManager: handleSubtitleData(url,data)
    VideoManager->>SubtitleParser: detectSubtitleFormat(url,data)
    SubtitleParser-->>VideoManager: format
    VideoManager->>SubtitleParser: parseYouTubeXML/parseYouTubeJSON3/parseVTT
    SubtitleParser-->>VideoManager: SubtitleCue[]

    VideoManager->>YouTubePage: findVideo()
    YouTubePage-->>VideoManager: HTMLVideoElement
    VideoManager->>YouTubePage: findMountTarget(video)
    YouTubePage-->>VideoManager: mountTarget

    VideoManager->>Overlay: mount(video,mountTarget)
    VideoManager->>Overlay: setCues(cues) with original text
    VideoManager->>YouTubePage: hideNativeSubtitle()
    VideoManager->>YouTubePage: mountQuickButton()

    loop Playback
        YouTubePage->>Overlay: video.currentTime via requestAnimationFrame
        Overlay->>Overlay: findCue(time) via binary search
        Overlay->>Overlay: render(cue) bilingual overlay
    end

    loop Batch translation
        VideoManager->>VideoManager: mergeByTimeGap(cues)
        VideoManager->>TranslateApi: translateText(batchWithContext,document.title)
        TranslateApi-->>VideoManager: translatedLines
        VideoManager->>VideoManager: Fill translatedText on SentenceGroup.cues
        VideoManager->>Overlay: setCues(updatedCues)
    end

    User->>YouTubePage: Click quick toggle button
    YouTubePage->>VideoManager: Toggle subtitleEnabled
    alt subtitleEnabled
        VideoManager->>YouTubePage: hideNativeSubtitle()
        VideoManager->>Overlay: show()
    else not subtitleEnabled
        VideoManager->>Overlay: hide()
        VideoManager->>YouTubePage: restoreNativeSubtitle()
    end
Loading

Updated class diagram for subtitle translation types and configuration

classDiagram
    class Config {
        +boolean on
        +boolean translationStatus
        +string inputBoxTranslationTrigger
        +string inputBoxTranslationTarget
        +boolean enableVideoSubtitle
        +Config()
    }

    class SubtitleCue {
        +number start
        +number end
        +string text
        +string translatedText
    }

    class SubtitleOverlay {
        -HTMLElement container
        -HTMLVideoElement video
        -SubtitleCue[] cues
        -number rafId
        -string lastCueKey
        -HTMLElement mountTarget
        -string originalMountPosition
        +mount(video, mountTarget)
        +setCues(cues)
        +show()
        +hide()
        +cleanup()
        -startLoop()
        -findCue(time)
        -render(cue)
    }

    class PlatformConfig {
        +string id
        +string[] matches
        +string[] subtitleUrlPatterns
        +string format
        +string videoSelector
        +string containerSelector
        +string hideNativeSelector
    }

    class PlatformsModule {
        +PlatformConfig[] platforms
        +detectPlatform(hostname)
        +getAllSubtitlePatterns()
    }

    class ParserModule {
        +parseYouTubeXML(xmlText) SubtitleCue[]
        +parseYouTubeJSON3(jsonText) SubtitleCue[]
        +parseVTT(vttText) SubtitleCue[]
        +detectSubtitleFormat(url, data) string
        -mergeOverlappingCues(cues) SubtitleCue[]
        -vttTimeToSeconds(t) number
        -stripVttTags(text) string
        -decodeEntities(text) string
    }

    class VideoSubtitleManager {
        -SubtitleOverlay overlay
        -boolean listenerAttached
        -string processingUrl
        -boolean subtitleEnabled
        +initVideoSubtitle()
        -sendConfig()
        -attachMessageListener()
        -handleSubtitleData(url, rawData)
        -mergeByTimeGap(cues) SentenceGroup[]
        -translateCuesBatched(cues, onProgress)
        -mountQuickButton()
        -buildBtnSvg(active) SVGElement
        -waitForElement(selector, callback, maxMs)
        -findVideo() HTMLVideoElement
        -findMountTarget(video) HTMLElement
        -hideNativeSubtitle()
        -restoreNativeSubtitle()
        -watchNavigation()
    }

    class SentenceGroup {
        +SubtitleCue[] cues
        +string text
    }

    Config <.. VideoSubtitleManager : reads
    SubtitleCue <.. SentenceGroup : element
    SubtitleOverlay o-- SubtitleCue : renders
    VideoSubtitleManager o-- SubtitleOverlay : owns
    VideoSubtitleManager ..> ParserModule : uses
    VideoSubtitleManager ..> PlatformsModule : uses
    PlatformsModule o-- PlatformConfig : aggregates
    ParserModule o-- SubtitleCue : creates
Loading

File-Level Changes

Change Details Files
Add a MAIN-world content script that hooks XHR/fetch to capture subtitle responses and forwards them to the extension via postMessage, configurable by regex-based URL patterns.
  • Define an IIFE that hooks XMLHttpRequest.open/send to record request URLs and, on successful responses, forwards subtitle responseText when the URL matches configured patterns.
  • Wrap window.fetch to detect subtitle URLs, clone responses, and forward the text content while preserving normal response behavior.
  • Maintain a lastCapture cache so captured subtitles can be resent when the content script becomes ready.
  • Listen for configuration messages from the content script to update subtitle URL regex patterns and send any cached capture back; send an initial 'ready' message on init.
public/video-subtitle-inject.js
Introduce a video subtitle management module that receives captured subtitle data, parses it, groups cues into sentence-like units, batch-translates them, and drives a custom overlay plus a YouTube toolbar toggle button.
  • Expose initVideoSubtitle from the content script side, which checks the config flag, sends subtitle pattern config to the injected script, attaches a postMessage listener, sets up SPA navigation watching, and mounts a YouTube quick toggle button when applicable.
  • Implement a postMessage listener that de-duplicates by URL, detects subtitle format, parses cues, mounts the overlay on the correct video/container, hides native subtitles, and then performs progressive batch translation with overlay updates.
  • Add time-gap-based cue grouping (mergeByTimeGap) with a carry-over mechanism to avoid isolating short end fragments and to merge ASR fragments into sentence groups before translation.
  • Implement translateCuesBatched to build a numbered, context-aware prompt, call translateText, map returned lines back to groups, and assign translatedText to each cue, falling back to original text on errors.
  • Add a YouTube-specific quick toggle button (built via createElement/createElementNS) injected into .ytp-right-controls, which toggles subtitleEnabled, shows/hides the overlay, and hides/restores native subtitles; reset state on SPA navigation via yt-navigate-finish and a title MutationObserver.
entrypoints/video/manager.ts
Create a reusable subtitle overlay component that runs an rAF-driven timeline loop, uses binary search to pick the active cue, and renders bilingual lines using safe DOM APIs.
  • Define SubtitleOverlay with lifecycle methods mount, setCues, show, hide, and cleanup, tracking the associated video, container, mount target, and previous CSS position.
  • On mount, create an absolutely positioned overlay div inside the video container, adjust the container to position:relative if needed, then start an rAF loop that queries video.currentTime and finds the active cue via binary search including overlapping cues.
  • Render subtitles by clearing and rebuilding children using createElement + textContent, showing original and translated lines in bilingual mode based on config.display, with styling tuned for readability.
  • Ensure cleanup cancels rAF, removes the overlay element, and restores the original container position to avoid layout side effects on navigation.
entrypoints/video/overlay.ts
Implement multi-format subtitle parsing utilities for YouTube XML, YouTube JSON3, and WebVTT formats with helper functions for time conversion and entity handling.
  • Define a common SubtitleCue interface with start/end times, original text, and optional translatedText.
  • Implement parseYouTubeXML using DOMParser to read nodes, decode entities, and then merge overlapping cues (mergeOverlappingCues) to rebuild full lines from scrolling captions.
  • Implement parseVTT that normalizes newlines, block-splits cues, parses time ranges, strips VTT markup tags, and joins cue text lines into a single string per cue.
  • Implement parseYouTubeJSON3 that parses events, concatenates segs.utf8, and converts ms-based times into seconds, skipping empty lines.
  • Add detectSubtitleFormat that uses content heuristics and URL hints to choose among YouTube XML, JSON3, and VTT, and helper utilities for VTT time parsing, entity decoding, and tag stripping.
entrypoints/video/parser.ts
Add platform configuration to map hostnames to video/subtitle selectors and subtitle URL patterns, and expose utilities to detect the platform and aggregate patterns for the injected script.
  • Define PlatformConfig with hostname matches, subtitle URL regex strings, expected format, video/container selectors, and optional native subtitle selectors to hide.
  • Add a YouTube config that targets youtube.com/youtubekids.com, matches /api/timedtext, selects the HTML5 player video/container, and hides .ytp-caption-window-container.
  • Add a generic VTT config that matches all hosts, looking for .vtt and common subtitle paths, selecting a generic
  • Implement detectPlatform to prefer non-generic configs based on hostname substring, falling back to generic VTT, and getAllSubtitlePatterns to dedupe and aggregate all subtitle regexes for MAIN-world injection.
entrypoints/video/platforms.ts
Wire the new video subtitle feature into the extension’s configuration, content script initialization, and popup UI, and register the MAIN-world script in the extension manifest for supported domains only.
  • Extend the Config model with an enableVideoSubtitle boolean field, defaulting to true in the constructor, and persist it like other settings.
  • In the main Vue UI, add a labeled section shown when the extension is on that explains the video subtitle translation feature and binds an el-switch to config.enableVideoSubtitle for user control.
  • Update the main content script entrypoint to import initVideoSubtitle from the new manager module and call it during onMount to initialize video subtitle translation on supported pages.
  • Update wxt.config.ts manifest to add a content_scripts entry that injects video-subtitle-inject.js in the MAIN world at document_start for a curated list of video platforms (YouTube, YouTube Kids, Udemy, Coursera, Khan Academy), with comments explaining CSP bypass and scope control.
entrypoints/utils/model.ts
components/Main.vue
entrypoints/content.ts
wxt.config.ts

Possibly linked issues

  • #: PR 实现 YouTube 字幕拦截、翻译和双语叠加显示,满足“添加 YouTube 双语字幕功能”需求。

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
Copy Markdown

@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 YouTube-specific behavior is scattered (e.g., hostname checks in both initVideoSubtitle and watchNavigation plus CSS selectors in platforms.ts); consider centralizing platform-specific logic (including whether to mount the quick button) into the PlatformConfig so that adding/removing platforms doesn’t require touching multiple files.
  • In SubtitleOverlay.mount, you call cleanup() after assigning this.video but before this.mountTarget, and cleanup() removes #fr-subtitle-overlay globally; if multiple overlays are ever mounted (e.g., multiple videos or future features), this global removal could become surprising—consider scoping cleanup to the instance’s container instead of using document.getElementById.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The YouTube-specific behavior is scattered (e.g., hostname checks in both `initVideoSubtitle` and `watchNavigation` plus CSS selectors in `platforms.ts`); consider centralizing platform-specific logic (including whether to mount the quick button) into the `PlatformConfig` so that adding/removing platforms doesn’t require touching multiple files.
- In `SubtitleOverlay.mount`, you call `cleanup()` after assigning `this.video` but before `this.mountTarget`, and `cleanup()` removes `#fr-subtitle-overlay` globally; if multiple overlays are ever mounted (e.g., multiple videos or future features), this global removal could become surprising—consider scoping cleanup to the instance’s container instead of using `document.getElementById`.

## Individual Comments

### Comment 1
<location path="entrypoints/video/manager.ts" line_range="290-295" />
<code_context>
+    return (video.parentElement as HTMLElement) || document.body
+}
+
+function hideNativeSubtitle() {
+    const platform = detectPlatform(window.location.hostname)
+    if (!platform.hideNativeSelector) return
+    // 用 display:none 彻底隐藏,visibility:hidden 仍占位且有时被 YouTube 重置
+    document.querySelectorAll<HTMLElement>(platform.hideNativeSelector)
+        .forEach(el => el.style.setProperty('display', 'none', 'important'))
+}
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Hiding native subtitles overwrites `display` without preserving original values, which can interfere with site styling.

Because `hideNativeSubtitle` sets `display: none !important` and the corresponding restore logic only calls `removeProperty('display')`, any pre-existing inline `display` value is lost after the first toggle, which can change how the page lays out those elements.

Consider storing the previous inline `display` value (e.g. in a `data-` attribute) before overriding it, and restoring from that when subtitles are re-enabled, so the page’s original layout is preserved.
</issue_to_address>

### Comment 2
<location path="entrypoints/video/overlay.ts" line_range="67" />
<code_context>
+            cancelAnimationFrame(this.rafId)
+            this.rafId = null
+        }
+        document.getElementById(OVERLAY_ID)?.remove()
+        if (this.mountTarget !== undefined && this.originalMountPosition !== undefined) {
+            this.mountTarget.style.position = this.originalMountPosition
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Cleanup removes any element with the overlay ID globally instead of just this instance’s container.

Using `document.getElementById(OVERLAY_ID)?.remove()` works now but risks removing an unrelated element if the host page reuses that ID, and it’s not tied to this instance. Calling `this.container?.remove()` would scope cleanup to the overlay created by this instance and better support multiple overlays or non-document mount points.

```suggestion
        this.container?.remove()
```
</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.

Comment thread entrypoints/video/manager.ts Outdated
Comment thread entrypoints/video/overlay.ts Outdated
- manager.ts:hideNativeSubtitle 隐藏前将原始内联 display 存入
  data-fr-orig-display,restoreNativeSubtitle 从中还原,
  避免覆盖宿主页面预设的 display 样式
- overlay.ts:cleanup 改用 this.container?.remove() 替代
  document.getElementById,避免误删宿主页面中同名元素

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@airhunter
Copy link
Copy Markdown
Author

@Bistutu Hi, I've added support for YouTube subtitle translations.
Could you please review this PR when you have time? Thanks!

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