#!/usr/bin/env node /** * i18n-dead-keys.mjs — 精确列出「确定死亡」的字典键,供人工确认后删除。 * * 判定分两档(避免误删用拼接方式消费的键): * DEAD — 键串在 site/ 任何源文件(除 i18n.js)中都不出现,也不作为任何文件的子串。 * REVIEW — 键串只作为更长字符串的一部分出现(典型是 T('前缀') + x 拼出来的), * 必须人工判断,工具不当作可删。 * * 只读:不修改任何文件。 */ import { readFileSync, readdirSync } from 'node:fs'; import { join, extname } from 'node:path'; const ROOT = join(import.meta.dirname, '..', '..'); const SITE = join(ROOT, 'site'); const i18n = readFileSync(join(SITE, 'i18n.js'), 'utf8'); const block = i18n.match(/var\s+EN\s*=\s*\{([\s\S]*?)\n\s*\};/); if (!block) { console.error('FAIL: 没找到 EN 字典块'); process.exit(1); } const keys = []; const re = /^[ \t]*(["'])((?:\\.|(?!\1)[\s\S])*?)\1[ \t]*:/gm; let m; while ((m = re.exec(block[1])) !== null) keys.push(m[2].replace(/\\(["'])/g, '$1')); /* 消费方:site 下的 js / html(i18n.js 本身除外);changelog.json、data.json 是构建产物, 其中的中文是历史记录,不代表 i18n 键被消费,故不纳入。 */ const consumers = readdirSync(SITE) .filter((f) => (extname(f) === '.js' && f !== 'i18n.js') || extname(f) === '.html') .map((f) => readFileSync(join(SITE, f), 'utf8')) .join('\n'); const dead = []; const review = []; for (const key of keys) { if (consumers.includes(key)) continue; // 精确命中:在用 /* 是否作为更长字符串的一部分出现 —— 拼接消费的迹象 */ const probe = key.length > 8 ? key.slice(0, 8) : key; if (consumers.includes(probe)) review.push(key); // 疑似拼接消费,人工判断 else dead.push(key); } console.log(`字典 ${keys.length} 键 · 确定死亡 ${dead.length} · 待人工判断 ${review.length}\n`); console.log('=== DEAD(键串在 site/ 源文件中完全不出现)==='); for (const k of dead) console.log(` ${k}`); console.log('\n=== REVIEW(疑似以拼接方式消费,不要自动删)==='); for (const k of review) console.log(` ${k}`);