Skip to content

Conversation

@kean
Copy link
Contributor

@kean kean commented Dec 22, 2025

Description

Closes CMM-745: On-device translation for posts.

Testing instructions

  • Open Reader and find a post in a locale different from your current locale and supported by the Translation framework
  • Tap "More" and verify that the Translate button is available
  • Tap "Translate" and verify that the spinner is shown in the navigation items
  • Verify that on the first translation, the system prompts you to download the required translations
  • Verify that after translation is successful, both the title and the content are translated. The content should preserve its HTML structure and tags

Recording

ScreenRecording_12-22-2025.16-58-08_1.MP4

@kean kean added this to the 26.6 milestone Dec 22, 2025
@kean kean force-pushed the task/integrate-translations branch from 8f8dc24 to 866bd15 Compare December 22, 2025 22:37
}
translationAvailabilityTask?.cancel()
translationAvailabilityTask = Task { @MainActor in
self.isTranslationAvailable = await self.checkIsTranslationAvailable()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It creates a little bit of a race condition, but in practice, it's extremely fast so it should not matter.

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Dec 22, 2025

App Icon📲 You can test the changes from this Pull Request in WordPress by scanning the QR code below to install the corresponding build.
App NameWordPress
ConfigurationRelease-Alpha
Build Number30286
VersionPR #25089
Bundle IDorg.wordpress.alpha
Commit14cb490
Installation URL5l5l47t494ndg
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Dec 22, 2025

App Icon📲 You can test the changes from this Pull Request in Jetpack by scanning the QR code below to install the corresponding build.
App NameJetpack
ConfigurationRelease-Alpha
Build Number30286
VersionPR #25089
Bundle IDcom.jetpack.alpha
Commit14cb490
Installation URL749q3uftjths0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

}

private lazy var _translationViewModel: Any = {
guard #available(iOS 26, *) else { return {} }
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure how I feel about this pattern... I guess it's fine, since the else block is dead code because the caller only runs on iOS 26+. But maybe there is another better (in terms of readability) way to code this?


// Use continuation to wait for translation result
return try await withCheckedThrowingContinuation { continuation in
self.continuation = continuation
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems a bit risky to hold the continuation instances at the class level, instead of locally in the function.

@crazytonyli
Copy link
Contributor

I couldn't get the translation to work. I can see the UI from the Translation framework, but after selecting languages, the app shows an "Unable to translate" error message. There are 100+ repeating error messages in Xcode console:

Got response from extension with error: Error Domain=TranslationErrorDomain Code=14 "(null)"
Connection interrupted, finishing translation with error Error Domain=TranslationErrorDomain Code=14 "(null)"

@kean
Copy link
Contributor Author

kean commented Jan 5, 2026

I couldn't get the translation to work. I can see the UI from the Translation framework, but after selecting languages, the app shows an "Unable to translate" error message. There are 100+ repeating error messages in Xcode console:

I forgot to mention that it requires a physical device. Have you tried on a physical one or a sim?

@crazytonyli
Copy link
Contributor

@kean Yes, I used my iPhone 16. I also downloaded the Chinese and English languages in the Translate app, which I don't think is necessary, but just in case.

@kean
Copy link
Contributor Author

kean commented Jan 5, 2026

I've tested a few other scenarios and found and fixed some issues:

  • Added isTranslating flag to guarantee that only one translation runs at a time. This ensures that the continuation you mentioned only gets invoked once. It has to be done in some way like that because .translationTask supports running only one translation at a time.
  • Fixed an issue where tapping "Translate" after cancellation the previous translation would not work
  • Fixed an issue where cancellation would be reported in the UI
  • Improved translate as it now uses the detected source language, which is more reliable than it trying to detect it automatically from full HTML

I also opened the following issue: CMM-1107: Post translation in Reader sometimes breaks the HTML tags. I don't think there is an easy fix, so I'm not planning to address it in the scope of the PR.

I've tested a couple of posts in Mandarin, and it worked for me. Would you mind sharing a post for which the translation fails?

ScreenRecording_01-05-2026.18-11-50_1.MP4

@kean kean requested a review from crazytonyli January 5, 2026 23:17
@crazytonyli
Copy link
Contributor

The translation works on my iPhone now. What do you think about adding a button to revert back to the original content?

Copy link
Contributor

@crazytonyli crazytonyli left a comment

Choose a reason for hiding this comment

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

I still feel like the indirection between setting the configuration property to trigger the translation process and getting the translated result can be improved.

guard #available(iOS 26, *) else {
return
}
let hostingController = UIHostingController(rootView: translationHostView)
Copy link
Contributor

Choose a reason for hiding this comment

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

It's annoying that the Translation framework does not support UIKit. What do you think about adding a comment here, saying this hidden UIHostingController view is used to present translation UI?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It definitely is. I added a note.

Color.clear
.frame(width: 0, height: 0)
.translationTask(viewModel.configuration) { session in
Task { @MainActor in
Copy link
Contributor

Choose a reason for hiding this comment

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

This Task is not needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I copied it from the documentation, but it looks like it's not actually needed. The closure is async. The await on MainActor confined ViewModel achieves the jump to the main thread. Updated.

Screenshot 2026-01-06 at 11 18 32 AM

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 6, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants