const fs = require('fs'); const path = require('path'); const root = path.join(__dirname, '..'); const js = fs.readFileSync(path.join(root, 'assets/js/main.js'), 'utf8'); const mapMatch = js.match(/const textMap = \{([\s\S]*?)\n \};/); const keys = new Set(); const re = /^\s*,?'((?:\\'|[^'])*)':/gm; let m; while ((m = re.exec(mapMatch[1]))) { keys.add(m[1].replace(/\\'/g, "'")); } const files = []; function walk(dir) { for (const f of fs.readdirSync(dir)) { const p = path.join(dir, f); if (fs.statSync(p).isDirectory()) { if (f === 'vendor' || f === 'node_modules' || f === 'scripts') continue; walk(p); } else if (f.endsWith('.html')) files.push(p); } } walk(root); const missingKeys = new Set(); const missingLeaf = new Set(); for (const file of files) { const html = fs.readFileSync(file, 'utf8'); const i18nKeys = [...html.matchAll(/data-i18n-key="([^"]+)"/g)].map((x) => x[1]); for (const t of i18nKeys) { if (!keys.has(t)) missingKeys.add(t); } const stripped = html .replace(//gi, '') .replace(//gi, ''); const leaf = [...stripped.matchAll(/>([^<>]{2,140}) x[1].replace(/\s+/g, ' ').trim()) .filter((t) => /^[A-Za-z0-9]/.test(t) && !/^\d+$/.test(t)); for (const t of new Set(leaf)) { if (t.includes('http') || t.includes('assets/') || t.includes('©') || t.includes('@')) continue; if (!keys.has(t) && !i18nKeys.includes(t)) missingLeaf.add(t); } } console.log('=== data-i18n-key not in textMap ==='); [...missingKeys].sort().forEach((k) => console.log(k)); console.log('\n=== leaf text not in textMap (sample) ==='); [...missingLeaf].sort().slice(0, 60).forEach((k) => console.log(k));