Bug 1907591 - [translations] Display error messages using moz-message-bar. r=nordzilla,fluent-reviewers,settings-reviewers,desktop-theme-reviewers,translations-reviewers,bolsson,dao,mossop

Differential Revision: https://phabricator.services.mozilla.com/D220870
This commit is contained in:
Meera Murthy 2024-09-25 17:52:38 +00:00
parent 43c53ee1bc
commit 39d6046bd6
4 changed files with 528 additions and 3 deletions

View File

@ -270,6 +270,12 @@ let gTranslationsPane = {
const eventNode = event.target;
const eventNodeParent = eventNode.parentNode;
const eventNodeClassList = eventNode.classList;
for (const err of document.querySelectorAll(
".translations-settings-language-error"
)) {
this.removeError(err);
}
switch (event.type) {
case "command":
if (
@ -838,6 +844,21 @@ let gTranslationsPane = {
}
},
showErrorMessage(parentNode, fluentId, language) {
const errorElement = document.createElement("moz-message-bar");
errorElement.setAttribute("type", "error");
errorElement.setAttribute("data-l10n-attrs", "message");
document.l10n.setAttributes(errorElement, fluentId, {
name: language,
});
errorElement.classList.add("translations-settings-language-error");
parentNode.appendChild(errorElement);
},
removeError(errorNode) {
errorNode?.remove();
},
/**
* Event Handler to download a language model selected by the user through HTML
* @param {Event} event
@ -859,6 +880,11 @@ let gTranslationsPane = {
} catch (error) {
console.error(error);
this.showErrorMessage(
eventButton.parentNode,
"translations-settings-language-download-error",
TranslationsParent.getLanguageDisplayName(langTag)
);
const hasAllFilesForLanguage =
await TranslationsParent.hasAllFilesForLanguage(langTag);
@ -917,6 +943,11 @@ let gTranslationsPane = {
} catch (error) {
// The download phases are invalidated with the error and must be reloaded.
console.error(error);
this.showErrorMessage(
eventButton.parentNode,
"translations-settings-language-remove-error",
TranslationsParent.getLanguageDisplayName(langTag)
);
const hasAllFilesForLanguage =
await TranslationsParent.hasAllFilesForLanguage(langTag);
if (hasAllFilesForLanguage) {
@ -967,6 +998,11 @@ let gTranslationsPane = {
} catch (error) {
console.error(error);
await this.reloadDownloadPhases();
this.showErrorMessage(
eventButton.parentNode,
"translations-settings-language-download-error",
"all"
);
return;
}
this.changeButtonState({
@ -995,6 +1031,11 @@ let gTranslationsPane = {
} catch (error) {
console.error(error);
await this.reloadDownloadPhases();
this.showErrorMessage(
eventButton.parentNode,
"translations-settings-language-remove-error",
"all"
);
return;
}
this.changeButtonState({

View File

@ -487,6 +487,481 @@ add_task(async function test_translations_settings_download_languages() {
await cleanup();
});
add_task(
async function test_translations_settings_download_languages_error_handling() {
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 spainishModels = [
"lex.50.50.enes.s2t.bin",
"lex.50.50.esen.s2t.bin",
"model.enes.intgemm.alphas.bin",
"model.esen.intgemm.alphas.bin",
"vocab.enes.spm",
"vocab.esen.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 },
});
const { translateDownloadLanguagesList } =
await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane(
settingsButton
);
let langList = translateDownloadLanguagesList.querySelector(
".translations-settings-language-list"
);
info("Test French language model for download error");
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;
await captureTranslationsError(() =>
remoteClients.translationModels.rejectPendingDownloads(
frenchModels.length
)
);
const errorElement = gBrowser.selectedBrowser.contentDocument.querySelector(
".translations-settings-language-error"
);
assertVisibility({
message: "Moz-message-bar with error message is visible",
visible: { errorElement },
});
is(
document.l10n.getAttributes(errorElement).id,
"translations-settings-language-download-error",
"Error message correctly shows download error"
);
is(
document.l10n.getAttributes(errorElement).args.name,
"French",
"Error message correctly shows download error for French language"
);
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"
);
remoteClients.translationsWasm.assertNoNewDownloads();
info("Download Spanish language model successfully.");
let langEs = Array.from(langList.querySelectorAll("label")).find(
el => el.getAttribute("value") === "es"
);
clickButton = BrowserTestUtils.waitForEvent(
langEs.parentNode.querySelector("moz-button"),
"click"
);
langEs.parentNode.querySelector("moz-button").click();
await clickButton;
const errorElementEs =
gBrowser.selectedBrowser.contentDocument.querySelector(
".translations-settings-language-error"
);
ok(
!errorElementEs,
"Previous error is remove when new action occured, i.e. click download Spanish button"
);
Assert.deepEqual(
await remoteClients.translationModels.resolvePendingDownloads(
spainishModels.length
),
spainishModels,
"Spanish models were downloaded."
);
if (
!langEs.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-remove-icon")
) {
await BrowserTestUtils.waitForMutationCondition(
langEs.parentNode.querySelector("moz-button"),
{ attributes: true, attributeFilter: ["class"] },
() =>
langEs.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-remove-icon")
);
}
ok(
langEs.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-remove-icon"),
"Delete icon is visible for Spanish language hence downloaded"
);
info("Test All language models download error");
// 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;
await captureTranslationsError(() =>
remoteClients.translationModels.rejectPendingDownloads(allModels.length)
);
await captureTranslationsError(() =>
remoteClients.translationsWasm.rejectPendingDownloads(allModels.length)
);
remoteClients.translationsWasm.assertNoNewDownloads();
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")
);
}
langAll.querySelector("moz-button").classList;
ok(
langAll
.querySelector("moz-button")
.classList.contains("translations-settings-download-icon"),
"Download icon is visible for 'all languages'"
);
const errorElementAll =
gBrowser.selectedBrowser.contentDocument.querySelector(
".translations-settings-language-error"
);
assertVisibility({
message: "Moz-message-bar with error message is visible",
visible: { errorElementAll },
});
is(
document.l10n.getAttributes(errorElementAll).id,
"translations-settings-language-download-error",
"Error message correctly shows download error"
);
is(
document.l10n.getAttributes(errorElementAll).args.name,
"all",
"Error message correctly shows download error for all language"
);
await cleanup();
}
);
add_task(async function test_translations_settings_download_languages_all() {
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 spainishModels = [
"lex.50.50.enes.s2t.bin",
"lex.50.50.esen.s2t.bin",
"model.enes.intgemm.alphas.bin",
"model.esen.intgemm.alphas.bin",
"vocab.enes.spm",
"vocab.esen.spm",
];
const ukrainianModels = [
"lex.50.50.enuk.s2t.bin",
"lex.50.50.uken.s2t.bin",
"model.enuk.intgemm.alphas.bin",
"model.uken.intgemm.alphas.bin",
"vocab.enuk.spm",
"vocab.uken.spm",
];
assertVisibility({
message: "Expect paneGeneral elements to be visible.",
visible: { settingsButton },
});
const { translateDownloadLanguagesList } =
await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane(
settingsButton
);
let langList = translateDownloadLanguagesList.querySelector(
".translations-settings-language-list"
);
info(
"Install each language French, Spanish and Ukrainian and check if All language state changes to 'all language downloaded' by changing the all language button icon to 'remove icon'"
);
info("Download French language model.");
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-remove-icon")
) {
await BrowserTestUtils.waitForMutationCondition(
langFr.parentNode.querySelector("moz-button"),
{ attributes: true, attributeFilter: ["class"] },
() =>
langFr.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-remove-icon")
);
}
ok(
langFr.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-remove-icon"),
"Delete icon is visible for French language hence downloaded"
);
info("Download Spanish language model.");
let langEs = Array.from(langList.querySelectorAll("label")).find(
el => el.getAttribute("value") === "es"
);
clickButton = BrowserTestUtils.waitForEvent(
langEs.parentNode.querySelector("moz-button"),
"click"
);
langEs.parentNode.querySelector("moz-button").click();
await clickButton;
Assert.deepEqual(
await remoteClients.translationModels.resolvePendingDownloads(
spainishModels.length
),
spainishModels,
"Spanish models were downloaded."
);
if (
!langEs.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-remove-icon")
) {
await BrowserTestUtils.waitForMutationCondition(
langEs.parentNode.querySelector("moz-button"),
{ attributes: true, attributeFilter: ["class"] },
() =>
langEs.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-remove-icon")
);
}
ok(
langEs.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-remove-icon"),
"Delete icon is visible for Spanish language hence downloaded"
);
info("Download Ukrainian language model.");
let langUk = Array.from(langList.querySelectorAll("label")).find(
el => el.getAttribute("value") === "uk"
);
clickButton = BrowserTestUtils.waitForEvent(
langUk.parentNode.querySelector("moz-button"),
"click"
);
langUk.parentNode.querySelector("moz-button").click();
await clickButton;
Assert.deepEqual(
await remoteClients.translationModels.resolvePendingDownloads(
ukrainianModels.length
),
ukrainianModels,
"Ukrainian models were downloaded."
);
if (
!langUk.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-remove-icon")
) {
await BrowserTestUtils.waitForMutationCondition(
langUk.parentNode.querySelector("moz-button"),
{ attributes: true, attributeFilter: ["class"] },
() =>
langUk.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-remove-icon")
);
}
ok(
langUk.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-remove-icon"),
"Delete icon is visible for Ukranian language hence downloaded."
);
// Download "All languages" is the first child
let langAll = langList.children[0];
ok(
langAll
.querySelector("moz-button")
.classList.contains("translations-settings-remove-icon"),
"Delete icon is visible for All Languages after all individual language models were downloaded."
);
info(
"Remove one language ensure that All Languages change state changes to 'removed' to indicate that all languages are not downloaded."
);
info("Remove Spanish language model.");
langEs.parentNode.querySelector("moz-button").click();
await clickButton;
if (
!langEs.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-download-icon")
) {
await BrowserTestUtils.waitForMutationCondition(
langEs.parentNode.querySelector("moz-button"),
{ attributes: true, attributeFilter: ["class"] },
() =>
langEs.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-download-icon")
);
}
ok(
langEs.parentNode
.querySelector("moz-button")
.classList.contains("translations-settings-download-icon"),
"Download icon is visible for Spanish language hence removed"
);
ok(
langAll
.querySelector("moz-button")
.classList.contains("translations-settings-download-icon"),
"Download icon is visible for all languages i.e. all languages are not downloaded since one language, Spainish was removed."
);
await cleanup();
});
const { PermissionTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/PermissionTestUtils.sys.mjs"
);

View File

@ -25,12 +25,19 @@ translations-settings-download-languages-link = Learn more about downloading lan
# $size (number) - The size of the download in megabites
translations-settings-download-size = ({ $size })
translations-settings-language-header = Language
# Variables:
# $name (string) - The language to be downloaded
translations-settings-language-download-error =
.heading = Download Error
.message = Language download failed. Try again.
.message = Could not download { $name } language. Please try again.
# Variables:
# $name (string) - The language to be downloaded
translations-settings-language-remove-error =
.heading = Remove Error
.message = Failed to remove language. Try again.
.message = Could not remove { $name } language. Please try again.
# Variables:
# $name (string) - The display name of the language that is to be downloaded
translations-settings-download-button =

View File

@ -49,6 +49,7 @@
.translations-settings-language {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: var(--space-small) 0;
border-top: 1px solid var(--in-content-border-color);
@ -57,7 +58,8 @@
}
}
.translations-settings-language-error {
flex: 100%;
display: inline-block;
flex: 0 1 100%;
}
.translations-settings-download-icon[type~="icon"]::part(button) {
background-image: url(chrome://browser/skin/downloads/downloads.svg);