Files
bookorbit/scripts/validate-locale-pr.test.mjs
T
neon b9e041d1b7 ci(i18n): enforce controlled crowdin localization workflow and pr validation
Add Crowdin pull request validation script, centralize locale configuration, and update GitHub workflows to enforce sparse target catalog validation.
2026-08-06 00:00:04 +00:00

116 lines
3.5 KiB
JavaScript

import assert from "node:assert/strict";
import { execFileSync, spawnSync } from "node:child_process";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
const scriptPath = fileURLToPath(new URL("./validate-locale-pr.mjs", import.meta.url));
const gitEnv = {
...process.env,
GIT_AUTHOR_EMAIL: "test@example.com",
GIT_AUTHOR_NAME: "Test",
GIT_COMMITTER_EMAIL: "test@example.com",
GIT_COMMITTER_NAME: "Test",
};
const git = (cwd, args) =>
execFileSync("git", args, {
cwd,
encoding: "utf8",
env: gitEnv,
stdio: ["ignore", "pipe", "pipe"],
}).trim();
const createRepository = () => {
const cwd = mkdtempSync(path.join(tmpdir(), "bookorbit-locale-policy-"));
git(cwd, ["init", "--quiet"]);
mkdirSync(path.join(cwd, "client/src/locales"), { recursive: true });
writeFileSync(path.join(cwd, "client/src/locales/en.json"), '{"example":"Example"}\n');
writeFileSync(path.join(cwd, "client/src/locales/de.json"), '{"example":"Beispiel"}\n');
git(cwd, ["add", "."]);
git(cwd, ["commit", "--quiet", "-m", "base"]);
return cwd;
};
const commitChanges = (cwd) => {
git(cwd, ["add", "--all"]);
git(cwd, ["commit", "--quiet", "-m", "change"]);
};
const validate = (cwd, isCrowdin = false) => {
const headSha = git(cwd, ["rev-parse", "HEAD"]);
const baseSha = git(cwd, ["rev-parse", "HEAD^"]);
return spawnSync(process.execPath, [scriptPath, baseSha, headSha, String(isCrowdin)], { cwd, encoding: "utf8" });
};
const withRepository = (callback) => {
const cwd = createRepository();
try {
callback(cwd);
} finally {
rmSync(cwd, { recursive: true, force: true });
}
};
test("allows an ordinary pull request to change en.json", () => {
withRepository((cwd) => {
writeFileSync(path.join(cwd, "client/src/locales/en.json"), '{"example":"Updated"}\n');
commitChanges(cwd);
assert.equal(validate(cwd).status, 0);
});
});
test("rejects an ordinary pull request that changes a target catalog", () => {
withRepository((cwd) => {
writeFileSync(path.join(cwd, "client/src/locales/de.json"), '{"example":"Geandert"}\n');
commitChanges(cwd);
const result = validate(cwd);
assert.equal(result.status, 1);
assert.match(result.stderr, /Crowdin-owned target catalog/);
assert.match(result.stderr, /client\/src\/locales\/en\.json/);
});
});
test("rejects deletion of an existing target catalog", () => {
withRepository((cwd) => {
rmSync(path.join(cwd, "client/src/locales/de.json"));
commitChanges(cwd);
assert.equal(validate(cwd).status, 1);
});
});
test("allows an empty catalog for new-language setup", () => {
withRepository((cwd) => {
writeFileSync(path.join(cwd, "client/src/locales/ja.json"), "{}\n");
commitChanges(cwd);
assert.equal(validate(cwd).status, 0);
});
});
test("rejects a translated catalog added outside Crowdin", () => {
withRepository((cwd) => {
writeFileSync(path.join(cwd, "client/src/locales/ja.json"), '{"example":"Rei"}\n');
commitChanges(cwd);
const result = validate(cwd);
assert.equal(result.status, 1);
assert.match(result.stderr, /new target catalog but is not empty/);
});
});
test("allows a classified Crowdin pull request to change target catalogs", () => {
withRepository((cwd) => {
writeFileSync(path.join(cwd, "client/src/locales/de.json"), '{"example":"Aktualisiert"}\n');
commitChanges(cwd);
assert.equal(validate(cwd, true).status, 0);
});
});