tests/i18nSplit.test.ts
Rama main · réplica de lectura del repositorio original.
// THE DICTIONARY, ONE LANGUAGE PER FILE (3.29 sweep, part 2).
//
// client/i18n/en.ts is the key list and client/i18n/ar.ts is held to it by
// type — parity is a compile error, and these cases say so in words as well,
// from the files themselves. Then the runtime half: a page installs one
// language at a time, t() speaks the one set, and a switch waits for its
// strings (whenDictionary) rather than drawing the language it is leaving.
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { describe, it } from "node:test";
import ar from "../client/i18n/ar.ts";
import en from "../client/i18n/en.ts";
import { importsOf } from "./helpers/importGraph.ts";
import { readDictionary } from "../scripts/dictionary.mjs";
const read = (rel: string): string => readFileSync(new URL(`../${rel}`, import.meta.url), "utf8");
describe("the two dictionary files", () => {
it("hold exactly the same keys, in the same order", () => {
assert.deepEqual(Object.keys(ar), Object.keys(en));
assert.ok(Object.keys(en).length > 3000, "the whole dictionary moved, not a slice of it");
});
it("give every key a value in both languages, Arabic in Arabic", () => {
for (const key of Object.keys(en) as (keyof typeof en)[]) {
assert.ok(en[key].length > 0, `en.${key} is empty`);
assert.ok(ar[key].length > 0, `ar.${key} is empty`);
}
});
it("keep parity a TYPE error: the Arabic satisfies the English key list", () => {
assert.match(read("client/i18n/ar.ts"), /\} satisfies Record<I18nKey, string>;/);
assert.match(read("client/i18n/ar.ts"), /import type \{ I18nKey \} from "\.\/en\.ts";/);
assert.match(read("client/i18n/en.ts"), /export type I18nKey = keyof typeof en;/);
});
it("read the same through the gates' text reader (scripts/dictionary.mjs)", () => {
const dict = readDictionary();
assert.equal(dict.size, Object.keys(en).length);
assert.deepEqual(dict.strays, []);
// The reader returns the source text between the quotes; for a value with
// no escapes that is the value itself.
assert.equal(dict.get("searchPlaceholder")?.en, en.searchPlaceholder);
assert.equal(dict.get("searchPlaceholder")?.ar, ar.searchPlaceholder);
});
it("are reached by no client module but the loader (and the Node installer)", () => {
for (const file of ["client/i18n/en.ts", "client/i18n/ar.ts"]) {
const from = importsOf(file)
// The key list's TYPE import (i18n.ts, ar.ts) is erased; it fetches nothing.
.filter((e) => e.from.startsWith("client/") && !(e.names.length === 1 && e.names[0] === "I18nKey"))
.map((e) => `${e.from}${e.names.includes("*") ? " (dynamic)" : ""}`)
.sort();
assert.deepEqual(from, ["client/i18n.ts (dynamic)", "client/i18n/both.ts"], `${file} is imported from ${from.join(", ")}`);
}
});
});
describe("one language at a time (client/i18n.ts)", async () => {
// A fresh module instance: nothing installed yet, as in a page at boot.
const i18n: typeof import("../client/i18n.ts") = await import(`../client/i18n.ts?fresh=${Date.now()}`);
it("speaks no dictionary before one is installed, and names the key rather than inventing a string", () => {
assert.equal(i18n.hasDictionary("en"), false);
assert.equal(i18n.t("searchPlaceholder"), "searchPlaceholder");
});
it("speaks the installed language, and the active one once it is set", () => {
i18n.installDictionary("en", en);
i18n.setLang("en");
assert.equal(i18n.t("searchPlaceholder"), en.searchPlaceholder);
// Arabic chosen but not yet installed: the installed English, never a key.
i18n.setLang("ar");
assert.equal(i18n.t("searchPlaceholder"), en.searchPlaceholder);
i18n.installDictionary("ar", ar);
assert.equal(i18n.t("searchPlaceholder"), ar.searchPlaceholder);
i18n.setLang("en");
});
it("runs a switch at once when its strings are already here", () => {
let ran = false;
i18n.whenDictionary("ar", () => {
ran = true;
});
assert.equal(ran, true);
});
});
describe("a switch waits for its strings", async () => {
const i18n: typeof import("../client/i18n.ts") = await import(`../client/i18n.ts?fresh=wait-${Date.now()}`);
it("applies after the chunk lands, and a later switch supersedes an earlier one still waiting", async () => {
i18n.installDictionary("en", en);
i18n.setLang("en");
const applied: string[] = [];
// Arabic is not installed: this one has to wait for the fetch…
i18n.whenDictionary("ar", () => applied.push("ar"));
assert.deepEqual(applied, [] as string[], "nothing is applied before the Arabic arrives");
// …and the reader goes back to English before it lands.
i18n.whenDictionary("en", () => applied.push("en"));
assert.deepEqual(applied, ["en"]);
await i18n.loadDictionary("ar");
await new Promise((r) => setTimeout(r, 0));
assert.deepEqual(applied, ["en"], "the superseded Arabic switch never lands");
assert.equal(i18n.hasDictionary("ar"), true, "…though its strings are kept for next time");
});
});