mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
fix(i18n): serve English for an unreadable catalog, refuse to boot without it
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.
This commit is contained in:
1 parent
ac31130bc0
commit
7fda400a8c
6 files changed
+365
-7
No files matched your search
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* `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/);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user