diff --git a/scripts/native-app-i18n.ts b/scripts/native-app-i18n.ts index d96aef9764dd..6326dd5f53fc 100644 --- a/scripts/native-app-i18n.ts +++ b/scripts/native-app-i18n.ts @@ -42,6 +42,7 @@ export type NativeI18nEntry = { type Candidate = Omit; type NativeTranslationArtifact = { entries: Array<{ id: string; source: string; translated: string }>; + glossaryHash: string; locale: string; version: 1; }; @@ -923,8 +924,15 @@ export async function syncNativeLocale( // Native runtime resources are owned by the Android and Apple slices; these // artifacts keep the shared translation-memory handoff current between them. const artifactPath = path.join(options.translationsDir ?? TRANSLATIONS_DIR, `${locale}.json`); + const glossary = options.glossary ?? (await loadGlossary(locale)); + const glossaryHash = createHash("sha256").update(JSON.stringify(glossary)).digest("hex"); let previousRaw = ""; - let previous: NativeTranslationArtifact = { entries: [], locale, version: 1 }; + let previous: NativeTranslationArtifact = { + entries: [], + glossaryHash: "", + locale, + version: 1, + }; try { previousRaw = await readFile(artifactPath, "utf8"); previous = JSON.parse(previousRaw) as NativeTranslationArtifact; @@ -932,10 +940,13 @@ export async function syncNativeLocale( // The first refresh creates the locale artifact. } const previousById = new Map(previous.entries.map((entry) => [entry.id, entry])); + const glossaryChanged = previous.glossaryHash !== glossaryHash; const pending = entries .filter((entry) => { const current = previousById.get(entry.id); - return !current || current.source !== entry.source || !current.translated.trim(); + return ( + glossaryChanged || !current || current.source !== entry.source || !current.translated.trim() + ); }) .map((entry) => ({ id: entry.id, @@ -943,15 +954,12 @@ export async function syncNativeLocale( sourcePath: entry.path, })); const translated = pending.length - ? await (options.translate ?? translateNativeEntries)( - pending, - locale, - options.glossary ?? (await loadGlossary(locale)), - ) + ? await (options.translate ?? translateNativeEntries)(pending, locale, glossary) : new Map(); const artifact: NativeTranslationArtifact = { version: 1, locale, + glossaryHash, entries: entries.map((entry) => ({ id: entry.id, source: entry.source, diff --git a/test/scripts/native-app-i18n.test.ts b/test/scripts/native-app-i18n.test.ts index 94cec4d62339..dd1341f232bc 100644 --- a/test/scripts/native-app-i18n.test.ts +++ b/test/scripts/native-app-i18n.test.ts @@ -193,6 +193,23 @@ describe("native app i18n inventory", () => { expect(second).toEqual({ changed: false, translated: 0 }); expect(await readFile(artifactPath, "utf8")).toBe(firstContents); expect((await stat(artifactPath)).mtimeMs).toBe(firstModifiedAt); + + const refreshed = await syncNativeLocale("sv", entries, { + glossary: [{ source: "Request", target: "Begäran" }], + translationsDir, + translate: async (pending) => + new Map(pending.map((entry) => [entry.id, `refreshed:${entry.source}`])), + }); + + expect(refreshed).toEqual({ changed: true, translated: 4 }); + const refreshedArtifact = JSON.parse(await readFile(artifactPath, "utf8")) as { + entries: Array<{ translated: string }>; + glossaryHash: string; + }; + expect(refreshedArtifact.glossaryHash).toMatch(/^[a-f0-9]{64}$/u); + expect( + refreshedArtifact.entries.every((entry) => entry.translated.startsWith("refreshed:")), + ).toBe(true); } finally { cleanupTempDirs(tempDirs); }