Skip to content

Feature/translate enhance#1942

Merged
Tlaster merged 14 commits intomasterfrom
feature/translate-enhance
Mar 30, 2026
Merged

Feature/translate enhance#1942
Tlaster merged 14 commits intomasterfrom
feature/translate-enhance

Conversation

@Tlaster
Copy link
Copy Markdown
Contributor

@Tlaster Tlaster commented Mar 28, 2026

fix #1513

@Tlaster Tlaster marked this pull request as ready for review March 29, 2026 07:40
@Tlaster Tlaster requested a review from Copilot March 29, 2026 07:40
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements a richer translation pipeline across the shared Kotlin code and the UI layers to address #1513, adding AI-backed translation with background “pre-translation” + caching, and UI affordances to show/trigger translation state.

Changes:

  • Introduces a structured JSON translation format for UiRichText and applies translated text while preserving styles/images.
  • Adds a translation cache table/DAO + pre-translation background service, integrating it into timeline/status/profile loading and deep-link actions.
  • Adds settings/UI updates (Android/Desktop/iOS + Compose) for pre-translation and display badges/menu actions.

Reviewed changes

Copilot reviewed 64 out of 66 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
shared/src/jvmTest/kotlin/dev/dimension/flare/ui/model/DeeplinkEventTest.kt Adds deeplink round-trip test for translation retry event.
shared/src/jvmMain/kotlin/dev/dimension/flare/common/Locale.jvm.kt Makes locale language read dynamically via getter.
shared/src/commonTest/kotlin/dev/dimension/flare/ui/render/TranslationJsonUiRichTextTest.kt Tests translation JSON export/apply behavior (styles/locked tokens).
shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/UserHandlerTest.kt Adds tests for profile pre-translation caching + retry behavior.
shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostHandlerTest.kt Adds tests for long-text detail pre-translation and wiring helpers.
shared/src/commonTest/kotlin/dev/dimension/flare/data/datasource/microblog/MixedRemoteMediatorTest.kt Adds end-to-end tests for timeline pre-translation scheduling/skip cases and stale cleanup.
shared/src/commonTest/kotlin/dev/dimension/flare/data/database/cache/mapper/MicroblogTest.kt Verifies mapper applies translations, menu actions, display states, and long-text behavior.
shared/src/commonTest/kotlin/dev/dimension/flare/data/database/cache/dao/TranslationDaoTest.kt Adds DAO tests for translation persistence, updates, stale marking, and deletes.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/render/TranslationJson.kt Introduces JSON translation document/tokenization + apply logic for rich text.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/TranslatePresenter.kt Switches to AiCompletionService, adds translation caching, JSON-based translation application.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/status/StatusContextPresenter.kt Enables long-text translation display in status context view.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AiConfigPresenter.kt Normalizes AI config and updates OpenAI model fetching behavior.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/server/AiTLDRPresenter.kt Switches TLDR to AiCompletionService and keeps legacy fallback.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenter.kt Integrates translation display options + pre-translation into timeline paging flow.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/DeepLinkPresenter.kt Handles new translation deeplink actions (translate/retry/show original).
shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/XQT.kt Captures source language into timeline posts for XQT.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/VVO.kt Sets source languages for VVO statuses/users.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Rss.kt Adds optional source language to feed items and persists it.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Misskey.kt Propagates profile language into UiProfile.sourceLanguages.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Mastodon.kt Propagates status language into UiTimelineV2.Post.sourceLanguages.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Bluesky.kt Extracts langs into persistent sourceLanguages for Bluesky posts.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiTimelineV2.kt Adds sourceLanguages + transient translationDisplayState to posts/feeds.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/UiProfile.kt Adds sourceLanguages + transient translationDisplayState, merges languages on update.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/TranslationDisplayState.kt Adds display-state enum for translation badge rendering.
shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/DeeplinkEvent.kt Adds translation deeplink event payloads + enforces exactly one payload.
shared/src/commonMain/kotlin/dev/dimension/flare/di/CommonModule.kt Registers AiCompletionService + PreTranslationService in DI.
shared/src/commonMain/kotlin/dev/dimension/flare/data/translation/PreTranslationService.kt Implements AI-backed pre-translation queueing/batching, caching, skip logic, and retries.
shared/src/commonMain/kotlin/dev/dimension/flare/data/network/ai/OpenAIService.kt Adds chatCompletionOrNull helper for config validation.
shared/src/commonMain/kotlin/dev/dimension/flare/data/network/ai/AiCompletionService.kt Adds unified AI completion facade for translate/TLDR.
shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/AppSettings.kt Adds preTranslation flag + iOS default companion.
shared/src/commonMain/kotlin/dev/dimension/flare/data/datastore/model/AiPromptDefaults.kt Updates default translate prompt to describe JSON translation contract.
shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/rss/RssTimelineRemoteMediator.kt Passes source-language into feed render.
shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/TimelineRemoteMediator.kt Enqueues statuses/references for pre-translation after saving to DB.
shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/paging/TimelinePagingMapper.kt Applies translations when mapping cached DB items to UI models.
shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/UserHandler.kt Enqueues profile pre-translation and conditionally applies cached translations.
shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/handler/PostHandler.kt Enqueues status pre-translation for detail view and applies cached translations in cache flow.
shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/ActionMenu.kt Adds menu item types for translate/show original/retry translation.
shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/TranslationDisplay.kt Implements translation display logic + menu injection via deeplinks + stable hashing.
shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbTranslation.kt Adds translation entity + enums + converters + entity-key helpers.
shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/model/DbPagingTimeline.kt Adds Room relation from DbStatus to DbTranslation list.
shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/TranslationDao.kt Adds translation DAO for CRUD + stale in-flight cleanup helpers.
shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/dao/StatusDao.kt Adds sync getWithReferencesSync used by retry translation.
shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/CacaheDatabase.kt Bumps cache DB version and registers DbTranslation + converters + DAO.
shared/src/appleMain/kotlin/dev/dimension/flare/common/Locale.apple.kt Makes locale language read dynamically via getter.
shared/src/androidMain/kotlin/dev/dimension/flare/common/Locale.android.kt Makes locale language read dynamically via getter.
iosApp/flare/UI/Screen/AiConfigScreen.swift Adds iOS pre-translation toggle; fixes picker binding type.
iosApp/flare/UI/FlareTheme.swift Uses shared AiConfig.default for environment default.
iosApp/flare/UI/Component/TranslateStatusComponent.swift Adds iOS translation badge component.
iosApp/flare/UI/Component/Status/StatusView.swift Shows translation badge for statuses.
iosApp/flare/UI/Component/Status/StatusActionView.swift Adds new action menu labels for translation actions.
iosApp/flare/UI/Component/Status/FeedView.swift Shows translation badge for feed items.
iosApp/flare/UI/Component/CommonProfileHeader.swift Shows translation badge for profiles.
iosApp/flare/Assets.xcassets/fa-language.symbolset/fa-language.svg Adds icon asset for translation badge.
iosApp/flare/Assets.xcassets/fa-language.symbolset/Contents.json Registers translation badge icon asset.
desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/settings/SettingsScreen.kt Adds desktop pre-translation toggle in AI config.
desktopApp/src/main/composeResources/values/strings.xml Adds desktop strings for pre-translation toggle.
desktopApp/src/main/composeResources/values-zh-rCN/strings.xml Adds Chinese desktop strings for pre-translation toggle.
compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/StatusTranslatePresenter.kt Adds cache target info so manual translate can persist in DB.
compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/FeedComponent.kt Shows translation badge for feed items.
compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/CommonStatusComponent.kt Adds translation badge + wires translation component with post context; adds menu strings mapping.
compose-ui/src/commonMain/kotlin/dev/dimension/flare/ui/component/ProfileHeader.kt Shows translation badge for profiles.
compose-ui/src/commonMain/composeResources/values/strings.xml Adds translation badge/menu strings.
compose-ui/src/commonMain/composeResources/values-zh-rCN/strings.xml Adds Chinese translation badge/menu strings.
app/src/main/res/values/strings.xml Adds Android strings for pre-translation toggle.
app/src/main/java/dev/dimension/flare/ui/screen/settings/AiConfigScreen.kt Adds Android pre-translation toggle UI.
Comments suppressed due to low confidence (1)

shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AiConfigPresenter.kt:104

  • AiConfigPresenter no longer clears the selected OpenAI model when serverUrl/apiKey changes. This can leave an invalid model configured for the new endpoint, causing translation/TLDR calls to fail until the user manually reselects a model. Consider restoring the previous behavior (reset model to empty when serverUrl/apiKey changes) or validating that the selected model is still present in the fetched model list and clearing it if not.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +84 to +101
val projection = toProjectionBlocks()
val projectedBlocksByContent = projection.associateBy { it.content }
val blocksById = document.blocks.associateBy { it.id }
if (blocksById.size != projection.size) {
throw TranslationFormatException(
"Expected ${projection.size} blocks but found ${blocksById.size}",
)
}

val translatedContents =
renderRuns.map { content ->
when (content) {
is RenderContent.BlockImage -> content
is RenderContent.Text -> {
val projectedBlock =
projectedBlocksByContent[content]
?: return@map content
val translatedBlock =
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

applyTranslationDocument builds projectedBlocksByContent via projection.associateBy { it.content }, but RenderContent.Text is a data class (value-equality). If the same block content appears multiple times (e.g., two identical paragraphs), the map will collapse entries and the wrong translated block may be applied (or one block never applied). Consider keying projection blocks by their position/index in renderRuns (or carrying a unique block id) instead of using RenderContent.Text as the map key.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +14
"The input is JSON extracted from a social post.\n" +
"For each item, either return status=\"Completed\" with a translated payload, " +
"or status=\"Skipped\" without a payload when the source language already " +
"matches {target_language}.\n" +
"Preserve entityKey, block ids, token ids, and token kinds exactly as-is.\n" +
"Only translate token text where kind is \"Translatable\".\n" +
"Keep token text where kind is \"Locked\" unchanged.\n" +
"Return ONLY JSON without markdown code fences or explanations.\n" +
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

The default translate prompt describes returning status="Completed" / status="Skipped", but the actual batch format is JSON with a status field (enum values like "Completed"/"Skipped"). This mismatch can lead the model to emit an incompatible shape. Update the prompt wording to explicitly require a JSON field (e.g., "status": "Completed") matching PreTranslationBatchItem.status.

Copilot uses AI. Check for mistakes.
Comment on lines +225 to +227
case .retryTranslation: return LocalizedStringResource(stringLiteral: "Retry translation")
case .translate: return LocalizedStringResource(stringLiteral: "Translate")
case .showOriginal: return LocalizedStringResource(stringLiteral: "Show original")
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

These new action labels are created with LocalizedStringResource(stringLiteral: ...), which bypasses localization and will always display English. The rest of the switch returns localization keys (e.g., "like", "more"). Please add proper localized keys for these actions and return those keys instead of string literals.

Suggested change
case .retryTranslation: return LocalizedStringResource(stringLiteral: "Retry translation")
case .translate: return LocalizedStringResource(stringLiteral: "Translate")
case .showOriginal: return LocalizedStringResource(stringLiteral: "Show original")
case .retryTranslation: return "retry_translation"
case .translate: return "translate"
case .showOriginal: return "show_original"

Copilot uses AI. Check for mistakes.
Comment on lines +612 to +616
private fun shouldTranslate(
existing: DbTranslation?,
sourceHash: String,
now: Long,
): Boolean {
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

shouldTranslate takes a now parameter but never uses it. This adds noise and can trigger unused-parameter warnings; consider removing the parameter or using it (e.g., for cooldown/backoff decisions).

Copilot uses AI. Check for mistakes.
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: 7220f6609c

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +15 to +16
val translationEvent: TranslationEvent? = null,
val postEvent: PostEvent? = null,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep DeeplinkEvent protobuf tags stable

Because DeeplinkEvent is serialized with ProtoBuf and no explicit @ProtoNumbers are defined, field numbers are derived from declaration order. Inserting translationEvent before postEvent changes the wire tag for postEvent, so previously generated flare-event://... payloads (from older app versions) can no longer decode correctly and will be dropped by parse() after the XOR invariant check. Please preserve existing tag numbers (e.g., via @ProtoNumber) when evolving this schema.

Useful? React with 👍 / 👎.


internal fun UiRichText.applyTranslationDocument(document: TranslationDocument): UiRichText {
val projection = toProjectionBlocks()
val projectedBlocksByContent = projection.associateBy { it.content }
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 Map translated blocks by position instead of content key

Using projection.associateBy { it.content } collapses duplicate RenderContent.Text blocks into a single entry. When a post contains two identical text blocks, both lookups resolve to the same projected block id, so one translated block is effectively reused and the other is ignored, producing incorrect block-to-translation alignment. This should keep a one-to-one mapping (e.g., by index/id) rather than deduplicating by value equality.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@Tlaster Tlaster merged commit d3ae8b2 into master Mar 30, 2026
9 checks passed
@Tlaster Tlaster deleted the feature/translate-enhance branch March 30, 2026 05:14
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.

[Request]Improve translate

2 participants