Bug 1881259 - [translations] Ensure Secondary Translation Settings interacts with Preferences. r=nordzilla,fluent-reviewers,settings-reviewers,desktop-theme-reviewers,translations-reviewers,bolsson,dao,Gijs

Differential Revision: https://phabricator.services.mozilla.com/D203097
This commit is contained in:
Meera Murthy 2024-07-19 15:11:22 +00:00
parent 63168f5158
commit 1fcecb153b
7 changed files with 1463 additions and 258 deletions

View File

@ -68,7 +68,7 @@
<h3 class="translations-settings-language-header" data-l10n-id="translations-settings-language-header"></h3>
<div class="translations-settings-language-list">
<div class="translations-settings-language">
<moz-button class="translations-settings-download-icon" type="ghost icon"
<moz-button class="translations-settings-download-icon translations-settings-manage-downloaded-language-button" type="ghost icon"
aria-label="translations-settings-download-all-languages"></moz-button>
<!-- The option to "All languages" is added here.
In translations.js the option to download individual languages is

File diff suppressed because it is too large Load Diff

View File

@ -53,7 +53,7 @@ add_task(async function test_translations_settings_pane_elements() {
},
});
const promise = BrowserTestUtils.waitForEvent(
const paneEvent = BrowserTestUtils.waitForEvent(
document,
"paneshown",
false,
@ -61,7 +61,7 @@ add_task(async function test_translations_settings_pane_elements() {
);
click(backButton);
await promise;
await paneEvent;
assertVisibility({
message: "Expect paneGeneral elements to be visible.",
@ -104,19 +104,56 @@ add_task(async function test_translations_settings_always_translate() {
await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane(
settingsButton
);
let alwaysTranslateSection = document.getElementById(
"translations-settings-always-translate-section"
);
await testLanguageList(alwaysTranslateSection, translateAlwaysMenuList);
await testLanguageListWithPref(alwaysTranslateSection);
await cleanup();
});
add_task(async function test_translations_settings_never_translate() {
const {
cleanup,
elements: { settingsButton },
} = await setupAboutPreferences(LANGUAGE_PAIRS, {
prefs: [["browser.translations.newSettingsUI.enable", true]],
});
const document = gBrowser.selectedBrowser.contentDocument;
assertVisibility({
message: "Expect paneGeneral elements to be visible.",
visible: { settingsButton },
});
const { translateNeverMenuList } =
await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane(
settingsButton
);
let neverTranslateSection = document.getElementById(
"translations-settings-never-translate-section"
);
await testLanguageList(neverTranslateSection, translateNeverMenuList);
await testLanguageListWithPref(neverTranslateSection);
await cleanup();
});
function getLangsFromPref(pref) {
let rawLangs = Services.prefs.getCharPref(pref);
if (!rawLangs) {
return [];
}
let langArr = rawLangs.split(",");
return langArr;
}
async function testLanguageList(translateSection, menuList) {
const sectionName =
const { sectionName, pref } =
translateSection.id === "translations-settings-always-translate-section"
? "Always"
: "Never";
? { sectionName: "Always", pref: ALWAYS_TRANSLATE_LANGS_PREF }
: { sectionName: "Never", pref: NEVER_TRANSLATE_LANGS_PREF };
is(
translateSection.querySelector(".translations-settings-languages-card"),
@ -124,25 +161,31 @@ async function testLanguageList(translateSection, menuList) {
`Language list not present in ${sectionName} Translate list`
);
for (let i = 0; i < menuList.children[0].children.length; i++) {
menuList.value = menuList.children[0].children[i].value;
let clickMenu = BrowserTestUtils.waitForEvent(menuList, "command");
menuList.dispatchEvent(new Event("command"));
const menuOptions = menuList.querySelector("menupopup").children;
for (const option of menuOptions) {
let clickMenu = BrowserTestUtils.waitForEvent(option, "command");
option.doCommand();
await clickMenu;
/** Languages are always added on the top, so check the firstChild
* for newly added languages.
* the firstChild.lastChild.innerText is the language display name
* the firstChild.querySelector("label").innerText is the language display name
* which is compared with the menulist display name that is selected
*/
let langElem = translateSection.querySelector(
".translations-settings-language-list"
).firstChild;
const displayName = getIntlDisplayName(option.value);
is(
translateSection.querySelector(".translations-settings-language-list")
.firstChild.lastChild.innerText,
getIntlDisplayName(menuList.children[0].children[i].value),
`Language list has element ${getIntlDisplayName(
menuList.children[0].children[i].value
)}`
langElem.querySelector("label").innerText,
displayName,
`Language list has element ${displayName}`
);
const langTag = langElem.querySelector("label").getAttribute("value");
ok(
getLangsFromPref(pref).includes(langTag),
`Perferences contains ${langTag}`
);
}
/** The test cases has 4 languages, so check if 4 languages are added to the list */
@ -157,13 +200,19 @@ async function testLanguageList(translateSection, menuList) {
for (let i = 0; i < langNum; i++) {
// Delete the first language in the list
let langName = languagelist.children[0].lastChild.innerText;
let langButton = languagelist.children[0].querySelector("moz-button");
let langElem = languagelist.children[0];
let langName = langElem.querySelector("label").innerText;
const langTag = langElem.querySelector("label").getAttribute("value");
let langButton = langElem.querySelector("moz-button");
let clickButton = BrowserTestUtils.waitForEvent(langButton, "click");
langButton.click();
await clickButton;
ok(
!getLangsFromPref(pref).includes(langTag),
`Perferences does not contain ${langTag}`
);
if (i < langNum - 1) {
is(
languagelist.childElementCount,
@ -181,40 +230,109 @@ async function testLanguageList(translateSection, menuList) {
}
}
add_task(async function test_translations_settings_never_translate() {
const {
cleanup,
elements: { settingsButton },
} = await setupAboutPreferences(LANGUAGE_PAIRS, {
prefs: [["browser.translations.newSettingsUI.enable", true]],
});
async function testLanguageListWithPref(translateSection) {
const langs = [
"fr",
"de",
"en",
"es",
"fr,de",
"fr,en",
"fr,es",
"de,en",
"de,en,es",
"es,fr,en",
"en,es,fr,de",
];
const { sectionName, pref } =
translateSection.id === "translations-settings-always-translate-section"
? { sectionName: "Always", pref: ALWAYS_TRANSLATE_LANGS_PREF }
: { sectionName: "Never", pref: NEVER_TRANSLATE_LANGS_PREF };
const document = gBrowser.selectedBrowser.contentDocument;
assertVisibility({
message: "Expect paneGeneral elements to be visible.",
visible: { settingsButton },
});
const { translateNeverMenuList } =
await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane(
settingsButton
);
let neverTranslateSection = document.getElementById(
"translations-settings-never-translate-section"
is(
translateSection.querySelector(".translations-settings-languages-card"),
null,
`Language list not present in ${sectionName} Translate list`
);
await testLanguageList(neverTranslateSection, translateNeverMenuList);
await cleanup();
});
for (const langOptions of langs) {
Services.prefs.setCharPref(pref, langOptions);
/** Languages are always added on the top, so check the firstChild
* for newly added languages.
* the firstChild.querySelector("label").innerText is the language display name
* which is compared with the menulist display name that is selected
*/
const langsAdded = langOptions.split(",");
is(
translateSection.querySelector(".translations-settings-language-list")
.childElementCount,
langsAdded.length,
`Language list has ${langsAdded.length} elements `
);
let langsAddedHtml = Array.from(
translateSection
.querySelector(".translations-settings-language-list")
.querySelectorAll("label")
);
for (const lang of langsAdded) {
const langFind = langsAddedHtml
.find(el => el.getAttribute("value") === lang)
.getAttribute("value");
is(langFind, lang, `Language list has element ${lang}`);
}
}
Services.prefs.setCharPref(pref, "");
is(
translateSection.querySelector(".translations-settings-languages-card"),
null,
`All removed from ${sectionName} Translate`
);
}
add_task(async function test_translations_settings_download_languages() {
const {
cleanup,
remoteClients,
elements: { settingsButton },
} = await setupAboutPreferences(LANGUAGE_PAIRS, {
prefs: [["browser.translations.newSettingsUI.enable", true]],
});
const frenchModels = [
"lex.50.50.enfr.s2t.bin",
"lex.50.50.fren.s2t.bin",
"model.enfr.intgemm.alphas.bin",
"model.fren.intgemm.alphas.bin",
"vocab.enfr.spm",
"vocab.fren.spm",
];
const allModels = [
"lex.50.50.enes.s2t.bin",
"lex.50.50.enfr.s2t.bin",
"lex.50.50.enuk.s2t.bin",
"lex.50.50.esen.s2t.bin",
"lex.50.50.fren.s2t.bin",
"lex.50.50.uken.s2t.bin",
"model.enes.intgemm.alphas.bin",
"model.enfr.intgemm.alphas.bin",
"model.enuk.intgemm.alphas.bin",
"model.esen.intgemm.alphas.bin",
"model.fren.intgemm.alphas.bin",
"model.uken.intgemm.alphas.bin",
"vocab.enes.spm",
"vocab.enfr.spm",
"vocab.enuk.spm",
"vocab.esen.spm",
"vocab.fren.spm",
"vocab.uken.spm",
];
assertVisibility({
message: "Expect paneGeneral elements to be visible.",
visible: { settingsButton },
@ -229,43 +347,283 @@ add_task(async function test_translations_settings_download_languages() {
".translations-settings-language-list"
);
for (let i = 0; i < langList.children.length; i++) {
// Test French language model install and uninstall function
let langFr = Array.from(langList.querySelectorAll("label")).find(
el => el.getAttribute("value") === "fr"
);
let clickButton = BrowserTestUtils.waitForEvent(
langFr.parentNode.querySelector("moz-button"),
"click"
);
langFr.parentNode.querySelector("moz-button").click();
await clickButton;
Assert.deepEqual(
await remoteClients.translationModels.resolvePendingDownloads(
frenchModels.length
),
frenchModels,
"French models were downloaded."
);
if (
!langFr.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-delete-icon")
) {
await BrowserTestUtils.waitForMutationCondition(
langFr.parentNode.querySelector("moz-button"),
{ attributes: true, attributeFilter: ["class"] },
() =>
langFr.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-delete-icon")
);
}
ok(
langFr.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-delete-icon"),
"Delete icon is visible"
);
langFr.parentNode.querySelector("moz-button").click();
await clickButton;
if (
!langFr.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-download-icon")
) {
await BrowserTestUtils.waitForMutationCondition(
langFr.parentNode.querySelector("moz-button"),
{ attributes: true, attributeFilter: ["class"] },
() =>
langFr.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-download-icon")
);
}
ok(
langFr.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-download-icon"),
"Download icon is visible"
);
// Test "All language" models install and uninstall function
// Download "All languages" is the first child
let langAll = langList.children[0];
let clickButtonAll = BrowserTestUtils.waitForEvent(
langAll.querySelector("moz-button"),
"click"
);
langAll.querySelector("moz-button").click();
await clickButtonAll;
Assert.deepEqual(
await remoteClients.translationModels.resolvePendingDownloads(
allModels.length
),
allModels,
"All models were downloaded."
);
Assert.deepEqual(
await remoteClients.translationsWasm.resolvePendingDownloads(1),
["bergamot-translator"],
"Wasm was downloaded."
);
if (
!langAll
.querySelector("moz-button")
.classList.contains("translations-settings-delete-icon")
) {
await BrowserTestUtils.waitForMutationCondition(
langAll.querySelector("moz-button"),
{ attributes: true, attributeFilter: ["class"] },
() =>
langAll
.querySelector("moz-button")
.classList.contains("translations-settings-delete-icon")
);
}
ok(
langAll
.querySelector("moz-button")
.classList.contains("translations-settings-delete-icon"),
"Delete icon is visible"
);
langAll.querySelector("moz-button").click();
await clickButton;
if (
!langAll
.querySelector("moz-button")
.classList.contains("translations-settings-download-icon")
) {
await BrowserTestUtils.waitForMutationCondition(
langAll.querySelector("moz-button"),
{ attributes: true, attributeFilter: ["class"] },
() =>
langAll
.querySelector("moz-button")
.classList.contains("translations-settings-download-icon")
);
}
ok(
langAll
.querySelector("moz-button")
.classList.contains("translations-settings-download-icon"),
"Download icon is visible"
);
await cleanup();
});
const { PermissionTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/PermissionTestUtils.sys.mjs"
);
add_task(async function test_translations_settings_never_translate_site() {
const {
cleanup,
elements: { settingsButton },
} = await setupAboutPreferences(LANGUAGE_PAIRS, {
prefs: [["browser.translations.newSettingsUI.enable", true]],
});
const document = gBrowser.selectedBrowser.contentDocument;
// const openTranslationsSettings =
await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane(
settingsButton
);
let neverTranslateSitesSection = document.getElementById(
"translations-settings-never-sites-section"
);
let siteList = neverTranslateSitesSection.querySelector(
".translations-settings-language-list"
);
info("Ensuring the list of never-translate sites is empty");
is(
getNeverTranslateSitesFromPerms().length,
0,
"The list of never-translate sites is empty"
);
is(siteList, null, "The never-translate sites html list is empty");
info("Adding sites to the neverTranslateSites perms");
await PermissionTestUtils.add(
"https://example.com",
TRANSLATIONS_PERMISSION,
Services.perms.DENY_ACTION
);
await PermissionTestUtils.add(
"https://example.org",
TRANSLATIONS_PERMISSION,
Services.perms.DENY_ACTION
);
await PermissionTestUtils.add(
"https://example.net",
TRANSLATIONS_PERMISSION,
Services.perms.DENY_ACTION
);
is(
getNeverTranslateSitesFromPerms().length,
3,
"The list of never-translate sites has 3 elements"
);
siteList = neverTranslateSitesSection.querySelector(
".translations-settings-language-list"
);
is(
siteList.childElementCount,
3,
"The never-translate sites html list has 3 elements"
);
const permissionsUrls = [
"https://example.com",
"https://example.org",
"https://example.net",
];
const siteNum = siteList.children.length;
for (let i = siteNum; i > 0; i--) {
is(
langList.children[i]
.querySelector("moz-button")
.classList.contains("translations-settings-download-icon"),
true,
"Download icon is visible"
siteList.children[i - 1].querySelector("label").textContent,
permissionsUrls[permissionsUrls.length - i],
`Never translate URL ${
permissionsUrls[permissionsUrls.length - i]
} is added`
);
}
for (let i = 0; i < siteNum; i++) {
// Delete the first site in the list
let siteElem = siteList.children[0];
// Delete the first language in the list
let siteName = siteElem.querySelector("label").innerText;
let siteButton = siteElem.querySelector("moz-button");
ok(
siteList.querySelector(`label[value="${siteName}"]`),
`Site ${siteName} present in the Never transalate site list`
);
let clickButton = BrowserTestUtils.waitForEvent(
langList.children[i].querySelector("moz-button"),
"click"
ok(
getNeverTranslateSitesFromPerms().find(p => p.origin === siteName),
`Site ${siteName} present in the Never transalate site permissions list`
);
langList.children[i].querySelector("moz-button").click();
let clickButton = BrowserTestUtils.waitForEvent(siteButton, "click");
siteButton.click();
await clickButton;
is(
langList.children[i]
.querySelector("moz-button")
.classList.contains("translations-settings-delete-icon"),
true,
"Delete icon is visible"
ok(
!siteList.querySelector(`label[value="${siteName}"]`),
`Site ${siteName} removed successfully from the Never transalate site list`
);
clickButton = BrowserTestUtils.waitForEvent(
langList.children[i].querySelector("moz-button"),
"click"
ok(
!getNeverTranslateSitesFromPerms().find(p => p.origin === siteName),
`Site ${siteName} removed from successfully from the Never transalate site permissions list`
);
langList.children[i].querySelector("moz-button").click();
await clickButton;
if (i < siteNum - 1) {
is(
siteList.childElementCount,
siteNum - i - 1,
`${siteName} removed from Never Translate Site`
);
} else {
/** Check if the language list card is removed after removing the last language */
is(
neverTranslateSitesSection.querySelector(
".translations-settings-languages-card"
),
null,
`${siteName} removed from Never Translate Site`
);
}
const siteLen = siteNum - i - 1;
is(
langList.children[i]
.querySelector("moz-button")
.classList.contains("translations-settings-download-icon"),
true,
"Download icon is visible"
getNeverTranslateSitesFromPerms().length,
siteLen,
`There are ${siteLen} site in Never translate site`
);
}
await cleanup();

View File

@ -21,4 +21,13 @@ translations-settings-never-sites-description = To add to this list, visit a sit
translations-settings-download-languages = Download languages
translations-settings-download-all-languages = All languages
translations-settings-download-languages-link = Learn more about downloading languages
# Variables:
# $size (number) - The size of the download in megabites
translations-settings-download-size = ({ $size })
translations-settings-language-header = Language
translations-settings-language-download-error =
.heading = Download Error
.message = Language download failed. Try again.
translations-settings-language-remove-error =
.heading = Remove Error
.message = Failed to remove language. Try again.

View File

@ -34,7 +34,6 @@
display: flex;
flex-direction: column;
max-height: calc( 7 * var(--space-xlarge));
overflow: auto;
padding-inline: calc( 2 * var(--space-small));
}
@ -44,6 +43,10 @@
font-weight: var(--font-weight-bold);
}
.translations-settings-language-list {
overflow: auto;
}
.translations-settings-language {
display: flex;
align-items: center;
@ -53,7 +56,9 @@
margin: 0px calc( 2 * var(--space-small));
}
}
.translations-settings-language-error {
flex: 100%;
}
.translations-settings-download-icon[type~="icon"]::part(button) {
background-image: url(chrome://browser/skin/downloads/downloads.svg);
}
@ -61,3 +66,11 @@
.translations-settings-delete-icon[type~="icon"]::part(button) {
background-image: url(chrome://global/skin/icons/delete.svg);
}
.translations-settings-loading-icon[type~="icon"]::part(button) {
background-image: url(chrome://global/skin/icons/loading.svg);
}
.translations-settings-download-size {
color: var(--color-gray-60);
}

View File

@ -100,7 +100,11 @@ XPCOMUtils.defineLazyPreferenceGetter(
"alwaysTranslateLangTags",
ALWAYS_TRANSLATE_LANGS_PREF,
/* aDefaultPrefValue */ "",
/* onUpdate */ null,
/* onUpdate */ () =>
Services.obs.notifyObservers(
null,
"translations:always-translate-languages-changed"
),
/* aTransform */ rawLangTags =>
rawLangTags ? new Set(rawLangTags.split(",")) : new Set()
);
@ -113,7 +117,11 @@ XPCOMUtils.defineLazyPreferenceGetter(
"neverTranslateLangTags",
NEVER_TRANSLATE_LANGS_PREF,
/* aDefaultPrefValue */ "",
/* onUpdate */ null,
/* onUpdate */ () =>
Services.obs.notifyObservers(
null,
"translations:never-translate-languages-changed"
),
/* aTransform */ rawLangTags =>
rawLangTags ? new Set(rawLangTags.split(",")) : new Set()
);
@ -1155,6 +1163,21 @@ export class TranslationsParent extends JSWindowActorParent {
.sort((a, b) => a.displayName.localeCompare(b.displayName));
}
/**
* Get the display name for the given language Tag.
*
* @param {string} langTag
* @returns {string}
*/
static getLanguageDisplayName(langTag) {
// Services.intl.getLanguageDisplayNames takes a list of language codes and
// returns a list of correspoding display names. Hence the langTag is sent as a list
let displayName = Services.intl.getLanguageDisplayNames(undefined, [
langTag,
]);
return displayName[0];
}
/**
* @param {object} event
* @param {object} event.data
@ -2063,6 +2086,23 @@ export class TranslationsParent extends JSWindowActorParent {
return results;
}
static async getLanguageSize(language) {
const records = [
...(await TranslationsParent.#getTranslationModelRecords()).values(),
];
let downloadSize = 0;
await Promise.all(
records.map(async record => {
if (record.fromLang !== language && record.toLang !== language) {
return;
}
downloadSize += parseInt(record.attachment.size);
})
);
return downloadSize;
}
/**
* Gets the expected download size that will occur (if any) if translate is called on two given languages for display purposes.
*
@ -2713,6 +2753,14 @@ export class TranslationsParent extends JSWindowActorParent {
return true;
}
static getAlwaysTranslateLanguages() {
return lazy.alwaysTranslateLangTags;
}
static getNeverTranslateLanguages() {
return lazy.neverTranslateLangTags;
}
/**
* Toggle the automatically popup pref, which will either
* enable or disable translations being offered to the user.

View File

@ -1215,6 +1215,14 @@ async function setupAboutPreferences(
true // waitForLoad
);
let initTranslationsEvent;
if (Services.prefs.getBoolPref("browser.translations.newSettingsUI.enable")) {
initTranslationsEvent = BrowserTestUtils.waitForEvent(
document,
"translationsSettingsInit"
);
}
const { remoteClients, removeMocks } = await createAndMockRemoteSettings({
languagePairs,
});
@ -1227,6 +1235,10 @@ async function setupAboutPreferences(
const elements = await selectAboutPreferencesElements();
if (Services.prefs.getBoolPref("browser.translations.newSettingsUI.enable")) {
await initTranslationsEvent;
}
async function cleanup() {
await closeAllOpenPanelsAndMenus();
await loadBlankPage();