Skip to content

feat 阅读Markdown的翻译功能#130

Merged
AmintaCCCP merged 23 commits intoAmintaCCCP:mainfrom
SummerRay160:Dev
May 4, 2026
Merged

feat 阅读Markdown的翻译功能#130
AmintaCCCP merged 23 commits intoAmintaCCCP:mainfrom
SummerRay160:Dev

Conversation

@SummerRay160
Copy link
Copy Markdown
Contributor

@SummerRay160 SummerRay160 commented Apr 30, 2026

Summary by CodeRabbit

  • New Features

    • Bilingual README viewer with translate/revert controls, three display modes (original/translated/bilingual), and optional auto-translate.
    • Per-heading translation support, improved heading navigation, and a resizable table-of-contents sidebar.
    • Visible translation progress, retry/abort flows, and robust handling for large documents and inline code.
  • Style

    • Reworked brand color tokens affecting UI color theming.

SummerRay160 and others added 11 commits April 30, 2026 04:11
refactor(RepositoryCard): 增强工具提示在浅色模式下的可读性
feat(ReadmeModal): 添加字体大小类型支持
style(index.css): 改进文本区域和输入框的浅色模式样式
refactor(MarkdownRenderer): 移除行号显示并支持字体大小调整
refactor(RepositoryEditModal): 优化浅色模式下的表单样式和交互
Adjust Tailwind typography classes in MarkdownRenderer: use 'prose prose-sm' for the 'small' fontSize instead of 'prose-xs', and remove the explicit 'prose-sm' for the medium/default case to fall back to the base 'prose' class. This normalizes sizing across fontSize options.
Fix stacking and improve the AI search tooltip UI and copy. Lowered SortByDropdown menu z-index to z-40 and added explicit z-indexes for the search input (z-40) and sort controls (z-30) to resolve overlay issues. Reworked the info tooltip: wider, responsive sizing, unified light/dark backgrounds and borders, improved text color/spacing, enabled word-wrapping, and adjusted the tooltip arrow styling/position. Also replaced the previous conditional AI copy with a single bilingual description explaining the multi-dimensional weighted search/ranking algorithm.
添加 hasHydrated 状态用于检测 store 是否完成水合
在 App 组件中添加加载状态以确保主题正确应用
更新搜索算法权重描述文案
根据activeAIConfig状态动态显示不同的搜索模式说明,当启用AI配置时显示语义搜索模式说明,否则显示本地智能排序说明
将加载状态的渲染逻辑移动到useEffect之后,使组件初始化逻辑更清晰
将加载状态的渲染逻辑移到认证检查之前,确保主题正确应用后再进行认证检查
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
实现文档翻译核心功能,包括:
1. 添加query-string依赖处理URL参数
2. 创建翻译服务模块调用微软翻译API
3. 实现Markdown分割与重组工具
4. 在ReadmeModal中添加翻译UI控件
5. 创建useMarkdownTranslation自定义hook管理翻译状态
@SummerRay160
Copy link
Copy Markdown
Contributor Author

目前测试还有很多问题,个人能力有限。可以的话帮忙改下

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 623bf46c-9d68-4f3d-9fb6-93cb068ad336

📥 Commits

Reviewing files that changed from the base of the PR and between 6a39a2d and 57b5855.

📒 Files selected for processing (1)
  • src/services/translateService.ts
✅ Files skipped from review due to trivial changes (1)
  • src/services/translateService.ts

📝 Walkthrough

Walkthrough

Adds bilingual README translation: a translation service, DOM text scanner, and a BilingualMarkdownRenderer with translate/revert handles; ReadmeModal is wired for translation UI, TOC bilingual labels, and resizable TOC. Also adds language helpers, normalizes heading text in MarkdownRenderer, updates Tailwind colors, and adds a query-string dependency.

Changes

Bilingual README translation feature

Layer / File(s) Summary
Types / Data Shape
src/services/translateService.ts, src/components/BilingualMarkdownRenderer.tsx, src/utils/markdownSplitter.ts
Adds TranslateResult, TranslateOptions, DisplayMode, TranslationStatus, DetectedLanguage and related helpers for translation direction and detection.
Core Services / Utilities
src/services/translateService.ts, src/utils/markdownSplitter.ts, src/utils/domTextScanner.ts
Implements Microsoft Translator auth/token caching and retry (apiMsAuth, withTranslateRetry), translateText/translateBatch/clearTranslateCache; adds text cleanup, language detection, and DOM scanning/wrapping helpers with ATTR_ORIGINAL/ATTR_TRANSLATION.
Rendering / Component Core
src/components/BilingualMarkdownRenderer.tsx, src/components/MarkdownRenderer.tsx
New BilingualMarkdownRenderer (forwardRef) that scans DOM, batches translations, inserts/removes bilingual nodes, exposes translate/revert/getStatus, manages displayMode/progress/error; MarkdownRenderer.extractTextFromChildren now normalizes whitespace for heading id derivation.
Integration / UI Wiring
src/components/ReadmeModal.tsx
Wires BilingualMarkdownRenderer into ReadmeModal, adds translation lifecycle callbacks, header translation controls (translate/retry/close, display-mode toggle), bilingual-aware TOC labels/scrolling, and resizable TOC sidebar; resets/reverts translation on close.
Config / Dependencies
package.json, tailwind.config.js
Adds query-string and its types; restructures Tailwind color tokens from flat brand-* keys to nested brand.{indigo,violet,hover}.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Readme as ReadmeModal
    participant Renderer as BilingualMarkdownRenderer
    participant Scanner as domTextScanner
    participant Service as translateService
    participant API as TranslatorAPI

    User->>Readme: Click "Translate"
    Readme->>Renderer: translate()
    Renderer->>Scanner: scanDomForTranslation(container)
    Scanner-->>Renderer: segments[]
    Renderer->>Service: translateBatch(segments)
    Service->>API: apiMsAuth, POST translate requests
    API-->>Service: translations
    Service-->>Renderer: translated segments
    Renderer->>Renderer: insert ATTR_TRANSLATION nodes / update progress
    Renderer-->>Readme: onHeadingsTranslated / status updates
    Readme-->>User: update UI (progress, mode, TOC labels)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

I nibble lines and hop through code,
I fetch the words where headings strode.
With tiny paws I stitch and sow,
Translated leaves in gentle row.
🐰✨ Readmes bloom in two-tone mode.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat 阅读Markdown的翻译功能' (feat: Translation feature for reading Markdown) accurately summarizes the PR's main objective of introducing translation functionality for Markdown content.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/ReadmeModal.tsx (1)

158-206: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Recompute TOC and heading IDs from the rendered content.

After translation, MarkdownRenderer gets displayContent, but tocItems, headingIdMap, and the observer lifecycle are still derived from readmeContent. That means TOC clicks and active-section highlighting stop matching as soon as the heading text changes in the translated README.

