committed by
GitHub
3 changed files with 594 additions and 10 deletions
@ -0,0 +1,588 @@ |
|||||
|
/** |
||||
|
* i18n 移除脚本 - 将国际化的文本替换为中文,移除 i18n 相关代码 |
||||
|
* 执行 npm run i18n-remove 运行此脚本 |
||||
|
* |
||||
|
* 此操作不可逆,会执行以下变更: |
||||
|
* - 读取 src/locales/zh-CN 的翻译映射 |
||||
|
* - 替换 intl.formatMessage / useIntl().formatMessage 调用为中文硬编码 |
||||
|
* - 替换 <FormattedMessage> 组件为中文文本 |
||||
|
* - 移除 useIntl、FormattedMessage、SelectLang、getLocale 的 import 和使用 |
||||
|
* - 从 config/config.ts 中移除 locale 配置 |
||||
|
* - 将 routes.ts 中的 name 值替换为中文菜单名 |
||||
|
* - 删除 src/locales/ 目录 |
||||
|
*/ |
||||
|
|
||||
|
const fs = require('node:fs'); |
||||
|
const path = require('node:path'); |
||||
|
|
||||
|
const I18N_SYMBOLS = [ |
||||
|
'useIntl', |
||||
|
'FormattedMessage', |
||||
|
'SelectLang', |
||||
|
'getLocale', |
||||
|
'getAllLocales', |
||||
|
'setLocale', |
||||
|
]; |
||||
|
const FORMAT_MESSAGE_PATTERNS = [ |
||||
|
'intl.formatMessage(', |
||||
|
'useIntl().formatMessage(', |
||||
|
]; |
||||
|
const QSTR = `'((?:[^'\\\\]|\\\\.)*)'|"((?:[^"\\\\]|\\\\.)*)"`; |
||||
|
|
||||
|
// ─── 工具函数 ───────────────────────────────────────────
|
||||
|
|
||||
|
function readDirRecursive(dir) { |
||||
|
const results = []; |
||||
|
if (!fs.existsSync(dir)) return results; |
||||
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { |
||||
|
const full = path.join(dir, entry.name); |
||||
|
if (entry.isDirectory()) { |
||||
|
results.push(...readDirRecursive(full)); |
||||
|
} else { |
||||
|
results.push(full); |
||||
|
} |
||||
|
} |
||||
|
return results; |
||||
|
} |
||||
|
|
||||
|
function toStrLiteral(text) { |
||||
|
return `'${text |
||||
|
.replace(/\\/g, '\\\\') |
||||
|
.replace(/'/g, "\\'") |
||||
|
.replace(/\n/g, '\\n') |
||||
|
.replace(/\r/g, '\\r') |
||||
|
.replace(/\u2028/g, '\\u2028') |
||||
|
.replace(/\u2029/g, '\\u2029')}'`;
|
||||
|
} |
||||
|
|
||||
|
function resolveText(id, defaultMsg, localeMap) { |
||||
|
return localeMap[id] || defaultMsg || id; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 找到括号匹配的闭合位置 |
||||
|
* @param {string} str - 源字符串 |
||||
|
* @param {number} openIdx - 开括号 ( 的位置 |
||||
|
* @returns {number} 闭括号 ) 的位置,-1 表示未找到 |
||||
|
*/ |
||||
|
function findClosingParen(str, openIdx) { |
||||
|
let depth = 0; |
||||
|
let i = openIdx; |
||||
|
while (i < str.length) { |
||||
|
const ch = str[i]; |
||||
|
if (ch === '(') depth++; |
||||
|
else if (ch === ')') { |
||||
|
depth--; |
||||
|
if (depth === 0) return i; |
||||
|
} else if (ch === "'" || ch === '"' || ch === '`') { |
||||
|
// skip string literal
|
||||
|
const quote = ch; |
||||
|
i++; |
||||
|
while (i < str.length && str[i] !== quote) { |
||||
|
if (str[i] === '\\') i++; // skip escaped char
|
||||
|
i++; |
||||
|
} |
||||
|
} else if (ch === '/' && str[i + 1] === '/') { |
||||
|
// skip single-line comment
|
||||
|
while (i < str.length && str[i] !== '\n') i++; |
||||
|
} else if (ch === '/' && str[i + 1] === '*') { |
||||
|
// skip block comment
|
||||
|
i += 2; |
||||
|
while (i < str.length - 1 && !(str[i] === '*' && str[i + 1] === '/')) i++; |
||||
|
i++; // skip past */
|
||||
|
} |
||||
|
i++; |
||||
|
} |
||||
|
return -1; |
||||
|
} |
||||
|
|
||||
|
// ─── Step 1: 构建 zh-CN 翻译映射 ────────────────────────
|
||||
|
|
||||
|
function buildLocaleMap() { |
||||
|
const localeDir = path.join('src', 'locales'); |
||||
|
const zhCNDir = path.join(localeDir, 'zh-CN'); |
||||
|
const files = readDirRecursive(zhCNDir).filter((f) => f.endsWith('.ts')); |
||||
|
const mainFile = path.join(localeDir, 'zh-CN.ts'); |
||||
|
const localeMap = {}; |
||||
|
|
||||
|
for (const file of files) { |
||||
|
const content = fs.readFileSync(file, 'utf-8'); |
||||
|
extractLocaleKeys(content, localeMap); |
||||
|
} |
||||
|
if (fs.existsSync(mainFile)) { |
||||
|
const content = fs.readFileSync(mainFile, 'utf-8'); |
||||
|
extractLocaleKeys(content, localeMap); |
||||
|
} |
||||
|
|
||||
|
return localeMap; |
||||
|
} |
||||
|
|
||||
|
function extractLocaleKeys(content, localeMap) { |
||||
|
// Match key: 'value' or key: "value", supporting escaped quotes inside values
|
||||
|
const regex = |
||||
|
/['"]([a-zA-Z0-9_.-]+)['"]\s*:\s*'((?:[^'\\]|\\.)*)'|['"]([a-zA-Z0-9_.-]+)['"]\s*:\s*"((?:[^"\\]|\\.)*)"/g; |
||||
|
let match = regex.exec(content); |
||||
|
while (match !== null) { |
||||
|
const key = match[1] || match[3]; |
||||
|
const value = match[2] || match[4]; |
||||
|
localeMap[key] = value; |
||||
|
match = regex.exec(content); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// ─── Step 2: 获取 menu 映射 ─────────────────────────────
|
||||
|
|
||||
|
function buildMenuMap(localeMap) { |
||||
|
const menuMap = {}; |
||||
|
for (const [key, value] of Object.entries(localeMap)) { |
||||
|
if (key.startsWith('menu.')) { |
||||
|
menuMap[key.slice(5)] = value; |
||||
|
} |
||||
|
} |
||||
|
return menuMap; |
||||
|
} |
||||
|
|
||||
|
// ─── Step 3: 替换路由文件中的 name ──────────────────────
|
||||
|
|
||||
|
function replaceRoutes(menuMap) { |
||||
|
const configDir = path.join('config'); |
||||
|
if (!fs.existsSync(configDir)) { |
||||
|
console.log('- config/ 目录不存在,跳过'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const routeFiles = fs |
||||
|
.readdirSync(configDir) |
||||
|
.filter((f) => f.startsWith('routes') && f.endsWith('.ts')) |
||||
|
.map((f) => path.join(configDir, f)); |
||||
|
|
||||
|
for (const routesPath of routeFiles) { |
||||
|
let content = fs.readFileSync(routesPath, 'utf-8'); |
||||
|
let modified = false; |
||||
|
|
||||
|
content = content.replace(/name:\s*['"]([^'"]+)['"]/g, (match, name) => { |
||||
|
const zhValue = menuMap[name]; |
||||
|
if (zhValue) { |
||||
|
modified = true; |
||||
|
return `name: ${toStrLiteral(zhValue)}`; |
||||
|
} |
||||
|
return match; |
||||
|
}); |
||||
|
|
||||
|
if (modified) { |
||||
|
fs.writeFileSync(routesPath, content); |
||||
|
console.log( |
||||
|
`✓ 已替换 ${path.relative('.', routesPath)} 中的路由名称为中文`, |
||||
|
); |
||||
|
} else { |
||||
|
console.log(`- ${path.relative('.', routesPath)} 无需替换`); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// ─── Step 4: 从 config/config.ts 移除 locale 配置 ────────
|
||||
|
|
||||
|
function removeLocaleConfig() { |
||||
|
const configPath = path.join('config', 'config.ts'); |
||||
|
if (!fs.existsSync(configPath)) { |
||||
|
console.log('- config/config.ts 不存在,跳过'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
let content = fs.readFileSync(configPath, 'utf-8'); |
||||
|
const original = content; |
||||
|
|
||||
|
// 移除 locale 块及其紧邻的 JSDoc 注释(只匹配国际化相关的注释)
|
||||
|
content = content.replace( |
||||
|
/\n\s*\/\*\*[^*]*\*+(?:[^/*][^*]*\*+)*\/\n\s*locale:\s*\{[^}]*\},?\n/g, |
||||
|
'\n', |
||||
|
); |
||||
|
|
||||
|
// 移除无注释的 locale: {...} 配置块
|
||||
|
content = content.replace(/\n\s*locale:\s*\{[^}]*\},?\n/g, '\n'); |
||||
|
|
||||
|
// 移除 layout 中的 locale: true
|
||||
|
content = content.replace(/\n\s*locale:\s*true,?\n/g, '\n'); |
||||
|
|
||||
|
if (content !== original) { |
||||
|
fs.writeFileSync(configPath, content); |
||||
|
console.log('✓ 已从 config/config.ts 移除 locale 配置'); |
||||
|
} else { |
||||
|
console.log('- config/config.ts 无需修改'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// ─── Step 5: 替换源文件中的 i18n 调用 ──────────────────
|
||||
|
|
||||
|
function processSourceFiles(localeMap) { |
||||
|
const srcDir = path.join('src'); |
||||
|
const files = readDirRecursive(srcDir).filter((f) => { |
||||
|
const ext = path.extname(f); |
||||
|
return ( |
||||
|
['.tsx', '.ts', '.jsx', '.js'].includes(ext) && |
||||
|
!f.includes('locales') && |
||||
|
!f.includes('.umi') && |
||||
|
!f.includes('.umi-production') && |
||||
|
!f.includes('.umi-test') && |
||||
|
!f.endsWith('.d.ts') |
||||
|
); |
||||
|
}); |
||||
|
|
||||
|
for (const file of files) { |
||||
|
processFile(file, localeMap); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function processFile(filePath, localeMap) { |
||||
|
let content = fs.readFileSync(filePath, 'utf-8'); |
||||
|
const original = content; |
||||
|
|
||||
|
if ( |
||||
|
!content.includes('formatMessage') && |
||||
|
!content.includes('FormattedMessage') && |
||||
|
!content.includes('useIntl') && |
||||
|
!content.includes('SelectLang') && |
||||
|
!content.includes('getLocale') && |
||||
|
!content.includes('getAllLocales') && |
||||
|
!content.includes('setLocale') |
||||
|
) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// ── 1. 替换 formatMessage 调用(使用括号匹配精确处理)
|
||||
|
// 只替换 id 为字符串字面量且无第二参数的简单调用
|
||||
|
content = replaceFormatMessageCalls(content, localeMap); |
||||
|
|
||||
|
// ── 2. 替换 <FormattedMessage ... /> (使用精确匹配)
|
||||
|
// 只替换 id 为字符串字面量的调用
|
||||
|
content = replaceFormattedMessageComponents(content, localeMap); |
||||
|
|
||||
|
// ── 3. 移除 const intl = useIntl() 声明
|
||||
|
// 只在文件中不再有其他 intl. 引用时移除
|
||||
|
if (!content.match(/\bintl\./)) { |
||||
|
content = content.replace( |
||||
|
/\n\s*\/\*\*[\s\S]*?\*\/\n\s*const\s+intl\s*=\s*useIntl\(\)\s*;?\n/g, |
||||
|
'\n', |
||||
|
); |
||||
|
content = content.replace( |
||||
|
/\n\s*const\s+intl\s*=\s*useIntl\(\)\s*;?\n/g, |
||||
|
'\n', |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// ── 4. 移除 SelectLang 使用
|
||||
|
content = content.replace( |
||||
|
/\n?\s*\{SelectLang\s*&&\s*<SelectLang\s*\/>\s*\}/g, |
||||
|
'', |
||||
|
); |
||||
|
|
||||
|
// ── 5. 处理 getLocale() 调用
|
||||
|
content = content.replace( |
||||
|
/const\s+(\w+)\s*=\s*getLocale\(\)\s*;?/g, |
||||
|
(_match, varName) => { |
||||
|
return `const ${varName} = 'zh-CN';`; |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
// ── 5b. 处理 getAllLocales() 调用
|
||||
|
content = content.replace( |
||||
|
/useMemo\(\(\)\s*=>\s*getAllLocales\(\),\s*\[\]\)/g, |
||||
|
"['zh-CN']", |
||||
|
); |
||||
|
content = content.replace( |
||||
|
/const\s+(\w+)\s*=\s*getAllLocales\(\)\s*;?/g, |
||||
|
(_match, varName) => { |
||||
|
return `const ${varName} = ['zh-CN'];`; |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
// ── 5c. 移除 setLocale() 调用(使用括号匹配处理嵌套括号)
|
||||
|
content = removeSetLocaleCalls(content); |
||||
|
|
||||
|
// ── 6. 移除 data-lang 属性
|
||||
|
content = content.replace( |
||||
|
/\s*data-lang(?:\s*=\s*(?:"[^"]*"|'[^']*'|\{[^}]*\}))?/g, |
||||
|
'', |
||||
|
); |
||||
|
|
||||
|
// ── 7. 移除 Lang 组件调用和定义
|
||||
|
content = content.replace(/\n?\s*<Lang\s*\/>/g, ''); |
||||
|
content = content.replace( |
||||
|
/const Lang = \(\) => \{\s*\n\s*const \{ styles \} = useStyles\(\);\s*\n\s*return \(\s*\n\s*<div className=\{styles\.lang\}>\s*\n\s*<\/div>\s*\n\s*\);\s*\n\s*\};\n?/g, |
||||
|
'', |
||||
|
); |
||||
|
|
||||
|
// ── 8. 清理 import 语句
|
||||
|
// 只移除文件中不再使用的 i18n 相关符号
|
||||
|
content = content.replace( |
||||
|
/import\s*\{([^}]*)\}\s*from\s*['"]@umijs\/max['"];?\n?/g, |
||||
|
(match, imports) => { |
||||
|
const codeWithoutImport = content.replace(match, ''); |
||||
|
const items = imports |
||||
|
.split(',') |
||||
|
.map((s) => s.trim()) |
||||
|
.filter((s) => { |
||||
|
if (!s || !I18N_SYMBOLS.includes(s)) return true; |
||||
|
const regex = new RegExp(`\\b${s}\\b`); |
||||
|
return regex.test(codeWithoutImport); |
||||
|
}); |
||||
|
|
||||
|
if (items.length === 0) { |
||||
|
return ''; |
||||
|
} |
||||
|
|
||||
|
return `import { ${items.join(', ')} } from '@umijs/max';\n`; |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
// ── 9. 简化 JSX 文本子节点中的 {'中文'} → 中文
|
||||
|
content = content.replace(/\{'([^']*)'\}/g, (match, text, offset, str) => { |
||||
|
const before = str.slice(Math.max(0, offset - 30), offset); |
||||
|
const charBeforeBrace = before.trimEnd().slice(-1); |
||||
|
// 不简化: JSX 属性(=)、模板字符串($)、函数参数/数组/表达式((,[,,)
|
||||
|
if ( |
||||
|
charBeforeBrace === '=' || |
||||
|
charBeforeBrace === '$' || |
||||
|
charBeforeBrace === '(' || |
||||
|
charBeforeBrace === ',' || |
||||
|
charBeforeBrace === '[' |
||||
|
) { |
||||
|
return match; |
||||
|
} |
||||
|
return text; |
||||
|
}); |
||||
|
|
||||
|
// ── 10. 简化 JSX 文本子节点中 FormattedMessage 产生的 '中文' → 中文
|
||||
|
content = content.replace( |
||||
|
/>(\s*)'([^'<]*?)'(\s*)<\//g, |
||||
|
(_match, ws1, text, ws2) => { |
||||
|
return `>${ws1}${text}${ws2}</`; |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
// ── 11. 清理残留
|
||||
|
content = content.replace(/^\s*;\s*$/gm, ''); |
||||
|
content = content.replace(/\n{3,}/g, '\n\n'); |
||||
|
content = `${content.trimEnd()}\n`; |
||||
|
|
||||
|
if (content !== original) { |
||||
|
fs.writeFileSync(filePath, content); |
||||
|
const relPath = path.relative('.', filePath); |
||||
|
console.log(`✓ 已处理: ${relPath}`); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 替换 intl.formatMessage(...) 和 useIntl().formatMessage(...) |
||||
|
* 使用括号匹配来精确找到完整的函数调用 |
||||
|
*/ |
||||
|
function replaceFormatMessageCalls(content, localeMap) { |
||||
|
const patterns = FORMAT_MESSAGE_PATTERNS; |
||||
|
|
||||
|
for (const pattern of patterns) { |
||||
|
let searchFrom = 0; |
||||
|
while (true) { |
||||
|
const idx = content.indexOf(pattern, searchFrom); |
||||
|
if (idx === -1) break; |
||||
|
|
||||
|
const openParenIdx = idx + pattern.length - 1; // position of (
|
||||
|
const closeParenIdx = findClosingParen(content, openParenIdx); |
||||
|
|
||||
|
if (closeParenIdx === -1) { |
||||
|
searchFrom = idx + 1; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
const fullCall = content.slice(idx, closeParenIdx + 1); |
||||
|
|
||||
|
// 检查 id 是否为字符串字面量(支持含转义引号的值)
|
||||
|
const idMatch = fullCall.match(new RegExp(`id:\\s*${QSTR}`)); |
||||
|
if (!idMatch) { |
||||
|
searchFrom = closeParenIdx + 1; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
const id = idMatch[1] || idMatch[2]; |
||||
|
const dmMatch = fullCall.match(new RegExp(`defaultMessage:\\s*${QSTR}`)); |
||||
|
const defaultMsg = dmMatch ? (dmMatch[1] || dmMatch[2]) : ''; |
||||
|
|
||||
|
// 检查是否有第二参数(ICU 格式化参数),如果有则跳过
|
||||
|
// 第一参数结束位置: 找到第一个 },{ 或 },\s*{
|
||||
|
const firstArgEnd = fullCall.indexOf('},'); |
||||
|
if (firstArgEnd !== -1 && firstArgEnd < fullCall.length - 2) { |
||||
|
// 有第二参数 — 无法静态替换,跳过
|
||||
|
console.log(` ⚠ 跳过含 ICU 参数的调用: ${id}`); |
||||
|
searchFrom = closeParenIdx + 1; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
const zhText = toStrLiteral(resolveText(id, defaultMsg, localeMap)); |
||||
|
content = |
||||
|
content.slice(0, idx) + zhText + content.slice(closeParenIdx + 1); |
||||
|
|
||||
|
searchFrom = idx + zhText.length; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return content; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 替换 <FormattedMessage id="xxx" defaultMessage="yyy" /> |
||||
|
* 只替换 id 为字符串字面量的组件 |
||||
|
*/ |
||||
|
function replaceFormattedMessageComponents(content, localeMap) { |
||||
|
let searchFrom = 0; |
||||
|
const pattern = '<FormattedMessage'; |
||||
|
|
||||
|
while (true) { |
||||
|
const idx = content.indexOf(pattern, searchFrom); |
||||
|
if (idx === -1) break; |
||||
|
|
||||
|
const endPattern = '/>'; |
||||
|
let endIdx = content.indexOf(endPattern, idx); |
||||
|
if (endIdx === -1) break; |
||||
|
endIdx += endPattern.length; // include />
|
||||
|
|
||||
|
const fullTag = content.slice(idx, endIdx); |
||||
|
|
||||
|
const idMatch = fullTag.match(new RegExp(`id=${QSTR}`)); |
||||
|
if (!idMatch) { |
||||
|
searchFrom = endIdx; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
const id = idMatch[1] || idMatch[2]; |
||||
|
const dmMatch = fullTag.match(new RegExp(`defaultMessage=${QSTR}`)); |
||||
|
const defaultMsg = dmMatch ? (dmMatch[1] || dmMatch[2]) : ''; |
||||
|
|
||||
|
const zhText = resolveText(id, defaultMsg, localeMap); |
||||
|
const replacement = toStrLiteral(zhText); |
||||
|
content = content.slice(0, idx) + replacement + content.slice(endIdx); |
||||
|
searchFrom = idx + replacement.length; |
||||
|
} |
||||
|
|
||||
|
return content; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 移除 setLocale() 调用,使用括号匹配处理嵌套括号 |
||||
|
*/ |
||||
|
function removeSetLocaleCalls(content) { |
||||
|
const pattern = 'setLocale('; |
||||
|
let searchFrom = 0; |
||||
|
while (true) { |
||||
|
const idx = content.indexOf(pattern, searchFrom); |
||||
|
if (idx === -1) break; |
||||
|
|
||||
|
const openParenIdx = idx + pattern.length - 1; |
||||
|
const closeParenIdx = findClosingParen(content, openParenIdx); |
||||
|
if (closeParenIdx === -1) { |
||||
|
searchFrom = idx + 1; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// Remove the entire call including trailing semicolon
|
||||
|
let endIdx = closeParenIdx + 1; |
||||
|
if (content[endIdx] === ';') endIdx++; |
||||
|
|
||||
|
content = content.slice(0, idx) + content.slice(endIdx); |
||||
|
searchFrom = idx; |
||||
|
} |
||||
|
return content; |
||||
|
} |
||||
|
|
||||
|
// ─── Step 6: 删除 locales 目录 ─────────────────────────
|
||||
|
|
||||
|
function deleteLocalesDir() { |
||||
|
const localeDir = path.join('src', 'locales'); |
||||
|
fs.rmSync(localeDir, { recursive: true, force: true }); |
||||
|
console.log('✓ 已删除 src/locales/ 目录'); |
||||
|
} |
||||
|
|
||||
|
// ─── 残留检查 ────────────────────────────────────────────
|
||||
|
|
||||
|
function checkResiduals() { |
||||
|
const residualSymbols = ['getLocale', 'getAllLocales', 'setLocale']; |
||||
|
const srcDir = path.join('src'); |
||||
|
const files = readDirRecursive(srcDir).filter((f) => { |
||||
|
const ext = path.extname(f); |
||||
|
return ( |
||||
|
['.tsx', '.ts', '.jsx', '.js'].includes(ext) && |
||||
|
!f.includes('.umi') && |
||||
|
!f.includes('.umi-production') && |
||||
|
!f.includes('.umi-test') && |
||||
|
!f.endsWith('.d.ts') |
||||
|
); |
||||
|
}); |
||||
|
|
||||
|
let found = false; |
||||
|
for (const file of files) { |
||||
|
const content = fs.readFileSync(file, 'utf-8'); |
||||
|
for (const sym of residualSymbols) { |
||||
|
const regex = new RegExp(`\\b${sym}\\b`); |
||||
|
const match = regex.exec(content); |
||||
|
if (match) { |
||||
|
const relPath = path.relative('.', file); |
||||
|
const line = content.slice(0, match.index).split('\n').length; |
||||
|
console.log(` ⚠ ${relPath}:${line} 残留 ${sym} 调用,请手动检查`); |
||||
|
found = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!found) { |
||||
|
console.log('✓ 无残留 i18n 调用'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// ─── Step 7: 从 package.json 移除 i18n-remove 脚本 ──────
|
||||
|
|
||||
|
function updatePackageJson() { |
||||
|
const pkgPath = 'package.json'; |
||||
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); |
||||
|
|
||||
|
if (pkg.scripts?.['i18n-remove']) { |
||||
|
delete pkg.scripts['i18n-remove']; |
||||
|
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`); |
||||
|
console.log('✓ 已从 package.json 移除 i18n-remove 脚本'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// ─── 主函数 ─────────────────────────────────────────────
|
||||
|
|
||||
|
function main() { |
||||
|
console.log('========================================'); |
||||
|
console.log(' 开始执行 i18n 移除脚本'); |
||||
|
console.log('========================================\n'); |
||||
|
|
||||
|
console.log('>>> 构建中文翻译映射...'); |
||||
|
const localeMap = buildLocaleMap(); |
||||
|
const menuMap = buildMenuMap(localeMap); |
||||
|
console.log(`✓ 已加载 ${Object.keys(localeMap).length} 条翻译映射`); |
||||
|
|
||||
|
console.log('\n>>> 替换路由名称为中文...'); |
||||
|
replaceRoutes(menuMap); |
||||
|
|
||||
|
console.log('\n>>> 移除 config/config.ts 中的 locale 配置...'); |
||||
|
removeLocaleConfig(); |
||||
|
|
||||
|
console.log('\n>>> 替换源文件中的 i18n 调用...'); |
||||
|
processSourceFiles(localeMap); |
||||
|
|
||||
|
console.log('\n>>> 删除 locales 目录...'); |
||||
|
deleteLocalesDir(); |
||||
|
|
||||
|
console.log('\n>>> 更新 package.json...'); |
||||
|
updatePackageJson(); |
||||
|
|
||||
|
console.log('\n>>> 检查残留 i18n 调用...'); |
||||
|
checkResiduals(); |
||||
|
|
||||
|
console.log('\n========================================'); |
||||
|
console.log(' i18n 移除完成!'); |
||||
|
console.log(' 请运行 npm install 并检查代码'); |
||||
|
console.log('========================================'); |
||||
|
} |
||||
|
|
||||
|
main(); |
||||
Loading…
Reference in new issue