Files
aiops/frontend/scripts/check-i18n-keys.mjs
T
Logan.Li fd60361d93 chore: 统一本地服务器与远程仓库源码
- 合并 main 与 master 历史并以服务器结构为基线
- 保留迁移、数据保留、AWS 账单与部署安全能力
- 建立 SDD 规格和 CI 验证闭环
2026-08-05 20:03:25 +08:00

66 lines
2.2 KiB
JavaScript

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`);