Also applies to: 235-235, 266-270, 557-563

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ReadmeModal.tsx` around lines 158 - 206, The TOC,
headingIdMap, and intersection observer are still based on readmeContent so
translations break TOC/active-section syncing; update the useEffect and any
logic that currently reads tocItems/headingIdMap from readmeContent to derive
them from the rendered DOM/displayContent instead: in the useEffect that
references contentRef, tocItems, readmeContent and the IntersectionObserver (the
effect using setActiveHeadingId), recompute tocItems/headingIdMap by querying
contentRef.current (e.g., headings and their ids/text) or use the
MarkdownRenderer's displayContent output as the dependency, then observe those
computed elements and update the effect dependency array to include
displayContent (or the derived TOC) so observer lifecycle and TOC clicks reflect
translated headings (apply same change for the other similar blocks mentioned).
🧹 Nitpick comments (1)
package.json (1)

25-29: ⚡ Quick win

Remove @types/query-string from runtime deps.

query-string already ships its own TypeScript declarations, and @types/query-string is a deprecated stub that explicitly says you do not need it installed. Keeping both here can pin stale v6 typings against the v9 runtime package. (npmjs.com)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 25 - 29, The package.json currently includes the
deprecated runtime dependency "@types/query-string" alongside "query-string";
remove the "@types/query-string" entry from package.json (the dependency named
"@types/query-string") so the project relies on the built-in TypeScript
declarations shipped with "query-string", and then run your install (or update
lockfile) to ensure the lockfile no longer pins the obsolete v6 typings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/services/translateService.ts`:
- Around line 122-126: The current parameter building in translateText and
translateBatchInternal always includes from via from || '', which prevents the
Microsoft Translator API from auto-detecting the source language; change both
functions (translateText and translateBatchInternal) to only include the from
key when a source language is explicitly provided (i.e., conditionally add from
to the params object or remove it when falsy) before calling
queryString.stringify so the from parameter is omitted entirely for
auto-detection.
- Around line 69-89: apiMsAuth currently ignores an AbortSignal and callers pass
signals expecting cancellation behavior; update apiMsAuth to accept an optional
signal parameter and pass that signal into the fetch call and any internal async
flows (including the shared tokenPromise) so token requests can be aborted, and
ensure tokenPromise is cleared on abort/failure; update the three callers
(translateText, translateBatch, translateBatchInternal) to forward their signal
into apiMsAuth; finally, change translateBatch to throw an actual AbortError
(e.g., new DOMException('Aborted', 'AbortError') or other AbortError class used
in the codebase) instead of Error('Aborted') so useMarkdownTranslation's
AbortError handling will work.

In `@src/utils/markdownSplitter.ts`:
- Line 17: The splitter's combinedRegex currently only protects triple-backtick
fences so fenced blocks using tilde fences (~~~) get passed through translation;
update the combinedRegex in markdownSplitter.ts (the variable combinedRegex) to
also match tilde-fenced blocks (e.g., include a branch like
(```[\s\S]*?```|~~~[\s\S]*?~~~)) so both fenced code blocks are treated as
non-translatable, keeping inline code (`...`), images/links patterns unchanged;
this mirrors extractToc()'s handling of both fence styles.
- Line 17: The combinedRegex currently treats entire markdown links/images as
non-translatable; update the regex (combinedRegex) to capture link label/alt
text and URL/src separately (e.g. use patterns like \!\[([^\]]*)\]\(([^)]+)\)
and \[([^\]]*)\]\(([^)]+)\)) so the label/alt text is a distinct capture group
from the URL, then change the tokenization logic that uses combinedRegex (and
the related handling in the block around lines 53-64) so it only freezes the
URL/src group while allowing the label/alt text group to be emitted as
translatable text. Ensure downstream code that maps capture groups to token
types is updated to treat group 1 as translatable and group 2 as a preserved
non-translatable token.

---

Outside diff comments:
In `@src/components/ReadmeModal.tsx`:
- Around line 158-206: The TOC, headingIdMap, and intersection observer are
still based on readmeContent so translations break TOC/active-section syncing;
update the useEffect and any logic that currently reads tocItems/headingIdMap
from readmeContent to derive them from the rendered DOM/displayContent instead:
in the useEffect that references contentRef, tocItems, readmeContent and the
IntersectionObserver (the effect using setActiveHeadingId), recompute
tocItems/headingIdMap by querying contentRef.current (e.g., headings and their
ids/text) or use the MarkdownRenderer's displayContent output as the dependency,
then observe those computed elements and update the effect dependency array to
include displayContent (or the derived TOC) so observer lifecycle and TOC clicks
reflect translated headings (apply same change for the other similar blocks
mentioned).

---

Nitpick comments:
In `@package.json`:
- Around line 25-29: The package.json currently includes the deprecated runtime
dependency "@types/query-string" alongside "query-string"; remove the
"@types/query-string" entry from package.json (the dependency named
"@types/query-string") so the project relies on the built-in TypeScript
declarations shipped with "query-string", and then run your install (or update
lockfile) to ensure the lockfile no longer pins the obsolete v6 typings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e3ef5d00-d630-4171-94e9-3f7980d1fc2f

📥 Commits

Reviewing files that changed from the base of the PR and between e383998 and b3b0a09.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • package.json
  • src/components/ReadmeModal.tsx
  • src/hooks/useMarkdownTranslation.ts
  • src/services/translateService.ts
  • src/utils/markdownSplitter.ts

Comment thread src/services/translateService.ts Outdated
Comment thread src/services/translateService.ts Outdated
Comment thread src/utils/markdownSplitter.ts Outdated
将TOC提取逻辑从README内容加载中分离,改为在翻译内容或原始内容更新时触发
优化翻译服务,支持请求中止并改进参数处理
增强markdown分割器,支持更多代码块格式并改进链接和图片处理
@SummerRay160 SummerRay160 mentioned this pull request Apr 30, 2026
@AmintaCCCP
Copy link
Copy Markdown
Owner

AmintaCCCP commented Apr 30, 2026

我也是纯 ai 改,最近被弱智模型坑的不行。我找找公益站先,之前手头的公益站都死的差不多了。
我发现上下文只要超模型支持的上下文 50% 幻觉就会大幅增加,压缩上下文也会丢信息。尽量拆小点做,一个 pr 少改点会好些。

@SummerRay160
Copy link
Copy Markdown
Contributor Author

现在主要是翻译后的MD排版总是在某些情况出问题,AI检查了很多问题自己也改了一些
总之希望可以充分测试再上线
另外我个人认为字节出的IDE 还是不错的,免费而且模型算是能用,当然也是看时段排队。

@AmintaCCCP
Copy link
Copy Markdown
Owner

现在主要是翻译后的MD排版总是在某些情况出问题,AI检查了很多问题自己也改了一些

总之希望可以充分测试再上线

另外我个人认为字节出的IDE 还是不错的,免费而且模型算是能用,当然也是看时段排队。

