Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 7838c13

Browse files
committed
Bug 1892743 - Add Global Translations Offer AC Options r=android-reviewers,giorga
Right now, it is only possible to access the “offer to translate” setting off of page settings. Unlike most of the other page settings, this setting is also a global setting. This patch adds an option to view this setting independently of page settings and helps keep it in sync, when page settings change this setting as well. We need this because it will soon be possible to access the global settings layer of settings directly via the main menu settings. This patch: * Adds `SetGlobalOfferTranslateSettingAction` - Sets the `offerTranslation` value on the global `TranslationsBrowserState`. Intended for global usage. * Adds `UpdateGlobalOfferTranslateSettingAction` - A global way to update the offer to translate value outside of a page. * Adds `TranslationOperation.FETCH_OFFER_SETTING` - The same as `SetGlobalOfferTranslateSettingAction`, but for a tab. Intended for tab usage. * Adds a call to set `offerTranslation` on `Init` Differential Revision: https://phabricator.services.mozilla.com/D208233
1 parent aa07c5e commit 7838c13

File tree

7 files changed

+233
-0
lines changed

7 files changed

+233
-0
lines changed

mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/action/BrowserAction.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,30 @@ sealed class TranslationsAction : BrowserAction() {
10471047
val setting: Boolean,
10481048
) : TranslationsAction(), ActionWithTab
10491049

1050+
/**
1051+
* Sets the translations offer setting on the global store.
1052+
* The translations offer setting controls when to offer a translation on a page.
1053+
*
1054+
* See [SetPageSettingsAction] for setting the offer setting on the session store.
1055+
*
1056+
* @property offerTranslation The offer setting to set.
1057+
*/
1058+
data class SetGlobalOfferTranslateSettingAction(
1059+
val offerTranslation: Boolean,
1060+
) : TranslationsAction()
1061+
1062+
/**
1063+
* Updates the specified translation offer setting on the translation engine and ensures the final
1064+
* state on the global store remains in-sync.
1065+
*
1066+
* See [UpdatePageSettingAction] for updating the offer setting on the session store.
1067+
*
1068+
* @property offerTranslation The offer setting to set.
1069+
*/
1070+
data class UpdateGlobalOfferTranslateSettingAction(
1071+
val offerTranslation: Boolean,
1072+
) : TranslationsAction()
1073+
10501074
/**
10511075
* Sets the map of BCP 47 language codes (key) and the [LanguageSetting] option (value).
10521076
*

mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/engine/middleware/TranslationsMiddleware.kt

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ class TranslationsMiddleware(
7979
requestPageSettings(context, action.tabId)
8080
}
8181
}
82+
TranslationOperation.FETCH_OFFER_SETTING -> {
83+
scope.launch {
84+
requestOfferSetting(context, action.tabId)
85+
}
86+
}
87+
8288
TranslationOperation.FETCH_AUTOMATIC_LANGUAGE_SETTINGS -> {
8389
scope.launch {
8490
requestLanguageSettings(context, action.tabId)
@@ -151,6 +157,14 @@ class TranslationsMiddleware(
151157
}
152158
}
153159
}
160+
161+
is TranslationsAction.UpdateGlobalOfferTranslateSettingAction -> {
162+
scope.launch {
163+
updateAlwaysOfferPopupPageSetting(
164+
setting = action.offerTranslation,
165+
)
166+
}
167+
}
154168
else -> {
155169
// no-op
156170
}
@@ -169,6 +183,7 @@ class TranslationsMiddleware(
169183
* Language Models - [requestLanguageModels]
170184
* Language Settings - [requestLanguageSettings]
171185
* Never Translate Sites List - [requestNeverTranslateSites]
186+
* Offer Setting - [requestOfferSetting]
172187
*
173188
* @param context Context to use to dispatch to the store.
174189
*/
@@ -179,6 +194,7 @@ class TranslationsMiddleware(
179194
requestLanguageModels(context)
180195
requestLanguageSettings(context)
181196
requestNeverTranslateSites(context)
197+
requestOfferSetting(context)
182198
}
183199

