import fs from 'node:fs'; import path from 'node:path'; const sourceRoot = path.resolve('src'); const locale = JSON.parse(fs.readFileSync(path.resolve('src/locales/zh-CN.json'), 'utf8')); const files = []; function walk(dir) { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const file = path.join(dir, entry.name); if (entry.isDirectory()) walk(file); else if (/\.(ts|tsx)$/.test(entry.name)) files.push(file); } } function normalizeLegacyKeys(resource) { const legacyEntries = []; const collect = (node, parentPath = []) => { for (const [key, value] of Object.entries(node)) { if (typeof value === 'string' && key.includes('.')) legacyEntries.push([parentPath, key, value]); if (value && typeof value === 'object' && !Array.isArray(value)) collect(value, [...parentPath, key]); } }; const setIfMissing = (segments, value) => { let node = resource; for (const segment of segments.slice(0, -1)) { if (!node[segment] || typeof node[segment] !== 'object') node[segment] = {}; node = node[segment]; } const last = segments.at(-1); if (node[last] === undefined) node[last] = value; }; collect(resource); legacyEntries.forEach(([parentPath, key, value]) => { setIfMissing([...parentPath, ...key.split('.')], value); setIfMissing(key.split('.'), value); }); } function hasKey(resource, key) { return key.split('.').every((segment) => { if (!resource || !Object.hasOwn(resource, segment)) return false; resource = resource[segment]; return true; }); } normalizeLegacyKeys(locale); walk(sourceRoot); const missing = new Map(); const pattern = /(?:\bi18n\.)?t\(\s*['"]([^'"]+)['"]/g; for (const file of files) { const source = fs.readFileSync(file, 'utf8'); for (const match of source.matchAll(pattern)) { const key = match[1]; // 聚焦历史文案工具生成的哈希键,避免新业务文案的翻译建设阻塞构建。 // Logan.Li if (/\.s\d[a-z0-9]{7,}$/i.test(key) && !hasKey(locale, key)) missing.set(key, file); } } if (missing.size > 0) { for (const [key, file] of missing) console.error(`${key}: ${path.relative(process.cwd(), file)}`); process.exit(1); } console.log(`i18n check passed: ${files.length} source files scanned`);