那个 trae 么,还有个 trae solo。之前刚出的时候用过一阵,后来就是排队不用了,亚马逊那个 kiro 也用过一段。现在主要就是 Claude code 和 opencode,不在电脑前就用🦞。 那个翻译是全文翻还是弄成沉浸式翻译那种?

@AmintaCCCP
Copy link
Copy Markdown
Owner

看了一下,主要是翻译以后有一些markdown语法不渲染,还有一些html标签

重构 markdown 解析器,新增对 HTML 标签和带链接图片的支持
优化语言检测和翻译方向判断逻辑
调整 tailwind 配置中的颜色结构
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/utils/markdownSplitter.ts`:
- Line 22: The current combinedRegex in markdownSplitter.ts only matches a
narrow set of opening/self-closing HTML tags and misses paired tags and explicit
closing tags, so update the regex (used as combinedRegex in this file) to also
match full paired tag blocks and their closing tags for the supported set
(video, audio, iframe, object, embed, svg, canvas, picture) by using patterns
like <tag\b[^>]*>[\s\S]*?<\/tag> and explicit <\/tag> alternatives, and ensure
source/img remain matched as self-closing; apply the same updated pattern where
combinedRegex is redefined (also around lines 65-82) so paired tags and closing
tags are shielded from translation.
- Around line 248-291: extractTranslatableChunks currently concatenates multiple
translatable segments into larger chunks which loses original boundaries and
makes mergeTranslatedChunks/splitTranslationToParts unrecoverable; change
extractTranslatableChunks to preserve 1:1 segment boundaries by emitting a chunk
per translatable MarkdownSegment (or include an unambiguous boundary token and
original segment index/length metadata on each chunk) so reconstructed text can
be mapped exactly back to segmentIds, and then update mergeTranslatedChunks and
splitTranslationToParts to rely on the preserved per-segment mapping (segmentIds
or explicit boundary metadata) instead of guessing paragraph counts.
- Around line 143-153: The current code pushes the markdown delimiters into
segments marked 'translatable' (see segments.push calls that add content '!['
and `${altText}]` using segmentIdCounter), which lets MT change syntax; instead
split these into separate segments so only the label text is translatable: push
a non-translatable/delimiter segment for '![' (type e.g. 'delimiter' or
'token'), then push a translatable segment containing only altText (no
surrounding brackets), then push another non-translatable segment for ']' —
apply the same change to the other occurrence noted (lines ~189-193) so all
bracket/marker content is excluded from translatable segments.
- Around line 343-345: The loop in markdownSplitter.ts currently uses "const
translation = translations[translationIndex] ?? ''", which silently replaces
missing translations and can erase the original chunk; replace this with an
explicit undefined check on translations[translationIndex] and either (a) throw
a descriptive Error (including translationIndex and chunk metadata) to fail
fast, or (b) fall back to the original chunk content by setting translation =
chunk.text (or chunk.content) when undefined; update the code paths around
translationIndex, chunks, and translations to use this check so missing items
are handled explicitly and add/update any tests that expect the new behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 32dc5a39-076c-44c4-b436-c9858ac21445

📥 Commits

Reviewing files that changed from the base of the PR and between be9f3c1 and 3b7eb05.

📒 Files selected for processing (2)
  • src/utils/markdownSplitter.ts
  • tailwind.config.js

Comment thread src/utils/markdownSplitter.ts Outdated
const segments: MarkdownSegment[] = [];

// 组合正则表达式:匹配各类 Markdown 元素
const combinedRegex = /(<picture[\s\S]*?<\/picture>)|(<(?:source|img|video|audio|iframe|object|embed|svg|canvas)[^>]*\/?>)|(```[\s\S]*?```)|(~~~[\s\S]*?~~~)|(`[^`\n]+`)|(\[!\[[^\]]*\]\([^)]*\)\]\([^)]+\))|(!\[([^\]]*)\]\([^)]+\))|(\[([^\]]*)\]\([^)]+\))/g;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

HTML tag shielding is incomplete for paired tags and closing tags.

At Line 22, the regex mainly captures opening/self-closing tags from a narrow whitelist. Closing tags and many paired HTML blocks can still flow into translatable text and get corrupted.

Suggested fix (at least preserve paired + closing tags for supported set)
-  const combinedRegex = /(<picture[\s\S]*?<\/picture>)|(<(?:source|img|video|audio|iframe|object|embed|svg|canvas)[^>]*\/?>)|(```[\s\S]*?```)|(~~~[\s\S]*?~~~)|(`[^`\n]+`)|(\[!\[[^\]]*\]\([^)]*\)\]\([^)]+\))|(!\[([^\]]*)\]\([^)]+\))|(\[([^\]]*)\]\([^)]+\))/g;
+  const combinedRegex =
+    /(<picture[\s\S]*?<\/picture>)|(<(?:video|audio|iframe|object|embed|svg|canvas)\b[^>]*>[\s\S]*?<\/(?:video|audio|iframe|object|embed|svg|canvas)>|<(?:source|img)\b[^>]*\/?>|<\/(?:video|audio|iframe|object|embed|svg|canvas)>)|(```[\s\S]*?```)|(~~~[\s\S]*?~~~)|(`[^`\n]+`)|(\[!\[[^\]]*\]\([^)]*\)\]\([^)]+\))|(!\[([^\]]*)\]\([^)]+\))|(\[([^\]]*)\]\([^)]+\))/g;

Also applies to: 65-82

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/markdownSplitter.ts` at line 22, The current combinedRegex in
markdownSplitter.ts only matches a narrow set of opening/self-closing HTML tags
and misses paired tags and explicit closing tags, so update the regex (used as
combinedRegex in this file) to also match full paired tag blocks and their
closing tags for the supported set (video, audio, iframe, object, embed, svg,
canvas, picture) by using patterns like <tag\b[^>]*>[\s\S]*?<\/tag> and explicit
<\/tag> alternatives, and ensure source/img remain matched as self-closing;
apply the same updated pattern where combinedRegex is redefined (also around
lines 65-82) so paired tags and closing tags are shielded from translation.

Comment thread src/utils/markdownSplitter.ts Outdated
Comment thread src/utils/markdownSplitter.ts Outdated
Comment thread src/utils/markdownSplitter.ts Outdated
@AmintaCCCP
Copy link
Copy Markdown
Owner

劳动节好好休息放松放松啊,别太卷了

重构翻译服务,添加FALLBACK_TOKEN_TTL_MS常量用于令牌过期处理
新增BilingualMarkdownRenderer组件实现双语对照显示
重构useMarkdownTranslation钩子和markdownSplitter工具函数
优化翻译分段处理逻辑和占位符替换机制
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/utils/markdownSplitter.ts (1)

30-32: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

~~~ fenced code blocks are not extracted — their content is sent to the translator.

Only triple-backtick fences are replaced with placeholders. Any README using ~~~ fencing (which GitHub Markdown fully supports) will have the raw code content forwarded to the translation API and potentially corrupted.

🐛 Proposed fix
-  processed = processed.replace(/```[\s\S]*?```/g, (match) => {
+  processed = processed.replace(/(```[\s\S]*?```|~~~[\s\S]*?~~~)/g, (match) => {
     return createPlaceholder('CODE', match);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/markdownSplitter.ts` around lines 30 - 32, The regex that replaces