184200
/**
@@ -465,6 +481,38 @@ class TranslationsMiddleware(
465481
}
466482
}
467483

484+
/**
485+
* Retrieves the setting to always offer to translate and dispatches the result to the
486+
* store via [TranslationsAction.SetGlobalOfferTranslateSettingAction]. Will additionally
487+
* dispatch a request to update page settings, when a [tabId] is provided.
488+
*
489+
* @param context Context to use to dispatch to the store.
490+
* @param tabId Tab ID associated with the request.
491+
*/
492+
private fun requestOfferSetting(
493+
context: MiddlewareContext<BrowserState, BrowserAction>,
494+
tabId: String? = null,
495+
) {
496+
logger.info("Requesting offer setting.")
497+
val alwaysOfferPopup: Boolean = engine.getTranslationsOfferPopup()
498+
499+
context.store.dispatch(
500+
TranslationsAction.SetGlobalOfferTranslateSettingAction(
501+
offerTranslation = alwaysOfferPopup,
502+
),
503+
)
504+
505+
if (tabId != null) {
506+
// Fetch page settings to ensure the state matches the engine.
507+
context.store.dispatch(
508+
TranslationsAction.OperationRequestedAction(
509+
tabId = tabId,
510+
operation = TranslationOperation.FETCH_PAGE_SETTINGS,
511+
),
512+
)
513+
}
514+
}
515+
468516
/**
469517
* Fetches the always or never language setting synchronously from the engine. Will
470518
* return null if an error occurs.

mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/reducer/TranslationsStateReducer.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,17 @@ internal object TranslationsStateReducer {
163163
}
164164
}
165165

166+
TranslationOperation.FETCH_OFFER_SETTING -> {
167+
// Reset the error state, and then generally expect
168+
// [TranslationsAction.SetGlobalOfferTranslateSettingAction] to update state in the
169+
// success case.
170+
state.copyWithTranslationsState(action.tabId) {
171+
it.copy(
172+
settingsError = null,
173+
)
174+
}
175+
}
176+
166177
TranslationOperation.FETCH_AUTOMATIC_LANGUAGE_SETTINGS -> {
167178
state.copy(
168179
translationEngine = state.translationEngine.copy(
@@ -229,6 +240,14 @@ internal object TranslationsStateReducer {
229240
}
230241
}
231242

243+
TranslationOperation.FETCH_OFFER_SETTING -> {
244+
state.copyWithTranslationsState(action.tabId) {
245+
it.copy(
246+
translationError = action.translationError,
247+
)
248+
}
249+
}
250+
232251
TranslationOperation.FETCH_AUTOMATIC_LANGUAGE_SETTINGS -> {
233252
state.copyWithTranslationsState(action.tabId) {
234253
it.copy(
@@ -325,6 +344,14 @@ internal object TranslationsStateReducer {
325344
}
326345
}
327346

347+
TranslationOperation.FETCH_OFFER_SETTING -> {
348+
state.copy(
349+
translationEngine = state.translationEngine.copy(
350+
offerTranslation = null,
351+
),
352+
)
353+
}
354+
328355
TranslationOperation.FETCH_NEVER_TRANSLATE_SITES -> {
329356
state.copy(
330357
translationEngine = state.translationEngine.copy(
@@ -399,6 +426,22 @@ internal object TranslationsStateReducer {
399426
}
400427
}
401428

429+
is TranslationsAction.SetGlobalOfferTranslateSettingAction -> {
430+
state.copy(
431+
translationEngine = state.translationEngine.copy(
432+
offerTranslation = action.offerTranslation,
433+
),
434+
)
435+
}
436+
437+
is TranslationsAction.UpdateGlobalOfferTranslateSettingAction -> {
438+
state.copy(
439+
translationEngine = state.translationEngine.copy(
440+
offerTranslation = action.offerTranslation,
441+
),
442+
)
443+
}
444+
402445
is TranslationsAction.SetEngineSupportedAction -> {
403446
state.copy(
404447
translationEngine = state.translationEngine.copy(

mobile/android/android-components/components/browser/state/src/main/java/mozilla/components/browser/state/state/TranslationsBrowserState.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import mozilla.components.concept.engine.translate.TranslationSupport
1313
* Value type that represents the state of the translations engine within a [BrowserState].
1414
*
1515
* @property isEngineSupported Whether the translations engine supports the device architecture.
16+
* @property offerTranslation Whether to offer translations or not to the user.
1617
* @property supportedLanguages Set of languages the translation engine supports.
1718
* @property languageModels Set of language machine learning translation models the translation engine has available.
1819
* @property languageSettings A map containing a key of BCP 47 language code and its
@@ -23,6 +24,7 @@ import mozilla.components.concept.engine.translate.TranslationSupport
2324
*/
2425
data class TranslationsBrowserState(
2526
val isEngineSupported: Boolean? = null,
27+
val offerTranslation: Boolean? = null,
2628
val supportedLanguages: TranslationSupport? = null,
2729
val languageModels: List<LanguageModel>? = null,
2830
val languageSettings: Map<String, LanguageSetting>? = null,

mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/action/TranslationsActionTest.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,4 +897,36 @@ class TranslationsActionTest {
897897
// Final state
898898
assertEquals(languageModels, store.state.translationEngine.languageModels)
899899
}
900+
901+
@Test
902+
fun `WHEN SetOfferTranslateSettingAction is called then set offerToTranslate`() {
903+
// Initial State
904+
assertNull(store.state.translationEngine.offerTranslation)
905+
906+
// Action started
907+
store.dispatch(
908+
TranslationsAction.SetGlobalOfferTranslateSettingAction(
909+
offerTranslation = false,
910+
),
911+
).joinBlocking()
912+
913+
// Action success
914+
assertFalse(store.state.translationEngine.offerTranslation!!)
915+
}
916+
917+
@Test
918+
fun `WHEN UpdateOfferTranslateSettingAction is called then set offerToTranslate`() {
919+
// Initial State
920+
assertNull(store.state.translationEngine.offerTranslation)
921+
922+
// Action started
923+
store.dispatch(
924+
TranslationsAction.UpdateGlobalOfferTranslateSettingAction(
925+
offerTranslation = false,
926+
),
927+
).joinBlocking()
928+
929+
// Action success
930+
assertFalse(store.state.translationEngine.offerTranslation!!)
931+
}
900932
}

