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; }