fenced code blocks only matches triple backticks so tilde fences (~~~...~~~) are
left intact; update the replacement in the processed.replace call that invokes
createPlaceholder('CODE', match) to also match tilde-fenced blocks (e.g., adjust
the pattern used by processed.replace to include both /```[\s\S]*?```/ and
/~~~[\s\S]*?~~~/ or a single alternation like
/(```[\s\S]*?```|~~~[\s\S]*?~~~)/g) so all fenced code blocks are extracted
before translation.
🧹 Nitpick comments (2)
src/services/translateService.ts (1)

101-105: 💤 Low value

Redundant conditional in tokenPromise catch block.

Both branches of the if are identical — they both unconditionally throw err. The check adds no value.

🛠️ Proposed cleanup
-    } catch (err) {
-      if (err instanceof Error && err.name === 'AbortError') {
-        throw err;
-      }
-      throw err;
+    } catch (err) {
+      throw err;
     } finally {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/translateService.ts` around lines 101 - 105, The catch block
inside the tokenPromise logic in translateService.ts redundantly checks for
AbortError but both branches just rethrow err; simplify by removing the if and
directly rethrow the caught error (i.e., replace the conditional in the
tokenPromise catch with a single "throw err;") so the behavior remains unchanged
and the code is cleaner.
src/hooks/useMarkdownTranslation.ts (1)

93-102: 💤 Low value

hasCodeBlock branch is redundant — both branches push the same values.

The if (segment.hasCodeBlock) and else if (segment.originalContent.trim()) branches are identical. A segment with hasCodeBlock = true always has non-empty originalContent (it contains a placeholder like __CODE_0__), so the else if already covers it.

♻️ Proposed simplification
-          if (segment.hasCodeBlock) {
-            batchTexts.push(segment.originalContent);
-            batchIndices.push(j);
-          } else if (segment.originalContent.trim()) {
+          if (segment.originalContent.trim()) {
             batchTexts.push(segment.originalContent);
             batchIndices.push(j);
           }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useMarkdownTranslation.ts` around lines 93 - 102, The two branches
inside the batching loop are redundant because segment.hasCodeBlock segments
also have non-empty originalContent; update the loop in useMarkdownTranslation
(the for-loop that builds batchTexts and batchIndices over newSegments) to
remove the hasCodeBlock branch and simply check segment.originalContent.trim()
(or truthiness) before pushing to batchTexts and batchIndices, preserving the
same push behavior for applicable segments.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/BilingualMarkdownRenderer.tsx`:
- Around line 23-47: The per-segment "translating" spinner is unreachable
because segments never get status 'translating' and BilingualMarkdownRenderer is
only mounted once translateStatus === 'translated'; remove the dead UI by
deleting the isTranslating const and the entire spinner JSX block (the
conditional that renders Loader2 and the "Translating..." text) from
BilingualMarkdownRenderer, and also remove any references to 'translating' in
that component; if you instead want live per-segment progress, update
useMarkdownTranslation to set segment.status = 'translating' before dispatching
each batch and change ReadmeModal to mount BilingualMarkdownRenderer when
translateStatus === 'translating' so the spinner can appear.

In `@src/hooks/useMarkdownTranslation.ts`:
- Around line 140-144: The catch in useMarkdownTranslation.ts currently checks
"err instanceof Error && err.name === 'AbortError'", which misses browser
DOMException aborts; change the abort handling in the catch inside the
useMarkdownTranslation hook to check err?.name === 'AbortError' (or typeof (err
as any)?.name === 'string' && err.name === 'AbortError') instead of instanceof
Error so aborted fetches hit the abort branch, call setStatus('idle') and return
false; update any references to the catch block around the
translateService/translate call so aborts are treated silently rather than
setting setError('Translation failed') and setStatus('error').

In `@src/utils/markdownSplitter.ts`:
- Around line 117-126: getTranslateDirection currently returns from: 'auto' when
detected is 'unknown', which then causes translateBatchInternal to send an
invalid from='auto' to Azure; change getTranslateDirection (and its return type)
so that when detected === 'unknown' it returns from as undefined (i.e., omit the
from property) instead of the string 'auto', and ensure translateBatchInternal's
spread still only adds from when it's defined (e.g., ...(from != null && { from
})), so the Azure Translator auto-detection is triggered by omitting the from
parameter.

---

Duplicate comments:
In `@src/utils/markdownSplitter.ts`:
- Around line 30-32: The regex that replaces fenced code blocks only matches
triple backticks so tilde fences (~~~...~~~) are left intact; update the
replacement in the processed.replace call that invokes createPlaceholder('CODE',
match) to also match tilde-fenced blocks (e.g., adjust the pattern used by
processed.replace to include both /```[\s\S]*?```/ and /~~~[\s\S]*?~~~/ or a
single alternation like /(```[\s\S]*?```|~~~[\s\S]*?~~~)/g) so all fenced code
blocks are extracted before translation.

---

Nitpick comments:
In `@src/hooks/useMarkdownTranslation.ts`:
- Around line 93-102: The two branches inside the batching loop are redundant
because segment.hasCodeBlock segments also have non-empty originalContent;
update the loop in useMarkdownTranslation (the for-loop that builds batchTexts
and batchIndices over newSegments) to remove the hasCodeBlock branch and simply
check segment.originalContent.trim() (or truthiness) before pushing to
batchTexts and batchIndices, preserving the same push behavior for applicable
segments.

In `@src/services/translateService.ts`:
- Around line 101-105: The catch block inside the tokenPromise logic in
translateService.ts redundantly checks for AbortError but both branches just
rethrow err; simplify by removing the if and directly rethrow the caught error
(i.e., replace the conditional in the tokenPromise catch with a single "throw
err;") so the behavior remains unchanged and the code is cleaner.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2dfe893c-dff0-4916-baa3-0b51fac617bf

📥 Commits

Reviewing files that changed from the base of the PR and between 3b7eb05 and 3f64033.

📒 Files selected for processing (5)
  • src/components/BilingualMarkdownRenderer.tsx
  • src/components/ReadmeModal.tsx
  • src/hooks/useMarkdownTranslation.ts
  • src/services/translateService.ts
  • src/utils/markdownSplitter.ts

Comment thread src/components/BilingualMarkdownRenderer.tsx Outdated
Comment thread src/hooks/useMarkdownTranslation.ts Outdated
Comment thread src/utils/markdownSplitter.ts
修复令牌验证时未检查缓存令牌是否存在的问题
优化markdown分割器中的代码块正则匹配,支持~~~语法
移除不必要的AbortError类型检查
简化双语渲染组件中的翻译状态显示逻辑
优化 translateService 中的 isTokenValid 类型守卫,使其更准确地判断 token 有效性
移除 BilingualMarkdownRenderer 中未使用的 language 属性及相关依赖
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/utils/markdownSplitter.ts`:
- Around line 46-49: The current markdown splitting flow is lossy: using
processed.split(/\n\n+/), filter(p => p.trim()), .trim() and join('\n\n') in
markdownSplitter.ts destroys intentional blank-line layout. Update the logic in
the functions handling "processed", "paragraphs" and the TranslationSegment
creation to preserve original inter-paragraph whitespace: avoid collapsing
multiple newlines with /\n\n+/, do not filter out empty-but-meaningful
paragraphs via filter(p => p.trim()), and stop trimming content before
rejoining; instead, record and carry the exact separator between paragraphs
(e.g., capture the matched separator when splitting) and use that original
separator when restoring segments so join uses the preserved separators rather
than a fixed '\n\n'. Ensure functions/methods that reference processed,
paragraphs, TranslationSegment construction, the trim() usage at the earlier
mentioned call site, and the final join call are updated to pass through or
store exact whitespace separators.
- Around line 30-44: Add protection for single-backtick inline code spans by
adding a replacement on the processed string that uses
createPlaceholder('INLINE_CODE', match) for matches of inline code (e.g.,
/`[^`\n]+`/g); place this replacement in markdownSplitter.ts near the other
processed.replace calls (after shielding fenced blocks with CODE and before
other content transformations) so inline code is replaced before sending text to
MT and won't be translated or altered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fe50c926-6735-41e6-9026-8cf1272919e5

📥 Commits

Reviewing files that changed from the base of the PR and between 3f64033 and fac94e3.

📒 Files selected for processing (4)
  • src/components/BilingualMarkdownRenderer.tsx
  • src/hooks/useMarkdownTranslation.ts
  • src/services/translateService.ts
  • src/utils/markdownSplitter.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/hooks/useMarkdownTranslation.ts
  • src/services/translateService.ts

Comment thread src/utils/markdownSplitter.ts Outdated
Comment thread src/utils/markdownSplitter.ts Outdated
支持内联代码和details标签的占位处理,保留原始分隔符
添加原文/译文/双语三种显示模式切换功能
优化翻译按钮样式和占位符恢复逻辑
@SummerRay160
Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

✅ Actions performed

Reviews resumed.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/components/BilingualMarkdownRenderer.tsx (2)

4-4: 💤 Low value

Remove unused EyeOff import.

EyeOff is imported but never used in the component.

Proposed fix
-import { FileText, Languages, Eye, EyeOff } from 'lucide-react';
+import { FileText, Languages, Eye } from 'lucide-react';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/BilingualMarkdownRenderer.tsx` at line 4, The import list in
BilingualMarkdownRenderer.tsx includes an unused symbol EyeOff which should be
removed; update the import statement that currently imports { FileText,
Languages, Eye, EyeOff } from 'lucide-react' to drop EyeOff so only used icons
(FileText, Languages, Eye) are imported, and run a quick lint/type check to
ensure no other references to EyeOff remain.

29-61: ⚡ Quick win

getDisplayContent duplicates restorePlaceholders from markdownSplitter.ts.

This function is nearly identical to restorePlaceholders exported from ../utils/markdownSplitter. Consider importing and reusing that function, then adding the extra leftover-placeholder pass if needed.

Proposed refactor
+import { restorePlaceholders } from '../utils/markdownSplitter';

 const SegmentBlock: React.FC<{...}> = memo(({ segment, placeholderMap, ... }) => {
   const hasTranslation = segment.translatedContent !== null && segment.status === 'done';

   const getDisplayContent = (content: string): string => {
     if (!content || !placeholderMap.size) return content;

-    let result = content;
-    
-    const entries = Array.from(placeholderMap.entries());
-    
-    for (const [key, value] of entries) {
-      if (result.includes(key)) {
-        result = result.split(key).join(value);
-        continue;
-      }
-      
-      const coreId = key.replace(/^_+|_+$/g, '');
-      const pattern = new RegExp(`_+${coreId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}_+`, 'gi');
-      
-      if (pattern.test(result)) {
-        result = result.replace(pattern, value);
-      }
-    }
+    let result = restorePlaceholders(content, placeholderMap);
     
+    // Extra pass for any remaining placeholders with modified format
     const placeholderPattern = /__[A-Z]+_\d+__/g;
     result = result.replace(placeholderPattern, (match) => {
       const coreId = match.replace(/^_+|_+$/g, '');
       const found = Array.from(placeholderMap.entries()).find(([k]) => k.replace(/^_+|_+$/g, '') === coreId);
       if (found) {
         return found[1];
       }
       return match;
     });
     
     return result;
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/BilingualMarkdownRenderer.tsx` around lines 29 - 61,
getDisplayContent duplicates restorePlaceholders from ../utils/markdownSplitter;
replace the local implementation by importing restorePlaceholders and calling it
(passing the same placeholderMap and content) inside BilingualMarkdownRenderer,
and then perform only the additional leftover-placeholder pass (the
/__[A-Z]+_\d+__/ replacement) if restorePlaceholders doesn’t already handle it.
Update references to placeholderMap and ensure getDisplayContent delegates to
restorePlaceholders (keeping the extra regex fallback) to avoid duplicated
logic.
src/components/ReadmeModal.tsx (1)

67-78: ⚡ Quick win

getDisplayContent duplicates segmentsToMarkdown logic.

This function replicates the segment reconstruction logic from segmentsToMarkdown in markdownSplitter.ts. Consider importing and reusing that utility.

Proposed refactor
+import { segmentsToMarkdown } from '../utils/markdownSplitter';

 const getDisplayContent = useCallback((): string => {
   if (translateStatus === 'translated' && segments.length > 0) {
-    return segments.reduce((acc, segment, index) => {
-      const content = segment.translatedContent || segment.originalContent;
-      if (index === 0) {
-        return content;
-      }
-      return acc + segment.separator + content;
-    }, '');
+    return segmentsToMarkdown(segments);
   }
   return readmeContent;
 }, [translateStatus, segments, readmeContent]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ReadmeModal.tsx` around lines 67 - 78, getDisplayContent
currently duplicates the segment-joining logic; replace the manual reduce with a
call to the existing utility segmentsToMarkdown from markdownSplitter.ts: import
segmentsToMarkdown and, when translateStatus === 'translated' and
segments.length > 0, return segmentsToMarkdown(segments) (or
segmentsToMarkdown(segments, useTranslated=true) if the utility accepts a flag)
instead of reconstructing content manually, otherwise return readmeContent;
update the dependency array to still include translateStatus, segments, and
readmeContent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/utils/markdownSplitter.ts`:
- Around line 55-81: The separator is being recorded off-by-one: the loop in
markdownSplitter (using separatorPattern, parts, segments, currentSeparator)
assigns currentSeparator to the segment but then updates currentSeparator =
separator, so segment.separator actually represents the separator after the
segment; fix by storing the separator that actually follows the content (i.e.,
set segment.separator = separator || '\n\n' instead of currentSeparator) or
alternatively change segmentsToMarkdown to concatenate content +
segment.separator (instead of segment.separator + content); update either the
loop (assign separator to the created TranslationSegment) or the
segmentsToMarkdown logic so the stored separator and reconstruction order match.

---

Nitpick comments:
In `@src/components/BilingualMarkdownRenderer.tsx`:
- Line 4: The import list in BilingualMarkdownRenderer.tsx includes an unused
symbol EyeOff which should be removed; update the import statement that
currently imports { FileText, Languages, Eye, EyeOff } from 'lucide-react' to
drop EyeOff so only used icons (FileText, Languages, Eye) are imported, and run
a quick lint/type check to ensure no other references to EyeOff remain.
- Around line 29-61: getDisplayContent duplicates restorePlaceholders from
../utils/markdownSplitter; replace the local implementation by importing
restorePlaceholders and calling it (passing the same placeholderMap and content)
inside BilingualMarkdownRenderer, and then perform only the additional
leftover-placeholder pass (the /__[A-Z]+_\d+__/ replacement) if
restorePlaceholders doesn’t already handle it. Update references to
placeholderMap and ensure getDisplayContent delegates to restorePlaceholders
(keeping the extra regex fallback) to avoid duplicated logic.

In `@src/components/ReadmeModal.tsx`:
- Around line 67-78: getDisplayContent currently duplicates the segment-joining
logic; replace the manual reduce with a call to the existing utility
segmentsToMarkdown from markdownSplitter.ts: import segmentsToMarkdown and, when
translateStatus === 'translated' and segments.length > 0, return
segmentsToMarkdown(segments) (or segmentsToMarkdown(segments,
useTranslated=true) if the utility accepts a flag) instead of reconstructing
content manually, otherwise return readmeContent; update the dependency array to
still include translateStatus, segments, and readmeContent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: df7ca08c-e117-4107-930b-4811c38306b9

📥 Commits

Reviewing files that changed from the base of the PR and between 3f64033 and f722aab.

📒 Files selected for processing (5)
  • src/components/BilingualMarkdownRenderer.tsx
  • src/components/ReadmeModal.tsx
  • src/hooks/useMarkdownTranslation.ts
  • src/services/translateService.ts
  • src/utils/markdownSplitter.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/services/translateService.ts
  • src/hooks/useMarkdownTranslation.ts

Comment thread src/utils/markdownSplitter.ts Outdated
@SummerRay160
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@SummerRay160
Copy link
Copy Markdown
Contributor Author

@coderabbitai Complete Review PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

Tip

For best results, initiate chat on the files or code changes.

@SummerRay160 Sure! I'll perform a complete review of PR #130, covering all the changes including the translation service, the useMarkdownTranslation hook, BilingualMarkdownRenderer, markdownSplitter, and the other modified files. The review will focus on the core issues mentioned — Markdown rendering after translation and HTML tag handling — as well as general code quality and correctness.

[full_review]
Please perform a thorough review of all changes in this PR, focusing on: (1) Markdown rendering correctness after translation, particularly around placeholder restoration and separator handling; (2) HTML tag support and rendering compatibility in the bilingual renderer; (3) translation token caching and auth logic in translateService; (4) correctness and edge cases in the markdownSplitter utility; (5) state management and cleanup in the useMarkdownTranslation hook.

重构翻译功能,使用DOM文本扫描器替代Markdown分割器,实现更精准的文本块识别和翻译
新增BilingualMarkdownRenderer组件,支持原文、译文和双语三种显示模式
优化翻译服务,添加重试机制和HTML内容类型支持
改进目录功能,支持翻译后的标题同步显示
添加appendSafeHTMLNodes函数来安全处理翻译文本中的内联代码节点,防止潜在的HTML注入风险。当文本包含内联代码时,不再直接设置innerHTML,而是递归复制安全节点。
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/BilingualMarkdownRenderer.tsx`:
- Around line 131-140: The language detection is being run on the raw Markdown
variable `markdown`, which biases results from code/URLs; update the flow to run
`detectLanguage` on the extracted human-readable text segment instead (the text
produced earlier when rendering/extracting the prose) rather than `markdown`,
then use that detected value for `getTranslateDirection(detected, targetLang)`
and the existing `setError`/`updateStatus` logic; locate the call site where
`detectLanguage(markdown)` is invoked and replace the argument with the
extracted segment text variable (e.g., `extractedText`/`segmentText`) so
detection reflects readable prose not code fences.
- Around line 196-212: The code currently uses
segments[i].element.after(wrapper) which creates invalid DOM when the source
element is a list item or table cell; change the logic in
BilingualMarkdownRenderer so that before calling element.after you check the tag
of segments[i].element (e.g., element.tagName or a helper isInlineContainer) and
if it is one of LI, TD, TH, DT, or DD you should append the translation wrapper
into the element (element.appendChild or element.append) instead of inserting it
as a sibling; keep the existing setting of ATTR_TRANSLATION, className,
textContent/innerHTML handling and still call
translationElementsRef.current.set(segments[i].id, wrapper) after inserting the
wrapper.
- Around line 201-203: The code assigns translator output to wrapper.innerHTML
when segments[i].hasInlineCode (see BilingualMarkdownRenderer.tsx,
wrapper.innerHTML = translatedTexts[i]), which risks XSS because
cleanTranslatedText() doesn't escape HTML; change this to set
wrapper.textContent = translatedTexts[i] to treat translatedTexts[i] as plain
text, or if you must preserve inline-code formatting, escape HTML before
assigning or construct DOM nodes (create text nodes and code elements) from the
escaped text instead of using innerHTML; update the logic around
segments[i].hasInlineCode and any callers that expect HTML accordingly.

In `@src/components/ReadmeModal.tsx`:
- Around line 334-340: The translatedHeadingMap is not cleared when
displayContent changes, causing stale translated labels to persist across
documents; inside the useEffect that runs on displayContent (the one that calls
extractToc and calls setTocItems/setHeadingIdMap), reset the
translatedHeadingMap to an empty map (or call the state setter that owns it)
before or immediately after calling extractToc so IDs like "heading-0" don't
reuse previous translations; locate the translatedHeadingMap state/variable (and
its setter) in this component and clear it in that effect (alternatively clear
it before fetchReadme() if that better fits the flow).

In `@src/services/translateService.ts`:
- Around line 74-100: withTranslateRetry currently retries every non-abort
error; change it to only retry transient failures by inspecting the thrown error
before backing off: preserve AbortError/CanceledError behavior, and for other
errors check if the error has an HTTP status (e.g., (err as
any).response?.status or (err as any).status) — if there is no status
(network/timeout) OR status === 429 OR status is 5xx then perform the
exponential backoff retry logic (sleep and continue), otherwise immediately
rethrow the error so deterministic 4xx client errors are not retried; keep the
existing apiMsAuth/token acquisition, maxRetries and backoff behavior and still
surface lastError after exhausting retries.

In `@src/utils/domTextScanner.ts`:
- Line 11: scanDomForTranslation is skipping <summary> elements because
BLOCK_SELECTOR (const BLOCK_SELECTOR) doesn't include 'summary'; update the
selector string inside src/utils/domTextScanner.ts to add "summary" (e.g.,
include ', summary' in the comma-separated list) so summary elements are treated
as block nodes and will be turned into translation segments by
scanDomForTranslation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c56d1ca3-6d1e-4233-bd97-5c62172bcd6b

📥 Commits

Reviewing files that changed from the base of the PR and between f722aab and fc2b2de.

📒 Files selected for processing (5)
  • src/components/BilingualMarkdownRenderer.tsx
  • src/components/ReadmeModal.tsx
  • src/services/translateService.ts
  • src/utils/domTextScanner.ts
  • src/utils/markdownSplitter.ts

Comment thread src/components/BilingualMarkdownRenderer.tsx Outdated
Comment thread src/components/BilingualMarkdownRenderer.tsx
Comment thread src/components/BilingualMarkdownRenderer.tsx
Comment thread src/components/ReadmeModal.tsx
Comment thread src/services/translateService.ts
Comment thread src/utils/domTextScanner.ts Outdated
修复翻译服务中的重试逻辑,仅对瞬态错误进行重试
优化双语渲染器中的代码块处理逻辑
更新DOM文本扫描器以包含summary标签
在ReadmeModal中重置翻译标题映射
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/ReadmeModal.tsx (1)

348-369: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reset resize side effects when the modal closes.

If the modal is closed while the TOC splitter is being dragged, the mouseup handler never runs and document.body.style.cursor / userSelect stay overridden. Clear isResizingRef.current and restore both body styles in this !isOpen branch too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ReadmeModal.tsx` around lines 348 - 369, When closing the
modal in the useEffect !isOpen branch, clear any ongoing resize state and
restore global body styles: set isResizingRef.current = false and reset
document.body.style.cursor and document.body.style.userSelect to their defaults
so a mid-drag TOC splitter doesn’t leave the page in a drag state; locate the
cleanup within the existing useEffect handling isOpen (around the code that
aborts abortControllerRef and resets state) and add these restores there
referencing isResizingRef, document.body.style.cursor and
document.body.style.userSelect.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/ReadmeModal.tsx`:
- Around line 110-121: The TOC active-state tracking must resolve bilingual
headings the same way as the click/scroll path: when observing nodes (the
IntersectionObserver callback that updates activeHeadingId) resolve the DOM node
using the bilingual selector pattern used in translationWrapper and compute the
id to store as target.dataset.biHeadingId ?? target.id; update any code that
currently uses target.id directly to use dataset.biHeadingId ?? id so the TOC
highlight stays in sync with clicks that scroll via data-bi-heading-id (refer to
translationWrapper, container.scrollTo, activeHeadingId, and the observer
callback).

In `@src/services/translateService.ts`:
- Around line 202-207: The 401 handling in the translate request clears
cachedToken and localStorage but throws a generic Error so withTranslateRetry
treats it as non-transient; change the code in the response.ok 401 branch (and
the identical block around the other occurrence referenced) to throw a specific
transient auth-expired error (e.g., throw new Error('AuthExpired') with a unique
property like error.isAuthExpired = true or use a dedicated AuthExpiredError
class) so withTranslateRetry can detect the auth-expired case, re-run the auth
flow to fetch a fresh token, and retry the request once; ensure only the 401
branch uses this special error while other non-OK statuses continue throwing
their original non-transient errors.
- Around line 140-164: The global tokenPromise is being created with a
caller-specific signal which lets one caller's abort cancel the shared fetch;
instead create tokenPromise without any AbortSignal (call fetch(AUTH_URL, {
method: 'GET', credentials: 'omit' }) inside the async IIFE so the shared fetch
is signal-agnostic and still stores token via storeToken and resets tokenPromise
in its finally block), and in the caller use a local race between tokenPromise
and the caller's own abort signal (e.g., Promise.race between tokenPromise and a
promise that rejects on signal.abort) so each caller can cancel awaiting the
token without canceling the underlying shared fetch; reference identifiers:
tokenPromise, AUTH_URL, storeToken, signal.

In `@src/utils/domTextScanner.ts`:
- Around line 75-98: filterOutermostBlocks currently removes selectable child
block elements which causes descendant blocks to be concatenated (e.g.,
"IntroDetails"); change the scanning to preserve nested block structure by
either (A) stop filtering out inner blocks in filterOutermostBlocks so
scanDomForTranslation iterates allElements (from BLOCK_SELECTOR) and emits a
DomBlockSegment per matched element, or (B) if keeping outer-only selection,
update extractTextPreservingInlineCode (used in scanDomForTranslation) to detect
descendant block-level elements and insert explicit separators (e.g., "\n\n" or
a paragraph token) between them when building the segment text so paragraph
boundaries are preserved; adjust id assignment (idCounter) to handle additional
segments accordingly.
- Around line 50-52: The inline code branch currently injects el.textContent
verbatim into result (when tag === 'code' && !el.closest('pre')), which can
break the placeholder HTML for snippets containing <, >, or &; update that
branch to HTML-escape the inline code text (convert & to &amp;, < to &lt;, > to
&gt;, and optionally " and ') before concatenating into result so the `<code>`
placeholder preserves the original literal characters; locate the inline branch
using the tag === 'code' check and the variables el.textContent, hasInlineCode,
and result to implement the escaping helper and use it when building the
`<code>...</code>` string.

---

Outside diff comments:
In `@src/components/ReadmeModal.tsx`:
- Around line 348-369: When closing the modal in the useEffect !isOpen branch,
clear any ongoing resize state and restore global body styles: set
isResizingRef.current = false and reset document.body.style.cursor and
document.body.style.userSelect to their defaults so a mid-drag TOC splitter
doesn’t leave the page in a drag state; locate the cleanup within the existing
useEffect handling isOpen (around the code that aborts abortControllerRef and
resets state) and add these restores there referencing isResizingRef,
document.body.style.cursor and document.body.style.userSelect.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5ce2da6b-d2b7-4e2f-b190-4229be97075a

📥 Commits

Reviewing files that changed from the base of the PR and between fc2b2de and 5f7265e.

📒 Files selected for processing (4)
  • src/components/BilingualMarkdownRenderer.tsx
  • src/components/ReadmeModal.tsx
  • src/services/translateService.ts
  • src/utils/domTextScanner.ts
✅ Files skipped from review due to trivial changes (1)
  • src/components/BilingualMarkdownRenderer.tsx

Comment thread src/services/translateService.ts Outdated
Comment thread src/services/translateService.ts
Comment thread src/utils/domTextScanner.ts Outdated
Comment thread src/utils/domTextScanner.ts
修复翻译服务中的认证过期处理,添加AuthExpiredError类标记认证过期错误
优化token获取逻辑,避免竞态条件和重复请求
添加对abort signal的支持,确保请求可被正确取消
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
src/components/BilingualMarkdownRenderer.tsx (1)

137-146: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Detect the source language from scanned prose, not raw Markdown.

markdown includes fences, URLs, inline HTML, and other syntax noise, so code-heavy READMEs can skew toward English and pick the wrong translation direction or skip translation entirely. Use the scanned segment text (or a joined sample of it) here instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/BilingualMarkdownRenderer.tsx` around lines 137 - 146, The
language detection is being run on raw markdown (detectLanguage(markdown)) which
includes fences/URLs and can misdetect; instead build a clean sample from the
scanned prose segments and run detection on that sample. Replace the call to
detectLanguage(markdown) with detection on a joined/scoped string derived from
the scanned segments (e.g., concatenate the `.text` of the scan results or a
sliced sample of those segments), keep targetLang = language, then proceed with
the existing detected → getTranslateDirection(detected, targetLang) logic and
the same setError/updateStatus handling if languages match. Ensure you reference
the scanned segments used elsewhere in this component (the variable holding scan
results) so the change stays localized to the detection step.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/BilingualMarkdownRenderer.tsx`:
- Around line 185-188: The current logic sets a single request-scoped textType
based on batchHasInlineCode which mixes segments serialized for 'html' with
plain texts (variables: batchHasInlineCode, batchIndices, segments, batchTexts,
textType, translateBatch), causing encoding/escaping mismatches; fix by
splitting the batch into two requests (one for segments where
segments[j].hasInlineCode === true and one for the rest) and call translateBatch
separately with textType='html' for the inline-code group and textType='plain'
for the plain group (or alternatively ensure every plain text is properly
escaped before sending when using the 'html' path).

In `@src/components/ReadmeModal.tsx`:
- Around line 110-121: The TOC scroll/observer logic currently always targets
`[data-bi-heading-id]` (translation wrappers) and never updates when
translation/display mode changes; update the target resolution to pick the first
visible heading for the current mode by checking for a translationWrapper
(`querySelector([data-bi-heading-id="${CSS.escape(id)}"])`) and if not
visible/found, fall back to the original heading element, and use that resolved
element for scrolling (`container.scrollTo` / `container.scrollTop`) and
observing; also ensure the observer rebinding logic includes the
translation/display state (add `translateStatus` and `displayMode` to the effect
deps that attaches the observer and the TOC click handler) so the observer is
torn down and reattached when translation or display mode changes.

In `@src/services/translateService.ts`:
- Around line 275-283: The loop in translateService (looping over batch, using
currentBatch/currentLength and calling translateBatchInternal) allows a single
text with text.length > maxChars to be pushed and sent, so add handling for
oversized items: if text.length > maxChars and currentBatch is empty, split that
text into chunks that are <= maxChars (implement a helper like
splitTextIntoChunks(text, maxChars)) and for each chunk call
translateBatchInternal (or accumulate chunks into currentBatch and process as
usual); alternatively, if splitting is not desired, explicitly throw/reject with
a clear error when encountering a single segment > maxChars. Ensure you
reference and update currentBatch/currentLength appropriately when processing
chunks so no request exceeds maxChars.
- Around line 105-131: The backoff sleep in withTranslateRetry should be
abortable so cancellation via the provided signal cancels the retry wait
immediately; modify sleep to accept an AbortSignal (or create an abortableSleep
helper) that rejects with an AbortError when signal.aborted, then pass the same
signal from withTranslateRetry into sleep (and ensure any callers like apiMsAuth
and isTransientError checks still use the original signal) so that awaiting
sleep(baseDelay * 2**(attempt-1), signal) will throw and let withTranslateRetry
propagate the abort instead of waiting the full delay.

---

Duplicate comments:
In `@src/components/BilingualMarkdownRenderer.tsx`:
- Around line 137-146: The language detection is being run on raw markdown
(detectLanguage(markdown)) which includes fences/URLs and can misdetect; instead
build a clean sample from the scanned prose segments and run detection on that
sample. Replace the call to detectLanguage(markdown) with detection on a
joined/scoped string derived from the scanned segments (e.g., concatenate the
`.text` of the scan results or a sliced sample of those segments), keep
targetLang = language, then proceed with the existing detected →
getTranslateDirection(detected, targetLang) logic and the same
setError/updateStatus handling if languages match. Ensure you reference the
scanned segments used elsewhere in this component (the variable holding scan
results) so the change stays localized to the detection step.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 006ee0ee-b593-432f-9339-68a3887f2ec8

📥 Commits

Reviewing files that changed from the base of the PR and between 5f7265e and 983c5b6.

📒 Files selected for processing (5)
  • src/components/BilingualMarkdownRenderer.tsx
  • src/components/MarkdownRenderer.tsx
  • src/components/ReadmeModal.tsx
  • src/services/translateService.ts
  • src/utils/domTextScanner.ts

Comment thread src/components/BilingualMarkdownRenderer.tsx Outdated
Comment thread src/components/ReadmeModal.tsx
Comment thread src/services/translateService.ts Outdated
- 在ReadmeModal中添加对可见元素的检查,防止滚动到隐藏元素
- 改进翻译服务,支持大文本分块处理和取消操作
- 优化双语Markdown渲染器,区分处理纯文本和HTML内容
- 导出TranslateResult接口供外部使用
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/services/translateService.ts`:
- Around line 321-338: The loop in translateBatch (the batch processing block
using currentBatch/currentLength) fails to split an oversized text when a prior
flush just occurred because the split guard only runs when currentBatch.length
=== 0; after flushing you push the oversized text into currentBatch and send it,
violating the maxChars limit. Fix by changing the logic around
splitTextIntoChunks and translateBatchInternal so that any text with text.length
> maxChars is always split and processed via
splitTextIntoChunks/translateBatchInternal regardless of currentBatch state
(e.g., after you flush currentBatch and reset currentBatch/currentLength,
re-evaluate the same text against maxChars and call splitTextIntoChunks if
needed, otherwise push to currentBatch). Ensure references: splitTextIntoChunks,
translateBatchInternal, currentBatch, currentLength, maxChars, and the loop
variable text are updated accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e0a814d9-3bea-4b54-b33a-719dbb5918a3

📥 Commits

Reviewing files that changed from the base of the PR and between 983c5b6 and 6a39a2d.

📒 Files selected for processing (3)
  • src/components/BilingualMarkdownRenderer.tsx
  • src/components/ReadmeModal.tsx
  • src/services/translateService.ts
✅ Files skipped from review due to trivial changes (2)
  • src/components/BilingualMarkdownRenderer.tsx
  • src/components/ReadmeModal.tsx

Comment thread src/services/translateService.ts
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@AmintaCCCP
Copy link
Copy Markdown
Owner

我觉得差不多能合并了吧

@SummerRay160
Copy link
Copy Markdown
Contributor Author

可以了

@AmintaCCCP AmintaCCCP merged commit 51809af into AmintaCCCP:main May 4, 2026
5 checks passed
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.

2 participants