現在越來越多的公司在做出海項目,出海項目首先要解決的就是語言國際化的問題,有很多如l18n、l10n的工具可以用,這些工具可以提供解決方案,但是不能約束開發者的開發行為。開發者仍然可能在代碼中存留沒有做過國際化處理的部分,假設后續 MR 的Code Review也沒有查出來,這就是很大的隱患了,甚至會有監管風險,因為很多項目不想讓別人知道是中國團隊做的,所以要嚴格禁止控制臺輸出中文日志。
我最近也是接到一個這樣的需求,本來需求是讓我人肉檢查一下項目中有沒有輸出中文日志的情況。人肉是不可能人肉的,我覺得這個可以通過寫腳本來實現檢查,因為核心要素其實就兩個點:
- 需要檢查中文
- 需要全局檢查所有文件
這些要素都可以通過 Node.js
腳本來實現。
公眾號:Code程序人生,個人網站:https://creatorblog.cn
這個腳本會實現以下能力:
- 讀取當前項目下所有目錄、所有文件
- 能有效檢查文件中是否包含中文字符
- 可以提前聲明要忽略的文件夾/文件
- 主動忽略所有注釋,包含
//
、/* ... */
、{/* ... */}
- 輸出詳細的檢查結果日志,包含 文件路徑、文件行數、相關代碼,并且高亮檢查出來的中文的部分
在項目根目錄新建一個文件,如check-chinese.js
。寫入以下內容:
const fs = require('fs');
const path = require('path');// 中文字符的正則表達式
const chineseRegex = /[\u4e00-\u9fa5]/;// 要忽略的文件或目錄
const ignorePatterns = ['node_modules','.git','.next','public','locales','dist','build','check-chinese'
];// 要檢查的文件擴展名
const extensions = ['.ts', '.tsx', '.js', '.jsx'];// 控制臺顏色
const colors = {reset: '\x1b[0m',bright: '\x1b[1m',dim: '\x1b[2m',underscore: '\x1b[4m',blink: '\x1b[5m',reverse: '\x1b[7m',hidden: '\x1b[8m',black: '\x1b[30m',red: '\x1b[31m',green: '\x1b[32m',yellow: '\x1b[33m',blue: '\x1b[34m',magenta: '\x1b[35m',cyan: '\x1b[36m',white: '\x1b[37m',bgBlack: '\x1b[40m',bgRed: '\x1b[41m',bgGreen: '\x1b[42m',bgYellow: '\x1b[43m',bgBlue: '\x1b[44m',bgMagenta: '\x1b[45m',bgCyan: '\x1b[46m',bgWhite: '\x1b[47m'
};// 使用第三方庫來解析代碼和注釋
function parseFileContent(content, filePath) {// 首先通過簡單的方式移除所有注釋let processedContent = content;// 1. 移除多行注釋 /* ... */processedContent = processedContent.replace(/\/\*[\s\S]*?\*\//g, match => {// 保留換行符以保持行號return match.replace(/[^\n\r]/g, ' ');});// 2. 移除單行注釋 // ...processedContent = processedContent.replace(/\/\/.*$/gm, match => {// 保留相同長度的空格return ' '.repeat(match.length);});// 3. 移除JSX注釋 {/* ... */}processedContent = processedContent.replace(/\{\/\*[\s\S]*?\*\/\}/g, match => {// 保留換行符以保持行號return match.replace(/[^\n\r]/g, ' ');});return processedContent;
}// 檢查文件中的中文字符
function checkFileForChinese(filePath) {try {const content = fs.readFileSync(filePath, 'utf8');// 處理文件內容,移除注釋const processedContent = parseFileContent(content, filePath);// 按行檢查處理后的內容const lines = processedContent.split('\n');const originalLines = content.split('\n');const results = [];lines.forEach((line, index) => {// 檢查行中是否包含中文字符if (chineseRegex.test(line)) {results.push({file: filePath,line: index + 1,content: originalLines[index].trim()});}});return results;} catch (error) {console.error(`${colors.red}無法讀取文件 ${filePath}: ${error.message}${colors.reset}`);return [];}
}// 遞歸遍歷目錄
function traverseDirectory(dir) {const results = [];try {const files = fs.readdirSync(dir, { withFileTypes: true });for (const file of files) {const fullPath = path.join(dir, file.name);// 檢查是否應該忽略此路徑if (ignorePatterns.some(pattern => fullPath.includes(pattern))) {continue;}if (file.isDirectory()) {results.push(...traverseDirectory(fullPath));} else if (file.isFile() && extensions.includes(path.extname(file.name))) {results.push(...checkFileForChinese(fullPath));}}} catch (error) {console.error(`${colors.red}無法讀取目錄 ${dir}: ${error.message}${colors.reset}`);}return results;
}// 高亮顯示中文字符
function highlightChinese(text) {return text.replace(/([\u4e00-\u9fa5]+)/g, `${colors.bgYellow}${colors.black}$1${colors.reset}`);
}// 主函數
function main() {console.log(`${colors.cyan}${colors.bright}=== 開始掃描中文字符... ===${colors.reset}`);const results = traverseDirectory('.');if (results.length === 0) {console.log(`${colors.green}${colors.bright}? 沒有找到非注釋中的中文字符!${colors.reset}`);} else {console.log(`${colors.yellow}${colors.bright}! 找到 ${results.length} 處可能需要國際化的中文字符:${colors.reset}\n`);// 按文件分組const fileGroups = {};results.forEach(result => {if (!fileGroups[result.file]) {fileGroups[result.file] = [];}fileGroups[result.file].push(result);});// 輸出分組結果Object.keys(fileGroups).forEach(file => {console.log(`${colors.blue}${colors.bright}文件: ${file}${colors.reset}`);fileGroups[file].forEach(result => {console.log(` ${colors.magenta}行 ${result.line}:${colors.reset} ${highlightChinese(result.content)}`);});console.log(''); // 空行分隔不同文件});console.log(`${colors.yellow}${colors.bright}請檢查上述中文字符,并考慮使用國際化方案替換它們。${colors.reset}`);}
}// 執行主函數
main();
然后通過node ./check-chinese.js
來執行腳本
也可以在package.json
中新增一個scripts
{..."scripts": {..."check-chinese": "node ./check-chinese.js"},
}
然后通過npm run check-chinese
來執行腳本