mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
The server reads messages/<locale>.json from disk on first use. A missing or corrupt file threw, and since the matchers iterate every locale, one bad non-English catalog turned every request that touched them into a 500. That locale is now served the English catalog, and the failure is logged once as i18n.catalog.fallback with the locale and the reason. English is the fallback itself: a process whose messages/en.json is missing or does not parse refuses to start, with the file and the reason in the message. The image build checks the catalogs twice with scripts/verify-message-catalogs.mjs: in the builder against the locale list in the source, and in the runner stage against the copies the image carries.
64 lines
2.2 KiB
TypeScript
64 lines
2.2 KiB
TypeScript
/**
|
|
* `scripts/verify-message-catalogs.mjs`, which the image build runs in the
|
|
* builder and again in the runner stage, passes the shipped catalogs and fails
|
|
* on a missing or corrupt one. A check that cannot fail would let an image
|
|
* without its catalogs through, so both directions are pinned.
|
|
*/
|
|
import { spawnSync } from "node:child_process";
|
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
|
|
const SCRIPT = join(process.cwd(), "scripts", "verify-message-catalogs.mjs");
|
|
const CONFIG = join(process.cwd(), "src", "lib", "i18n", "config.ts");
|
|
|
|
function run(args: string[]) {
|
|
return spawnSync(process.execPath, [SCRIPT, ...args], { encoding: "utf8" });
|
|
}
|
|
|
|
let scratch: string | null = null;
|
|
afterEach(() => {
|
|
if (scratch) rmSync(scratch, { recursive: true, force: true });
|
|
scratch = null;
|
|
});
|
|
|
|
describe("verify-message-catalogs", () => {
|
|
it("passes the catalogs in the tree and writes the locale list", () => {
|
|
scratch = mkdtempSync(join(tmpdir(), "hl-catalog-check-"));
|
|
const list = join(scratch, "locales");
|
|
const res = run([
|
|
"--dir",
|
|
join(process.cwd(), "messages"),
|
|
"--config",
|
|
CONFIG,
|
|
"--write",
|
|
list,
|
|
]);
|
|
expect(res.status).toBe(0);
|
|
expect(res.stdout).toMatch(/message catalogs ok: .*\ben\b/);
|
|
|
|
// The runner-stage form, against the list the builder wrote.
|
|
expect(
|
|
run(["--dir", join(process.cwd(), "messages"), "--locales", list]).status,
|
|
).toBe(0);
|
|
});
|
|
|
|
it("fails on a missing catalog and on one that does not parse", () => {
|
|
scratch = mkdtempSync(join(tmpdir(), "hl-catalog-check-"));
|
|
const dir = join(scratch, "messages");
|
|
mkdirSync(dir);
|
|
writeFileSync(join(dir, "en.json"), "{}");
|
|
writeFileSync(join(dir, "de.json"), '{"common": ');
|
|
const list = join(scratch, "locales");
|
|
writeFileSync(list, "en\nde\nfr\n");
|
|
|
|
const res = run(["--dir", dir, "--locales", list]);
|
|
expect(res.status).toBe(1);
|
|
expect(res.stderr).toMatch(/de\.json: does not parse/);
|
|
expect(res.stderr).toMatch(/fr\.json: missing/);
|
|
expect(res.stderr).not.toMatch(/en\.json/);
|
|
});
|
|
});
|