Skip to content

Commit be2c4c6

Browse files
committed
fix(i18n): invalidate native artifacts on glossary changes
1 parent 1611e04 commit be2c4c6

2 files changed

Lines changed: 32 additions & 7 deletions

File tree

scripts/native-app-i18n.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export type NativeI18nEntry = {
4242
type Candidate = Omit<NativeI18nEntry, "id">;
4343
type NativeTranslationArtifact = {
4444
entries: Array<{ id: string; source: string; translated: string }>;
45+
glossaryHash: string;
4546
locale: string;
4647
version: 1;
4748
};
@@ -923,35 +924,42 @@ export async function syncNativeLocale(
923924
// Native runtime resources are owned by the Android and Apple slices; these
924925
// artifacts keep the shared translation-memory handoff current between them.
925926
const artifactPath = path.join(options.translationsDir ?? TRANSLATIONS_DIR, `${locale}.json`);
927+
const glossary = options.glossary ?? (await loadGlossary(locale));
928+
const glossaryHash = createHash("sha256").update(JSON.stringify(glossary)).digest("hex");
926929
let previousRaw = "";
927-
let previous: NativeTranslationArtifact = { entries: [], locale, version: 1 };
930+
let previous: NativeTranslationArtifact = {
931+
entries: [],
932+
glossaryHash: "",
933+
locale,
934+
version: 1,
935+
};
928936
try {
929937
previousRaw = await readFile(artifactPath, "utf8");
930938
previous = JSON.parse(previousRaw) as NativeTranslationArtifact;
931939
} catch {
932940
// The first refresh creates the locale artifact.
933941
}
934942
const previousById = new Map(previous.entries.map((entry) => [entry.id, entry]));
943+
const glossaryChanged = previous.glossaryHash !== glossaryHash;
935944
const pending = entries
936945
.filter((entry) => {
937946
const current = previousById.get(entry.id);
938-
return !current || current.source !== entry.source || !current.translated.trim();
947+
return (
948+
glossaryChanged || !current || current.source !== entry.source || !current.translated.trim()
949+
);
939950
})
940951
.map((entry) => ({
941952
id: entry.id,
942953
source: entry.source,
943954
sourcePath: entry.path,
944955
}));
945956
const translated = pending.length
946-
? await (options.translate ?? translateNativeEntries)(
947-
pending,
948-
locale,
949-
options.glossary ?? (await loadGlossary(locale)),
950-
)
957+
? await (options.translate ?? translateNativeEntries)(pending, locale, glossary)
951958
: new Map<string, string>();
952959
const artifact: NativeTranslationArtifact = {
953960
version: 1,
954961
locale,
962+
glossaryHash,
955963
entries: entries.map((entry) => ({
956964
id: entry.id,
957965
source: entry.source,

test/scripts/native-app-i18n.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,23 @@ describe("native app i18n inventory", () => {
193193
expect(second).toEqual({ changed: false, translated: 0 });
194194
expect(await readFile(artifactPath, "utf8")).toBe(firstContents);
195195
expect((await stat(artifactPath)).mtimeMs).toBe(firstModifiedAt);
196+
197+
const refreshed = await syncNativeLocale("sv", entries, {
198+
glossary: [{ source: "Request", target: "Begäran" }],
199+
translationsDir,
200+
translate: async (pending) =>
201+
new Map(pending.map((entry) => [entry.id, `refreshed:${entry.source}`])),
202+
});
203+
204+
expect(refreshed).toEqual({ changed: true, translated: 4 });
205+
const refreshedArtifact = JSON.parse(await readFile(artifactPath, "utf8")) as {
206+
entries: Array<{ translated: string }>;
207+
glossaryHash: string;
208+
};
209+
expect(refreshedArtifact.glossaryHash).toMatch(/^[a-f0-9]{64}$/u);
210+
expect(
211+
refreshedArtifact.entries.every((entry) => entry.translated.startsWith("refreshed:")),
212+
).toBe(true);
196213
} finally {
197214
cleanupTempDirs(tempDirs);
198215
}

0 commit comments

Comments
 (0)