Skip to content

Commit

Permalink
Bug 1820259 - Add strategy for matching records for a language; r=nor…
Browse files Browse the repository at this point in the history
…dzilla

Differential Revision: https://phabricator.services.mozilla.com/D176187
  • Loading branch information
gregtatum committed Apr 26, 2023
1 parent 564a4aa commit 6edf4fd
Show file tree
Hide file tree
Showing 2 changed files with 232 additions and 12 deletions.
86 changes: 74 additions & 12 deletions toolkit/components/translations/actors/TranslationsParent.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -433,23 +433,19 @@ export class TranslationsParent extends JSWindowActorParent {
if (TranslationsParent.#mockedLanguagePairs) {
return TranslationsParent.#mockedLanguagePairs;
}

const records = await this.#getTranslationModelRecords();
const languagePairKeys = new Set();
const languagePairMap = new Map();

for (const { fromLang, toLang, version } of records.values()) {
const isBeta = Services.vc.compare(version, "1.0") < 0;
languagePairKeys.add({ key: fromLang + toLang, isBeta });
}

const languagePairs = [];
for (const { key, isBeta } of languagePairKeys) {
languagePairs.push({
fromLang: key[0] + key[1],
toLang: key[2] + key[3],
isBeta,
});
const key = `${fromLang},${toLang}`;
if (!languagePairMap.has(key)) {
languagePairMap.set(key, { fromLang, toLang, isBeta });
}
}

return languagePairs;
return Array.from(languagePairMap.values());
}

/**
Expand Down Expand Up @@ -837,6 +833,72 @@ export class TranslationsParent extends JSWindowActorParent {
return buffer;
}

/**
* Get the necessary files for translating to and from the app language and a
* requested language. This may require the files for a pivot language translation
* if there is no language model for a direct translation.
*
* @param {string} requestedLanguage The BCP 47 language tag.
* @param {boolean} isForDeletion - Return a more restrictive set of languages, as
* these files are marked for deletion. We don't want to remove
* files that are needed for some other language's pivot translation.
* @returns {Set<TranslationModelRecord>}
*/
async getRecordsForTranslatingToAndFromAppLanguage(
requestedLanguage,
isForDeletion = false
) {
const records = await this.#getTranslationModelRecords();
const appLanguage = new Intl.Locale(Services.locale.appLocaleAsBCP47)
.language;

let matchedRecords = new Set();

if (requestedLanguage === appLanguage) {
// There are no records if the requested language and app language are the same.
return matchedRecords;
}

const addLanguagePair = (fromLang, toLang) => {
let matchFound = false;
for (const record of records.values()) {
if (record.fromLang === fromLang && record.toLang === toLang) {
matchedRecords.add(record);
matchFound = true;
}
}
return matchFound;
};

if (
// Is there a direct translation?
!addLanguagePair(requestedLanguage, appLanguage)
) {
// This is no direct translation, get the pivot files.
addLanguagePair(requestedLanguage, PIVOT_LANGUAGE);
// These files may be required for other pivot translations, so don't remove
// them if we are deleting records.
if (!isForDeletion) {
addLanguagePair(PIVOT_LANGUAGE, appLanguage);
}
}

if (
// Is there a direct translation?
!addLanguagePair(appLanguage, requestedLanguage)
) {
// This is no direct translation, get the pivot files.
addLanguagePair(PIVOT_LANGUAGE, requestedLanguage);
// These files may be required for other pivot translations, so don't remove
// them if we are deleting records.
if (!isForDeletion) {
addLanguagePair(appLanguage, PIVOT_LANGUAGE);
}
}

return matchedRecords;
}

