Skip to content

refa: Translation Module - #205

Merged
omg-xtao merged 2 commits into
devfrom
refa/trans
Oct 9, 2026
Merged

omg-xtao merged 2 commits into
devfrom
refa/trans

Conversation

@omg-xtao

@omg-xtao omg-xtao commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

新增 "Keep formatting" 设置,翻译时保留文本的粗体、斜体等格式,新增相关配置项和翻译逻辑,重构翻译代码以支持格式保留,同时优化翻译任务的生命周期管理,避免页面销毁后仍执行翻译任务。

Summary by Sourcery

Improve the translation module with formatting preservation, richer message support, and lifecycle-safe translation tasks.

New Features:

  • Add a Keep formatting setting that preserves message entities such as bold, italic, links, spoilers, code, blockquotes, and custom emoji during translation.
  • Support rich-text translation for regular messages, polls, and article content while storing results in Telegram’s translation fields.

Bug Fixes:

  • Keep locally generated translations visible independently of dialog-wide automatic translation settings.
  • Prevent message and article translation jobs from continuing after their chat or article viewer is destroyed.

Enhancements:

  • Refactor message and article translation into scoped, bounded-concurrency coroutine workflows with progress reporting, caching, cancellation, and improved error handling.
  • Preserve locally generated translation state when message objects are rebuilt.

Chores:

  • Remove legacy custom translated message fields in favor of upstream translation fields.

新增 "Keep formatting" 设置,翻译时保留文本的粗体、斜体等格式,新增相关配置项和翻译逻辑,重构翻译代码以支持格式保留,同时优化翻译任务的生命周期管理,避免页面销毁后仍执行翻译任务。

thanks to @OctoGramApp

Co-authored-by: nick <64551534+null-nick@users.noreply.github.com>
Co-authored-by: waifucon <parsastdev@duck.com>
Co-Authored-By: M3.1-Flash-Preview <model@minimax.io>
@omg-xtao omg-xtao self-assigned this Oct 9, 2026
@omg-xtao omg-xtao added the enhancement New feature or request label Oct 9, 2026
@sourcery-ai

sourcery-ai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Refactors third-party message, poll, and article translation around rich-text entity preservation and configurable formatting retention, while moving local translation state into MessageObject and making translation work lifecycle-scoped so it cannot outlive its chat or article viewer.

Sequence diagram for rich-text message translation

sequenceDiagram
    participant User
    participant ChatActivity
    participant MessageTrans
    participant MessageTransText
    participant Translator
    participant MessageObject

    User->>ChatActivity: translateMessages()
    ChatActivity->>MessageTrans: translateOne()
    MessageTrans->>MessageTransText: queryTextOf(source)
    alt KeepFormatting enabled
        MessageTransText-->>MessageTrans: HTML with entities
    else KeepFormatting disabled
        MessageTransText-->>MessageTrans: Plain text
    end
    MessageTrans->>Translator: translate(target, query, context)
    Translator-->>MessageTrans: translated text
    MessageTrans->>MessageTransText: merge(source, translated, separator)
    MessageTransText-->>MessageTrans: translatedText with entities
    MessageTrans->>MessageObject: markTranslatedLocally()
    MessageObject->>MessageObject: updateTranslation(false)
Loading

Sequence diagram for lifecycle-scoped translation cancellation

sequenceDiagram
    participant User
    participant ChatActivity
    participant TranslationScope
    participant Translator
    participant ArticleViewer

    User->>ChatActivity: translateMessages()
    ChatActivity->>TranslationScope: launch translation job
    TranslationScope->>Translator: translate requests
    User->>ChatActivity: onFragmentDestroy()
    ChatActivity->>TranslationScope: cancelTranslations()
    TranslationScope-->>Translator: cancel pending work
    User->>ArticleViewer: close article
    ArticleViewer->>ArticleViewer: destroyArticleViewer()
    ArticleViewer->>TranslationScope: cancelArticleTranslations()
Loading

File-Level Changes

Change Details Files
Introduces rich-text-aware translation with optional formatting preservation.
  • Adds a Keep formatting preference enabled by default.
  • Converts Telegram message entities to provider-facing HTML and parses translated HTML back into entities, including custom emoji, links, spoilers, blockquotes, and code.
  • Merges original and translated text according to the hide-origin setting while preserving entity offsets.
  • Refactors plain-message and poll translation to share cache and rich-text handling, storing results in upstream translatedText/translatedPoll fields.
