Files
openclaw/test/scripts/control-ui-i18n-sync-plan.test.ts
Peter Steinberger 7e78de747b refactor(ui): replace duplicated locale bundles with canonical translation memory (#117157)
* refactor(ui): generate locale bundles from canonical translation memory

* fix(ui): keep server-only locale config out of browser test helpers
2026-07-31 19:40:27 -07:00

299 lines
8.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
hashControlUiTranslationText,
materializeControlUiLocaleCatalog,
} from "../../scripts/lib/control-ui-i18n-catalog.ts";
import {
createControlUiLocaleSyncPlan,
flattenTranslations,
resolveLocaleMetaProvenance,
type LocaleEntry,
type LocaleMeta,
type TranslationMemoryEntry,
} from "../../scripts/lib/control-ui-i18n-sync-plan.ts";
const entry: LocaleEntry = {
exportName: "fr",
fileName: "fr.ts",
languageKey: "fr",
locale: "fr",
};
const hashText = (text: string) => `hash:${text}`;
const cacheKeyFor = (key: string, textHash: string) => `cache:${key}:${textHash}`;
function memoryEntry(overrides: Partial<TranslationMemoryEntry> = {}): TranslationMemoryEntry {
return {
cache_key: "legacy-cache",
model: "legacy-model",
provider: "legacy-provider",
segment_id: "legacy.segment",
source_path: "ui/src/i18n/locales/fr.ts",
src_lang: "en",
text: "Shared",
text_hash: hashText("Shared"),
tgt_lang: "fr",
translated: "Partage",
updated_at: "2026-01-01T00:00:00.000Z",
...overrides,
};
}
function localeMeta(overrides: Partial<LocaleMeta> = {}): LocaleMeta {
return {
fallbackKeys: [],
generatedAt: "2026-01-01T00:00:00.000Z",
locale: "fr",
model: "legacy-model",
provider: "legacy-provider",
sourceHash: "old-source",
totalKeys: 0,
translatedKeys: 0,
workflow: 1,
...overrides,
};
}
describe("createControlUiLocaleSyncPlan", () => {
it("preserves provenance when a configured provider performs no translation", () => {
const previousMeta = localeMeta();
expect(
resolveLocaleMetaProvenance({
didTranslate: false,
model: "next-model",
previousMeta,
provider: "next-provider",
}),
).toEqual({ model: previousMeta.model, provider: previousMeta.provider });
expect(
resolveLocaleMetaProvenance({
didTranslate: true,
model: "next-model",
previousMeta,
provider: "next-provider",
}),
).toEqual({ model: "next-model", provider: "next-provider" });
});
it("plans reuse and renders deterministic locale artifacts", () => {
const sourceFlat = flattenTranslations({
group: {
cached: "Cached source",
existing: "Existing source",
pending: "Pending source",
reused: "Shared",
},
});
const exactCacheKey = cacheKeyFor("group.cached", hashText("Cached source"));
const exactCache = memoryEntry({
cache_key: exactCacheKey,
segment_id: "group.cached",
text: "Cached source",
text_hash: hashText("Cached source"),
translated: "En cache",
});
const sharedCache = memoryEntry();
const plan = createControlUiLocaleSyncPlan({
allowTranslate: false,
cacheKeyFor,
entry,
existingFlat: new Map([
["group.cached", "Ancien cache"],
["group.existing", "Existant"],
]),
force: false,
hashText,
previousMeta: localeMeta({ fallbackKeys: ["group.cached"] }),
sourceFlat,
sourceHash: "next-source",
translationMemory: new Map([
[sharedCache.cache_key, sharedCache],
[exactCache.cache_key, exactCache],
]),
});
expect(plan.pending.map((item) => item.key)).toEqual(["group.pending"]);
expect(plan.newFallbackCount).toBe(1);
const artifacts = plan.render({
defaultGlossary: [{ source: "OpenClaw", target: "OpenClaw" }],
generatedAt: "2026-02-02T00:00:00.000Z",
glossary: [],
model: "legacy-model",
provider: "legacy-provider",
workflow: 1,
});
expect(artifacts.meta).toBe(
`${JSON.stringify(
{
fallbackKeys: ["group.cached", "group.pending"],
generatedAt: "2026-02-02T00:00:00.000Z",
locale: "fr",
model: "legacy-model",
provider: "legacy-provider",
sourceHash: "next-source",
totalKeys: 4,
translatedKeys: 2,
workflow: 1,
},
null,
2,
)}\n`,
);
expect(artifacts.glossary).toBe(
`${JSON.stringify([{ source: "OpenClaw", target: "OpenClaw" }], null, 2)}\n`,
);
const reusedCache = {
...sharedCache,
cache_key: cacheKeyFor("group.reused", hashText("Shared")),
segment_id: "group.reused",
};
expect(artifacts.translationMemory).toBe(
`${[reusedCache, exactCache]
.toSorted((left, right) => left.cache_key.localeCompare(right.cache_key))
.map((value) => JSON.stringify(value))
.join("\n")}\n`,
);
});
it("reuses grouped segment aliases only while their source text still matches", () => {
const sourceFlat = flattenTranslations({ group: { alias: "Shared" } });
const grouped = memoryEntry({ segment_ids: ["group.alias"] });
const createPlan = (source: ReadonlyMap<string, string>) =>
createControlUiLocaleSyncPlan({
allowTranslate: false,
cacheKeyFor,
entry,
existingFlat: new Map(),
force: false,
hashText,
previousMeta: localeMeta(),
sourceFlat: source,
sourceHash: "source",
translationMemory: new Map([[grouped.cache_key, grouped]]),
});
expect(createPlan(sourceFlat).pending).toEqual([]);
expect(createPlan(new Map([["group.alias", "Changed"]])).pending).toHaveLength(1);
});
it("materializes grouped aliases in source order and discards stale or retired segments", () => {
const grouped = memoryEntry({
segment_id: "group.first",
segment_ids: ["group.second", "removed"],
text_hash: hashControlUiTranslationText("Shared"),
translated: "Partagé",
});
const source = flattenTranslations({ group: { first: "Shared", second: "Shared" } });
expect(
materializeControlUiLocaleCatalog(source, new Map([[grouped.cache_key, grouped]])),
).toEqual({
group: { first: "Partagé", second: "Partagé" },
});
expect(
materializeControlUiLocaleCatalog(
new Map([["group.first", "Changed"]]),
new Map([[grouped.cache_key, grouped]]),
),
).toEqual({});
});
it("refreshes recorded fallbacks and records translated replacements", () => {
const sourceFlat = flattenTranslations({ title: "New English" });
const previousMeta = localeMeta({
fallbackKeys: ["title"],
sourceHash: "previous-source",
totalKeys: 1,
translatedKeys: 0,
});
const plan = createControlUiLocaleSyncPlan({
allowTranslate: true,
cacheKeyFor,
entry,
existingFlat: new Map([["title", "Old English"]]),
force: true,
hashText,
previousMeta,
sourceFlat,
sourceHash: "next-source",
translationMemory: new Map(),
});
expect(plan.newFallbackCount).toBe(0);
plan.recordTranslations(plan.pending, new Map([["title", "Nouveau"]]), {
model: "next-model",
provider: "next-provider",
sourceLocale: "en",
updatedAt: () => "2026-02-02T00:00:00.000Z",
});
const artifacts = plan.render({
defaultGlossary: [],
generatedAt: "2026-03-03T00:00:00.000Z",
glossary: [],
model: "next-model",
provider: "next-provider",
workflow: 1,
});
expect(artifacts.fallbackCount).toBe(0);
expect(artifacts.nextFlat.get("title")).toBe("Nouveau");
expect(JSON.parse(artifacts.meta)).toMatchObject({
fallbackKeys: [],
generatedAt: "2026-03-03T00:00:00.000Z",
translatedKeys: 1,
});
expect(artifacts.translationMemory).toBe(
`${JSON.stringify(
memoryEntry({
cache_key: cacheKeyFor("title", hashText("New English")),
model: "next-model",
provider: "next-provider",
segment_id: "title",
text: "New English",
text_hash: hashText("New English"),
translated: "Nouveau",
updated_at: "2026-02-02T00:00:00.000Z",
}),
)}\n`,
);
});
it("preserves generatedAt when semantic metadata is unchanged", () => {
const sourceFlat = flattenTranslations({ title: "Titre" });
const previousMeta = localeMeta({
sourceHash: "same-source",
totalKeys: 1,
translatedKeys: 1,
});
const plan = createControlUiLocaleSyncPlan({
allowTranslate: false,
cacheKeyFor,
entry,
existingFlat: new Map([["title", "Titre"]]),
force: false,
hashText,
previousMeta,
sourceFlat,
sourceHash: "same-source",
translationMemory: new Map(),
});
const artifacts = plan.render({
defaultGlossary: [],
generatedAt: "2026-03-03T00:00:00.000Z",
glossary: [],
model: "legacy-model",
provider: "legacy-provider",
workflow: 1,
});
expect(JSON.parse(artifacts.meta)).toMatchObject({
generatedAt: previousMeta.generatedAt,
});
});
});