const fs = require('fs'); const path = require('path'); /** * 检查指定文件中使用的 t('...') 翻译键是否都在 JSON 文件中定义 */ function checkMissingTranslations(sourceFile, jsonFile) { // 读取源代码文件 const sourceCode = fs.readFileSync(sourceFile, 'utf-8'); // 读取翻译 JSON 文件 const translations = JSON.parse(fs.readFileSync(jsonFile, 'utf-8')); // 只匹配 t('...') 和 t("...") 和 t(`...`),不包含模板变量 const tRegex = /\bt\(["`']([^"`'${}]+)["`']\)/g; const matches = [...sourceCode.matchAll(tRegex)]; // 获取所有使用的键(去重并清理空白) const usedKeys = [...new Set( matches .map(match => match[1].trim()) .filter(key => key.length > 0) )]; // 查找缺失的键 const missingKeys = usedKeys.filter(key => !(key in translations)); // 查找未使用的键 const definedKeys = Object.keys(translations); const unusedKeys = definedKeys.filter(key => !usedKeys.includes(key)); return { usedKeys: usedKeys.sort(), missingKeys: missingKeys.sort(), unusedKeys: unusedKeys.sort(), totalUsed: usedKeys.length, totalMissing: missingKeys.length, totalUnused: unusedKeys.length, totalDefined: definedKeys.length }; } /** * 递归检查目录中所有文件 */ function checkDirectory(dir, jsonFile) { const results = {}; let totalMissing = 0; function scanDir(directory) { const files = fs.readdirSync(directory); files.forEach(file => { const fullPath = path.join(directory, file); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { scanDir(fullPath); } else if (file.endsWith('.tsx') || file.endsWith('.ts')) { try { const result = checkMissingTranslations(fullPath, jsonFile); if (result.missingKeys.length > 0) { results[fullPath] = result; totalMissing += result.missingKeys.length; } } catch (err) { console.error(`❌ 处理文件出错 ${fullPath}:`, err.message); } } }); } scanDir(dir); return { results, totalMissing }; } // 主程序 const args = process.argv.slice(2); if (args.length === 0) { console.log('📚 翻译键检查工具\n'); console.log('用法:'); console.log(' 检查单个文件: node check-translations.js '); console.log(' 检查整个目录: node check-translations.js --dir '); console.log('\n示例:'); console.log(' node check-translations.js src/components/Jodetail/JodetailSearch.tsx src/i18n/zh/jo.json'); console.log(' node check-translations.js --dir src/components/Jodetail src/i18n/zh/jo.json'); console.log('\n注意:'); console.log(' 只检查 t("key") 调用'); console.log(' ❌ 忽略 alert(), console.log() 等普通字符串'); console.log(' ❌ 忽略模板字符串中的 ${} 变量部分'); process.exit(0); } if (args[0] === '--dir') { // 检查整个目录 const directory = args[1]; const jsonFile = args[2]; console.log(`\n🔍 正在检查目录: ${directory}`); console.log(`📖 使用翻译文件: ${jsonFile}\n`); const { results, totalMissing } = checkDirectory(directory, jsonFile); if (Object.keys(results).length === 0) { console.log(' 太棒了!没有发现缺失的翻译键!'); } else { console.log(`⚠️ 发现 ${Object.keys(results).length} 个文件有缺失的翻译键\n`); // 收集所有缺失的键(去重) const allMissingKeys = new Set(); Object.entries(results).forEach(([file, result]) => { const relativePath = file.replace(process.cwd(), '').replace(/\\/g, '/'); console.log(`\n📄 ${relativePath}`); console.log(` 使用: ${result.totalUsed} 个键 | ❌ 缺失: ${result.totalMissing} 个键`); result.missingKeys.forEach(key => { console.log(` - "${key}"`); allMissingKeys.add(key); }); }); // 输出可以直接复制的 JSON 格式 console.log(`\n\n📋 需要添加到 ${jsonFile} 的翻译键 (共 ${allMissingKeys.size} 个):`); console.log('─'.repeat(60)); allMissingKeys.forEach(key => { console.log(` "${key}": "",`); }); console.log('─'.repeat(60)); } } else { // 检查单个文件 const sourceFile = args[0]; const jsonFile = args[1]; console.log(`\n🔍 正在检查文件: ${sourceFile}`); console.log(`📖 使用翻译文件: ${jsonFile}\n`); try { const result = checkMissingTranslations(sourceFile, jsonFile); console.log(`📊 统计信息:`); console.log(` 代码中使用的键: ${result.totalUsed}`); console.log(` JSON 中定义的键: ${result.totalDefined}`); console.log(` ❌ 缺失的键: ${result.totalMissing}`); console.log(` ⚠️ 未使用的键: ${result.totalUnused}`); if (result.missingKeys.length > 0) { console.log(`\n❌ 缺失的翻译键 (需要添加到 ${jsonFile}):`); console.log('─'.repeat(60)); result.missingKeys.forEach(key => { console.log(` "${key}": "",`); }); console.log('─'.repeat(60)); } else { console.log('\n 太棒了!所有使用的翻译键都已定义!'); } if (result.unusedKeys.length > 0 && result.unusedKeys.length <= 20) { console.log(`\n⚠️ 未使用的翻译键 (在 JSON 中但代码中未使用):`); result.unusedKeys.forEach(key => { console.log(` - "${key}"`); }); } } catch (err) { console.error('❌ 错误:', err.message); process.exit(1); } } console.log('\n'); {/* # 检查单个文件 node check-translations.js src/components/Jodetail/JodetailSearch.tsx src/i18n/zh/jo.json # 检查整个 Jodetail 目录 node check-translations.js --dir src/components/Jodetail src/i18n/zh/jo.json # 检查所有组件 node check-translations.js --dir src/components src/i18n/zh/jo.json */}