Initial Commit
This commit is contained in:
109
packages/ui/react/react-ui/scripts/check-locale-completeness.ts
Normal file
109
packages/ui/react/react-ui/scripts/check-locale-completeness.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* `check-locale-completeness.ts` — assert every key in
|
||||
* `<package>/src/locales/en-GB.json` exists in every other locale
|
||||
* bundle shipped by the package. Pure file walk; no transitive deps on
|
||||
* the package source.
|
||||
*
|
||||
* Usage:
|
||||
* bun packages/react/react-ui/scripts/check-locale-completeness.ts <package-root>
|
||||
*
|
||||
* Exit codes:
|
||||
* 0 — every key parity-complete
|
||||
* 1 — at least one key missing
|
||||
* 2 — internal failure
|
||||
*/
|
||||
|
||||
import { readdir, readFile } from "node:fs/promises";
|
||||
import { join, resolve } from "node:path";
|
||||
|
||||
interface BundleDiff {
|
||||
readonly locale: string;
|
||||
readonly missing: ReadonlyArray<string>;
|
||||
readonly extra: ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
export async function checkLocaleCompleteness(
|
||||
packageRoot: string
|
||||
): Promise<BundleDiff[]> {
|
||||
const localesDir = join(resolve(packageRoot), "src", "locales");
|
||||
const enPath = join(localesDir, "en-GB.json");
|
||||
const enKeys = new Set(
|
||||
Object.keys(JSON.parse(await readFile(enPath, "utf8")))
|
||||
);
|
||||
const entries = await readdir(localesDir);
|
||||
const diffs: BundleDiff[] = [];
|
||||
for (const entry of entries) {
|
||||
if (entry === "en-GB.json") continue;
|
||||
if (!entry.endsWith(".json")) continue;
|
||||
const otherPath = join(localesDir, entry);
|
||||
const other = JSON.parse(await readFile(otherPath, "utf8")) as Record<
|
||||
string,
|
||||
string
|
||||
>;
|
||||
const otherKeys = new Set(Object.keys(other));
|
||||
const missing: string[] = [];
|
||||
for (const k of enKeys) {
|
||||
if (!otherKeys.has(k)) missing.push(k);
|
||||
}
|
||||
const extra: string[] = [];
|
||||
for (const k of otherKeys) {
|
||||
if (!enKeys.has(k)) extra.push(k);
|
||||
}
|
||||
if (missing.length > 0 || extra.length > 0) {
|
||||
diffs.push({
|
||||
locale: entry.replace(/\.json$/, ""),
|
||||
missing,
|
||||
extra,
|
||||
});
|
||||
}
|
||||
}
|
||||
return diffs;
|
||||
}
|
||||
|
||||
function format(diffs: ReadonlyArray<BundleDiff>): string {
|
||||
if (diffs.length === 0) {
|
||||
return "check-locale-completeness: every locale parity-complete.";
|
||||
}
|
||||
const lines: string[] = [
|
||||
`check-locale-completeness: ${diffs.length} locale(s) failed parity.`,
|
||||
];
|
||||
for (const d of diffs) {
|
||||
lines.push(` ${d.locale}:`);
|
||||
if (d.missing.length > 0) {
|
||||
lines.push(" missing keys:");
|
||||
for (const k of d.missing) lines.push(` - ${k}`);
|
||||
}
|
||||
if (d.extra.length > 0) {
|
||||
lines.push(" extra keys (not in en-GB):");
|
||||
for (const k of d.extra) lines.push(` - ${k}`);
|
||||
}
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const argv = process.argv.slice(2);
|
||||
if (argv.length === 0) {
|
||||
process.stderr.write(
|
||||
"Usage: check-locale-completeness.ts <package-root>\n"
|
||||
);
|
||||
process.exit(2);
|
||||
}
|
||||
const root = argv[0] as string;
|
||||
const diffs = await checkLocaleCompleteness(root);
|
||||
const message = format(diffs);
|
||||
if (diffs.length === 0) {
|
||||
process.stdout.write(`${message}\n`);
|
||||
process.exit(0);
|
||||
}
|
||||
process.stderr.write(`${message}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
await main().catch((err: unknown) => {
|
||||
process.stderr.write(`internal error: ${(err as Error).message}\n`);
|
||||
process.exit(2);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user