1e62837c68
An audit found the client already i18n-first: one hard-coded UI string (the battle-scene aria-label, now keyed) and en/ru already share an identical 692-key set. - Persist the locale: i18n.setLocale writes localStorage (galaxy-locale) and the store boots from stored > browser detection > default, so a language switch survives reloads. - tests/i18n-completeness.test.ts: en/ru key-set parity, non-empty values, and locale persistence. - Docs: ui/docs/i18n.md; mark F3 done in ui/PLAN-finalize.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
60 lines
2.0 KiB
TypeScript
60 lines
2.0 KiB
TypeScript
import "@testing-library/jest-dom/vitest";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import en from "../src/lib/i18n/locales/en";
|
|
import ru from "../src/lib/i18n/locales/ru";
|
|
|
|
// Structural guard against translation drift: the en bundle defines the
|
|
// TranslationKey type, so any key added to one locale but not the other
|
|
// (or left blank) must fail here rather than silently fall back at
|
|
// runtime.
|
|
describe("i18n bundle completeness", () => {
|
|
it("en and ru expose identical key sets", () => {
|
|
const onlyEn = Object.keys(en).filter((k) => !(k in ru));
|
|
const onlyRu = Object.keys(ru).filter((k) => !(k in en));
|
|
expect({ onlyEn, onlyRu }).toEqual({ onlyEn: [], onlyRu: [] });
|
|
});
|
|
|
|
it("has no empty translation values", () => {
|
|
for (const [k, v] of Object.entries(en)) {
|
|
expect(v.length, `en.${k}`).toBeGreaterThan(0);
|
|
}
|
|
for (const [k, v] of Object.entries(ru)) {
|
|
expect(v.length, `ru.${k}`).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
});
|
|
|
|
// The store is a module singleton constructed on first import (it reads
|
|
// localStorage in its initialiser), so each case clears storage and
|
|
// resets the module registry, then imports a freshly-built store.
|
|
const LOCALE_KEY = "galaxy-locale";
|
|
|
|
describe("locale persistence", () => {
|
|
beforeEach(() => {
|
|
localStorage.clear();
|
|
vi.resetModules();
|
|
});
|
|
|
|
it("persists an explicit choice and restores it on the next load", async () => {
|
|
const first = await import("../src/lib/i18n/index.svelte");
|
|
first.i18n.setLocale("ru");
|
|
expect(localStorage.getItem(LOCALE_KEY)).toBe("ru");
|
|
|
|
vi.resetModules();
|
|
const second = await import("../src/lib/i18n/index.svelte");
|
|
expect(second.i18n.locale).toBe("ru");
|
|
});
|
|
|
|
it("falls back to browser detection when nothing is stored", async () => {
|
|
const mod = await import("../src/lib/i18n/index.svelte");
|
|
expect(["en", "ru"]).toContain(mod.i18n.locale);
|
|
});
|
|
|
|
it("ignores an unrecognised stored value", async () => {
|
|
localStorage.setItem(LOCALE_KEY, "fr");
|
|
const mod = await import("../src/lib/i18n/index.svelte");
|
|
expect(["en", "ru"]).toContain(mod.i18n.locale);
|
|
});
|
|
});
|