Skip to content

feat(markdown): render AI translation results with formatting#1161

Open
leozanee wants to merge 4 commits into
tisfeng:devfrom
leozanee:dev
Open

feat(markdown): render AI translation results with formatting#1161
leozanee wants to merge 4 commits into
tisfeng:devfrom
leozanee:dev

Conversation

@leozanee
Copy link
Copy Markdown

Summary

Render Markdown emitted by streaming AI/LLM services (OpenAI, Claude Code,
Custom OpenAI, Gemini, etc.) into formatted NSAttributedString in result
cells, instead of showing the raw ## / ** markers.

Scope

  • New MarkdownRenderer (Swift, no deps): block + inline pass with
    streaming-safety (unterminated ** / fences don't crash)
  • MarkdownLabel extends EZLabel, falls back to plain-text path when
    disabled — non-AI services and dark mode keep working unchanged
  • MarkdownToggleButton per-card override icon with 0.2s fade transition
  • Settings → General → Display global toggle (default ON)
  • Localized en / zh-Hans / zh-Hant / sk

Non-goals

  • Window resize UX is unchanged (separate concern)
  • Tables, footnotes, HTML embedded in markdown not supported (rare in AI output)

Test plan

  • MarkdownRendererTests (16 cases: blocks, inline, partial input, perf)
  • Manual: query AI service, verify headings / bold / lists render
  • Manual: per-card toggle flips one card without affecting siblings
  • Manual: Google / Bing / DeepL / Youdao show no toggle button, render unchanged
  • Build and run on macOS 13+

Render Markdown source from streaming AI/LLM services as styled
NSAttributedString in result cells: headings, emphasis, blockquotes,
lists, fenced/inline code, and links.

Plain-text services (Google, Bing, DeepL, Youdao) keep their existing
appearance. Add a global toggle in Settings → Display and a per-card
icon button with fade transition for inline override.
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Hello leozanee, Thank you for your first PR contribution 🎉 leozanee

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 43b9d20b83

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Easydict/Swift/Feature/Markdown/MarkdownLabel.swift Outdated
Comment thread Easydict/Swift/Feature/Markdown/MarkdownRenderer.swift Outdated
- Mark `EZLabel.text` as nullable to reflect the runtime contract: the
  ivar is nil during super init when font / line-spacing setters trigger
  `updateDisplayedText`, which previously would trap Swift bridging.
- Require word boundaries around `_` in italic emphasis so identifiers
  like `foo_bar_baz` keep literal underscores instead of italicizing
  the middle segment. `*` is unchanged.
- Cover both with new MarkdownRenderer tests.
@tisfeng
Copy link
Copy Markdown
Owner

tisfeng commented May 5, 2026

Thanks your PR, we will review it soon

@MoonMao42
Copy link
Copy Markdown
Collaborator

粗体+斜体 渲染三星号有问题,不能渲染出粗体+斜体.s实现效果应该是这样
image

Try a `***...***` match before the existing `**...**` bold check so the
outer pair of stars does not bind first and leak a stray `*` as literal.
Inner content is rendered with both bold and italic font traits applied
via `NSFontManager`.

Reported by @MoonMao42 in tisfeng#1161.
@leozanee
Copy link
Copy Markdown
Author

leozanee commented May 7, 2026

粗体+斜体 渲染三星号有问题,不能渲染出粗体+斜体.s实现效果应该是这样 image

Fixed in 2a45aae. The triple-star path now wraps the inner content with both bold and italic traits via NSFontManager. Added a regression test in MarkdownRendererTests.swift. Thanks for catching this!

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2a45aaeac3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

private func appendParagraph(_ text: String, to output: NSMutableAttributedString) {
let attrs = baseAttributes()
output.append(renderInline(text, base: attrs))
output.append(NSAttributedString(string: "\n", attributes: attrs))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid appending a trailing blank line

When Markdown rendering is enabled, every non-empty paragraph path appends a newline and render(_:) never trims the final separator, so even a one-line streaming answer like Hello becomes Hello\n. EZWordResultView sizes the NSTextView from this attributed string via labelSize, so Markdown-enabled AI results get an extra empty line/vertical space compared with the same result rendered as plain text; this is especially visible for short answers and when toggling Markdown on/off.

Useful? React with 👍 / 👎.

# Conflicts:
#	Easydict/App/Easydict-Bridging-Header.h
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.

3 participants