67 lines
2.7 KiB
TypeScript
67 lines
2.7 KiB
TypeScript
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
import { extname, join, resolve } from "node:path";
|
|
import {
|
|
auditLocaleKeys,
|
|
extractTranslationKeysFromFiles,
|
|
flattenMessageKeys,
|
|
loadLocales,
|
|
} from "@wrnexus/i18n";
|
|
|
|
function sourceFiles(root: string): string[] {
|
|
const output: string[] = [];
|
|
const visit = (directory: string) => {
|
|
if (!existsSync(directory)) return;
|
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist")
|
|
continue;
|
|
const path = join(directory, entry.name);
|
|
if (entry.isDirectory()) visit(path);
|
|
else if ([".wrn", ".ts", ".tsx", ".js", ".jsx"].includes(extname(entry.name)))
|
|
output.push(path);
|
|
}
|
|
};
|
|
visit(join(root, "app"));
|
|
return output.sort();
|
|
}
|
|
export function runI18nCommand(appRoot: string, command: string): boolean {
|
|
const root = resolve(appRoot);
|
|
const extracted = extractTranslationKeysFromFiles(sourceFiles(root));
|
|
const keys = [...new Set(extracted.map((entry) => entry.key))].sort();
|
|
const messages = loadLocales(join(root, "app", "locales"), { strict: true });
|
|
const locales = Object.keys(messages).sort();
|
|
const reference = locales[0] ?? "en";
|
|
const audit = auditLocaleKeys(messages, reference);
|
|
const referenceKeys = new Set(flattenMessageKeys(messages[reference] ?? {}));
|
|
const unused = [...referenceKeys].filter((key) => !keys.includes(key)).sort();
|
|
const missingFromReference = keys.filter((key) => !referenceKeys.has(key));
|
|
const report = { reference, locales, extracted: keys, missingFromReference, unused, audit };
|
|
if (command === "extract") {
|
|
const directory = join(root, ".wrnexus");
|
|
mkdirSync(directory, { recursive: true });
|
|
const file = join(directory, "i18n-keys.json");
|
|
writeFileSync(file, `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
|
console.log(`✓ Extracted ${keys.length} translation keys to ${file}`);
|
|
return true;
|
|
}
|
|
if (command !== "validate")
|
|
throw new Error("WRN-I18N-COMMAND: use i18n extract or i18n validate.");
|
|
for (const locale of locales) {
|
|
const missing = [
|
|
...new Set([...(audit[locale]?.missing ?? []), ...missingFromReference]),
|
|
].sort();
|
|
if (missing.length) {
|
|
console.log(`Missing in ${locale}:`);
|
|
missing.forEach((key) => console.log(`- ${key}`));
|
|
}
|
|
}
|
|
if (unused.length) {
|
|
console.log("Unused keys:");
|
|
unused.forEach((key) => console.log(`- ${key}`));
|
|
}
|
|
const healthy =
|
|
missingFromReference.length === 0 &&
|
|
Object.values(audit).every((value) => value.missing.length === 0);
|
|
if (healthy) console.log(`✓ ${locales.length} locales contain every extracted key.`);
|
|
return healthy;
|
|
}
|