TMessagesProj/src/main/java/tw/nekomimi/nekogram/parts/MessageTrans.kt
TMessagesProj/src/main/java/tw/nekomimi/nekogram/parts/MessageTransText.kt
TMessagesProj/src/main/java/tw/nekomimi/nekogram/parts/MessageTransPoll.kt
TMessagesProj/src/main/java/tw/nekomimi/nekogram/transtale/html/HTMLKeeper.java
TMessagesProj/src/main/java/tw/nekomimi/nekogram/transtale/html/HTMLTagAttributesHandler.java
TMessagesProj/src/main/java/tw/nekomimi/nekogram/transtale/html/CustomElementHandler.java
TMessagesProj/src/main/kotlin/xyz/nextalone/nagram/NaConfig.kt
TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoGeneralSettingsActivity.java
TMessagesProj/src/main/res/values/strings_na.xml
Moves local translation state from serialized message fields to MessageObject state and integrates it with rendering and message rebuilding.
  • Adds translatedLocally and reapplication logic so third-party translations remain visible independently of dialog auto-translation settings.
  • Updates message cells, menus, polls, captions, loading indicators, and auto-translation eligibility to use MessageObject.translated.
  • Removes custom translated and translatedMessage fields from TLRPC.Message and legacy content overrides.
  • Carries local translation state across MessageObject reconstruction.
TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java
TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java
TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java
TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java
TMessagesProj/src/main/java/tw/nekomimi/nekogram/ui/MessageHelper.java
TMessagesProj/src/main/kotlin/xyz/nextalone/nagram/helper/MessageHelper.kt
Scopes translation jobs to their owning chat or article viewer and cancels them during lifecycle teardown.
  • Replaces GlobalScope and manually managed thread pools with SupervisorJob-based IO coroutine scopes using bounded parallelism.
  • Tracks scopes with weak maps and cancels existing/replaced jobs, progress dialogs, and pending work on user cancellation.
  • Cancels chat translations on ChatActivity destruction and article translations on ArticleViewer destruction.
  • Adds bounded article translation, progress tracking, and failure handling.
TMessagesProj/src/main/java/tw/nekomimi/nekogram/parts/MessageTrans.kt
TMessagesProj/src/main/java/tw/nekomimi/nekogram/parts/ArticleTrans.kt
TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java
TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java

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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

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

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="TMessagesProj/src/main/java/tw/nekomimi/nekogram/parts/ArticleTrans.kt" line_range="172" />
<code_context>
     }
+    if (cancel.get()) return false
+
+    val translated = runCatching { Translator.translate(text) }.getOrNull()
+    progress.tick { updatePaintSize() }
+    if (translated == null) {
</code_context>
<issue_to_address>
**Cancellation shows failure dialogs**

When a translation coroutine is canceled while a translator call is suspended or an article viewer is destroyed, `runCatching` consumes `CancellationException`, and the article `catch (Exception)` also treats it as a provider failure while the separate `cancel` flag remains false. The message and article paths schedule failure dialogs; the article `finally` also schedules `updatePaintSize` after destruction, so users see UI work against a canceled or destroyed screen.

Propagate `CancellationException` through `runCatching`, handle it separately from provider failures, and skip article UI callbacks after cancellation.

Also at `TMessagesProj/src/main/java/tw/nekomimi/nekogram/parts/MessageTrans.kt:206-209`, `TMessagesProj/src/main/java/tw/nekomimi/nekogram/parts/MessageTrans.kt:238-241`, `TMessagesProj/src/main/java/tw/nekomimi/nekogram/parts/ArticleTrans.kt:129-134`.
</issue_to_address>

### Comment 2
<location path="TMessagesProj/src/main/java/tw/nekomimi/nekogram/transtale/html/HTMLKeeper.java" line_range="70-73" />
<code_context>
+
+            ArrayList<CharacterStyle> currentActiveSpans = new ArrayList<>(Arrays.asList(spans));
+
+            for (int j = lastActiveSpans.size() - 1; j >= 0; j--) {
+                CharacterStyle lastSpan = lastActiveSpans.get(j);
+                if (!currentActiveSpans.contains(lastSpan)) {
+                    closeTagFor(out, lastSpan);
+                }
+            }
</code_context>
<issue_to_address>
**Overlapping formatting emits invalid HTML**

When source formatting ranges overlap without being properly nested, `toHtml` closes a span without closing and reopening still-active nested spans, producing crossing tags such as `<b>…<i>…</b>…</i>`. Providers receive malformed markup, so translated formatting is lost or misapplied.

Serialize overlapping spans by closing and reopening active spans so the output always has properly nested tags.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 2 findings to address first, and a faulty translation or formatting conversion could be stored in the local translation cache and remain displayed on rebuilt messages after the code is reverted. The affected content is bounded and can be cleared or retranslated; it does not move money, change access, delete records, or create an irreversible external side effect.

Blocking findings: TMessagesProj/src/main/java/tw/nekomimi/nekogram/parts/ArticleTrans.kt:172, TMessagesProj/src/main/java/tw/nekomimi/nekogram/transtale/html/HTMLKeeper.java:73


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread TMessagesProj/src/main/java/tw/nekomimi/nekogram/parts/ArticleTrans.kt Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

One or more issues must be addressed before approval.

6 open findings
What changed in this PR

Adds rich-text preservation and safer coroutine lifecycles to NekoX’s message and article translation systems.

Changes:

  • Adds the “Keep formatting” setting and HTML/entity conversion utilities.
  • Refactors message and poll translation with caching and bounded concurrency.
  • Adds cancellation handling for chat and article destruction.
File Description
TMessagesProj/​src/​main/​res/​values/​strings_na.xml Updated as part of this pull request.
TMessagesProj/​src/​main/​kotlin/​xyz/​nextalone/​nagram/​NaConfig.kt Updated as part of this pull request.
TMessagesProj/​src/​main/​kotlin/​xyz/​nextalone/​nagram/​helper/​MessageHelper.kt Updated as part of this pull request.
TMessagesProj/​src/​main/​java/​tw/​nekomimi/​nekogram/​ui/​MessageHelper.java Updated as part of this pull request.
TMessagesProj/​src/​main/​java/​tw/​nekomimi/​nekogram/​transtale/​html/​HTMLTagAttributesHandler.java Updated as part of this pull request.
TMessagesProj/​src/​main/​java/​tw/​nekomimi/​nekogram/​transtale/​html/​HTMLKeeper.java Updated as part of this pull request.
TMessagesProj/​src/​main/​java/​tw/​nekomimi/​nekogram/​transtale/​html/​CustomElementHandler.java Updated as part of this pull request.
TMessagesProj/​src/​main/​java/​tw/​nekomimi/​nekogram/​settings/​NekoGeneralSettingsActivity.java Updated as part of this pull request.
TMessagesProj/​src/​main/​java/​tw/​nekomimi/​nekogram/​parts/​MessageTransText.kt Updated as part of this pull request.
TMessagesProj/​src/​main/​java/​tw/​nekomimi/​nekogram/​parts/​MessageTransPoll.kt Updated as part of this pull request.
TMessagesProj/​src/​main/​java/​tw/​nekomimi/​nekogram/​parts/​MessageTrans.kt Updated as part of this pull request.
TMessagesProj/​src/​main/​java/​tw/​nekomimi/​nekogram/​parts/​ArticleTrans.kt Updated as part of this pull request.
TMessagesProj/​src/​main/​java/​org/​telegram/​ui/​ChatActivity.java Updated as part of this pull request.
TMessagesProj/​src/​main/​java/​org/​telegram/​ui/​Cells/​ChatMessageCell.java Updated as part of this pull request.
TMessagesProj/​src/​main/​java/​org/​telegram/​ui/​ArticleViewer.java Updated as part of this pull request.
TMessagesProj/​src/​main/​java/​org/​telegram/​tgnet/​TLRPC.java Updated as part of this pull request.
TMessagesProj/​src/​main/​java/​org/​telegram/​messenger/​MessageObject.java Updated as part of this pull request.

🧠 Review effort: Lite


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +129 to +132
} catch (e: Exception) {
if (!cancel.get()) {
UIUtil.runOnUIThread { showArticleTransFailed(status, e) }
}
UIUtil.runOnUIThread { showArticleTransFailed(status, e) }
}
} finally {
UIUtil.runOnUIThread(Runnable { updatePaintSize() })
}.awaitAll()
} finally {
messages.forEach { it.translating = false }
UIUtil.runOnUIThread { if (!cancel.get()) status?.uDismiss() }
Comment on lines +206 to 211
val translated = runCatching {
Translator.translate(target, MessageTransText.queryTextOf(source), context)
}.getOrElse {
reportError(target, listOf(message), it, cancel, status)
return false
}
Comment on lines +253 to +255
private fun markTranslatedLocally(message: MessageObject, block: (TLRPC.Message) -> Unit) {
block(message.messageOwner)
message.translatedLocally = true
Comment on lines +342 to +344
} else if (mSpan instanceof BlockquoteSpan) {
entity = new TLRPC.TL_messageEntityBlockquote();
entity.collapsed = true;
@omg-xtao
omg-xtao merged commit b349f97 into dev Oct 9, 2026
0 of 3 checks passed
@omg-xtao
omg-xtao deleted the refa/trans branch October 9, 2026 09:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants