const fs = require('fs'); const path = require('path'); const ts = require('typescript'); function transpile(file) { const code = fs.readFileSync(file, 'utf8'); const out = ts.transpileModule(code, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2019 }, }); return out.outputText; } function load(file) { const out = transpile(file); const module = { exports: {} }; const fn = new Function('module', 'exports', out); fn(module, module.exports); return module.exports.default || module.exports; } function flatten(obj, prefix = '') { const keys = new Set(); for (const k of Object.keys(obj)) { const v = obj[k]; const p = prefix ? prefix + '.' + k : k; if (v && typeof v === 'object' && !Array.isArray(v)) { for (const sk of flatten(v, p)) keys.add(sk); } else { keys.add(p); } } return keys; } const zhKeys = flatten(load('src/locales/zh/index.ts')); const enKeys = flatten(load('src/locales/en/index.ts')); console.log('zh keys:', zhKeys.size, 'en keys:', enKeys.size); const dir = 'src'; const exts = ['.tsx', '.ts', '.jsx', '.js']; function walk(d) { let r = []; for (const f of fs.readdirSync(d)) { const p = path.join(d, f); if (fs.statSync(p).isDirectory()) { r = r.concat(walk(p)); } else if (exts.some((e) => f.endsWith(e)) && !p.includes('locales')) { r.push(p); } } return r; } const used = new Set(); const fileKey = {}; for (const f of walk(dir)) { const code = fs.readFileSync(f, 'utf8'); const re = /(?:[^.\w]|^)(?:t|i18n\.t)\(\s*['"]([a-zA-Z0-9_.-]+)['"]/g; let m; while ((m = re.exec(code)) !== null) { const k = m[1]; used.add(k); (fileKey[k] = fileKey[k] || new Set()).add(f); } } console.log('Total distinct keys used in source:', used.size); const missing = [...used].filter((k) => !zhKeys.has(k) || !enKeys.has(k)); console.log('Keys used but missing in locale files:', missing.length); for (const k of missing.sort()) { console.log(' ' + k + ' <- ' + [...(fileKey[k] || [])].join(', ')); } // Also detect literal raw t('...') keys that might be dynamic (skip)