/** * Every t('key') in the source, checked against the catalogues. * * A key the code calls and the catalogue lacks renders as humanised English at * best, so this is the check that says the extraction is actually complete. * The reverse — a catalogue entry nothing calls — is only waste, and reported * separately. */ import { readFileSync, readdirSync, statSync } from 'node:fs'; import { join } from 'node:path'; const walk = (dir, acc = []) => { for (const e of readdirSync(dir)) { if (e === 'node_modules' || e.startsWith('.')) continue; const p = join(dir, e); if (statSync(p).isDirectory()) walk(p, acc); else if (/\.(tsx|ts|jsx|js)$/.test(p)) acc.push(p); } return acc; }; const flat = (obj, prefix = '', out = {}) => { for (const [k, v] of Object.entries(obj)) { const key = prefix ? `${prefix}.${k}` : k; if (v && typeof v === 'object') flat(v, key, out); else out[key] = v; } return out; }; const en = flat(JSON.parse(readFileSync('src/lib/i18n/locales/en.json', 'utf8'))); const es = flat(JSON.parse(readFileSync('src/lib/i18n/locales/es.json', 'utf8'))); const used = new Set(); const dynamic = []; for (const file of walk('src')) { const src = readFileSync(file, 'utf8'); for (const m of src.matchAll(/\bt\(\s*'([^']+)'/g)) used.add(m[1]); for (const m of src.matchAll(/\bt\(\s*`([^`]+)`/g)) dynamic.push(`${file}: ${m[1]}`); } /* A plural key is stored as name_one / name_other, so the base name is never itself in the catalogue and is not missing. */ const present = (k) => k in en || `${k}_other` in en || `${k}_one` in en; const missingEn = [...used].filter((k) => !present(k)).sort(); const missingEs = [...used].filter((k) => present(k) && !(k in es || `${k}_other` in es)).sort(); const unused = Object.keys(en).filter((k) => !used.has(k) && !used.has(k.replace(/_(one|other)$/, ''))).sort(); console.log(`\n t() calls with a literal key : ${used.size}`); console.log(` built from a template : ${dynamic.length} (checked by hand)`); console.log(` MISSING from en.json : ${missingEn.length}`); console.log(` MISSING from es.json : ${missingEs.length}`); console.log(` in en.json, never called : ${unused.length}`); for (const k of missingEn.slice(0, 15)) console.log(` missing en: ${k}`); for (const k of missingEs.slice(0, 10)) console.log(` missing es: ${k}`); if (missingEn.length) process.exitCode = 1;