/**
* Gets the language model files in an array buffer by downloading attachments from
* Remote Settings, or retrieving them from the local cache. Each translation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,161 @@ add_task(async function test_pivot_language_behavior() {

return cleanup();
});

async function usingAppLocale(locale, callback) {
info(`Mocking the locale "${locale}", expect missing resource errors.`);
const { availableLocales, requestedLocales } = Services.locale;
Services.locale.availableLocales = [locale];
Services.locale.requestedLocales = [locale];

if (Services.locale.appLocaleAsBCP47 !== locale) {
throw new Error("Unable to change the app locale.");
}
await callback();

// Reset back to the originals.
Services.locale.availableLocales = availableLocales;
Services.locale.requestedLocales = requestedLocales;
}

add_task(async function test_translating_to_and_from_app_language() {
const PIVOT_LANGUAGE = "en";

const { actor, cleanup } = await setupActorTest({
languagePairs: [
{ fromLang: PIVOT_LANGUAGE, toLang: "es" },
{ fromLang: "es", toLang: PIVOT_LANGUAGE },
{ fromLang: PIVOT_LANGUAGE, toLang: "fr" },
{ fromLang: "fr", toLang: PIVOT_LANGUAGE },
{ fromLang: PIVOT_LANGUAGE, toLang: "pl" },
{ fromLang: "pl", toLang: PIVOT_LANGUAGE },
],
});

/**
* Each language pair has multiple models. De-duplicate the language pairs and
* return a sorted list.
*/
function getUniqueLanguagePairs(records) {
const langPairs = new Set();
for (const { fromLang, toLang } of records) {
langPairs.add(fromLang + toLang);
}
return Array.from(langPairs)
.sort()
.map(langPair => ({
fromLang: langPair[0] + langPair[1],
toLang: langPair[2] + langPair[3],
}));
}

function assertLanguagePairs({
app,
requested,
message,
languagePairs,
isForDeletion,
}) {
return usingAppLocale(app, async () => {
Assert.deepEqual(
getUniqueLanguagePairs(
await actor.getRecordsForTranslatingToAndFromAppLanguage(
requested,
isForDeletion
)
),
languagePairs,
message
);
});
}

await assertLanguagePairs({
message:
"When the app locale is the pivot language, download another language.",
app: PIVOT_LANGUAGE,
requested: "fr",
languagePairs: [
{ fromLang: PIVOT_LANGUAGE, toLang: "fr" },
{ fromLang: "fr", toLang: PIVOT_LANGUAGE },
],
});

await assertLanguagePairs({
message: "When a pivot language is required, they are both downloaded.",
app: "fr",
requested: "pl",
languagePairs: [
{ fromLang: PIVOT_LANGUAGE, toLang: "fr" },
{ fromLang: PIVOT_LANGUAGE, toLang: "pl" },
{ fromLang: "fr", toLang: PIVOT_LANGUAGE },
{ fromLang: "pl", toLang: PIVOT_LANGUAGE },
],
});

await assertLanguagePairs({
message:
"When downloading the pivot language, only download the one for the app's locale.",
app: "es",
requested: PIVOT_LANGUAGE,
languagePairs: [
{ fromLang: PIVOT_LANGUAGE, toLang: "es" },
{ fromLang: "es", toLang: PIVOT_LANGUAGE },
],
});

await assertLanguagePairs({
message:
"Delete just the requested language when the app locale is the pivot language",
app: PIVOT_LANGUAGE,
requested: "fr",
isForDeletion: true,
languagePairs: [
{ fromLang: PIVOT_LANGUAGE, toLang: "fr" },
{ fromLang: "fr", toLang: PIVOT_LANGUAGE },
],
});

await assertLanguagePairs({
message: "Delete just the requested language, and not the pivot.",
app: "fr",
requested: "pl",
isForDeletion: true,
languagePairs: [
{ fromLang: PIVOT_LANGUAGE, toLang: "pl" },
{ fromLang: "pl", toLang: PIVOT_LANGUAGE },
],
});

await assertLanguagePairs({
message: "Delete just the requested language, and not the pivot.",
app: "fr",
requested: "pl",
isForDeletion: true,
languagePairs: [
{ fromLang: PIVOT_LANGUAGE, toLang: "pl" },
{ fromLang: "pl", toLang: PIVOT_LANGUAGE },
],
});

await assertLanguagePairs({
message: "Delete just the pivot → app and app → pivot.",
app: "es",
requested: PIVOT_LANGUAGE,
isForDeletion: true,
languagePairs: [
{ fromLang: PIVOT_LANGUAGE, toLang: "es" },
{ fromLang: "es", toLang: PIVOT_LANGUAGE },
],
});

await assertLanguagePairs({
message:
"If the app and request language are the same, nothing is returned.",
app: "fr",
requested: "fr",
languagePairs: [],
});

return cleanup();
});

0 comments on commit 6edf4fd

Please sign in to comment.