Files
galaxy-game/ui/frontend/tests/i18n-completeness.test.ts
T
Ilia Denisov 1e62837c68
Tests · UI / test (push) Waiting to run
Tests · UI / test (pull_request) Successful in 2m11s
feat(ui): locale persistence + i18n completeness guards (F3)
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>
2026-05-22 08:48:13 +02:00

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);
});
});