mobile/android/android-components/components/browser/state/src/test/java/mozilla/components/browser/state/engine/middleware/TranslationsMiddlewareTest.kt

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,4 +945,81 @@ class TranslationsMiddlewareTest {
945945

946946
waitForIdle()
947947
}
948+
949+
@Test
950+
fun `WHEN InitTranslationsBrowserState is dispatched AND the engine is supported THEN SetOfferTranslateSettingAction is also dispatched`() = runTest {
951+
// Send Action
952+
translationsMiddleware.invoke(context = context, next = {}, action = TranslationsAction.InitTranslationsBrowserState)
953+
954+
// Set the engine to support
955+
val engineSupportedCallback = argumentCaptor<((Boolean) -> Unit)>()
956+
// At least once, since InitAction also will trigger this
957+
verify(engine, atLeastOnce()).isTranslationsEngineSupported(
958+
onSuccess = engineSupportedCallback.capture(),
959+
onError = any(),
960+
)
961+
engineSupportedCallback.value.invoke(true)
962+
963+
// Verify results for offer
964+
verify(engine, atLeastOnce()).getTranslationsOfferPopup()
965+
waitForIdle()
966+
967+
// Verifying at least once
968+
verify(store, atLeastOnce()).dispatch(
969+
TranslationsAction.SetGlobalOfferTranslateSettingAction(
970+
offerTranslation = false,
971+
),
972+
)
973+
974+
waitForIdle()
975+
}
976+
977+
@Test
978+
fun `WHEN FETCH_OFFER_SETTING is dispatched with a tab id THEN SetOfferTranslateSettingAction and SetPageSettingsAction are also dispatched`() = runTest {
979+
// Set the mock offer value
980+
whenever(
981+
engine.getTranslationsOfferPopup(),
982+
).thenAnswer { true }
983+
984+
// Send Action
985+
val action =
986+
TranslationsAction.OperationRequestedAction(
987+
tabId = tab.id,
988+
operation = TranslationOperation.FETCH_OFFER_SETTING,
989+
)
990+
translationsMiddleware.invoke(context, {}, action)
991+
waitForIdle()
992+
993+
// Verify Dispatch
994+
verify(store, atLeastOnce()).dispatch(
995+
TranslationsAction.SetGlobalOfferTranslateSettingAction(
996+
offerTranslation = true,
997+
),
998+
)
999+
1000+
// Since we had a tabId, this call will also happen
1001+
verify(store, atLeastOnce()).dispatch(
1002+
TranslationsAction.SetPageSettingsAction(
1003+
tabId = tab.id,
1004+
pageSettings = any(),
1005+
),
1006+
)
1007+
1008+
waitForIdle()
1009+
}
1010+
1011+
@Test
1012+
fun `WHEN UpdateOfferTranslateSettingAction is called then setTranslationsOfferPopup is called on the engine`() = runTest {
1013+
// Send Action
1014+
val action =
1015+
TranslationsAction.UpdateGlobalOfferTranslateSettingAction(
1016+
offerTranslation = true,
1017+
)
1018+
translationsMiddleware.invoke(context, {}, action)
1019+
waitForIdle()
1020+
1021+
// Verify offer was set
1022+
verify(engine, atLeastOnce()).setTranslationsOfferPopup(offer = true)
1023+
waitForIdle()
1024+
}
9481025
}

mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/translate/TranslationOperation.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ enum class TranslationOperation {
3535
*/
3636
FETCH_PAGE_SETTINGS,
3737

38+
/**
39+
* Fetch the translations offer setting.
40+
* Note: this request is also encompassed in [FETCH_PAGE_SETTINGS], but intended for checking
41+
* fetching for global settings or when only this setting is needed.
42+
*/
43+
FETCH_OFFER_SETTING,
44+
3845
/**
3946
* Fetch the user preference on whether to offer, always translate, or never translate for
4047
* all supported language settings.

0 commit comments

Comments
 (0)