|
| 1 | +/** |
| 2 | + * Language Pack Diff Checker Script |
| 3 | + * |
| 4 | + * Purpose: |
| 5 | + * Compare src/lang/locale/en.ts and a target language file (e.g. zh-cn.ts), output missing and extra keys. |
| 6 | + * Output is color-coded: |
| 7 | + * - Green: Key exists in en.ts but missing in the target language file |
| 8 | + * - Red: Key exists in the target language file but not in en.ts |
| 9 | + * - Yellow: The same key exists in both files but the line numbers are different |
| 10 | + * |
| 11 | + * Dependencies: |
| 12 | + * Node.js environment, no extra dependencies required. |
| 13 | + * |
| 14 | + * Usage: |
| 15 | + * 1. Open a terminal in this file's directory (or project root). |
| 16 | + * 2. Run: |
| 17 | + * node src/lang/langDiffWithEn.js zh-cn |
| 18 | + * # or node src/lang/langDiffWithEn.js ja |
| 19 | + * # The argument is the language file name (without extension), default is zh-cn |
| 20 | + * |
| 21 | + * Output: |
| 22 | + * + (green): Key exists in en.ts but missing in the target language file |
| 23 | + * - (red): Key exists in the target language file but not in en.ts |
| 24 | + * ! (yellow): The same key exists in both files but the line numbers are different |
| 25 | + * If there is no output, the two files have identical keys. |
| 26 | + */ |
| 27 | +/** |
| 28 | + * 语言包差异检测脚本 |
| 29 | + * |
| 30 | + * 用途: |
| 31 | + * 比较 src/lang/locale/en.ts 与指定语言文件(如 zh-cn.ts)中的 key,输出缺失和多余的 key。 |
| 32 | + * 输出内容会用不同颜色区分: |
| 33 | + * - 绿色:en.ts 有但目标语言缺失的 key |
| 34 | + * - 红色:目标语言有但 en.ts 没有的 key |
| 35 | + * - 黄色:相同的 key 所在的行号不相同 |
| 36 | + * |
| 37 | + * 依赖: |
| 38 | + * Node.js 环境,无需额外依赖。 |
| 39 | + * |
| 40 | + * 用法: |
| 41 | + * 1. 在命令行进入本文件所在目录(或项目根目录)。 |
| 42 | + * 2. 运行: |
| 43 | + * node src/lang/langDiffWithEn.js zh-cn |
| 44 | + * # 或 node src/lang/langDiffWithEn.js ja |
| 45 | + * # 参数为语言文件名(不带扩展名),默认为 zh-cn |
| 46 | + * |
| 47 | + * 输出说明: |
| 48 | + * + 开头(绿色):en.ts 有但目标语言缺失的 key |
| 49 | + * - 开头(红色):目标语言有但 en.ts 没有的 key |
| 50 | + * ! 开头(黄色):相同的 key 所在的行号不相同 |
| 51 | + * 若无输出则表示两个文件 key 完全一致。 |
| 52 | + */ |
| 53 | + |
| 54 | +const fs = require("fs"); |
| 55 | +const path = require("path"); |
| 56 | + |
| 57 | +/** |
| 58 | + * 读取 locale 文件夹下指定语言和 en.ts 文件,逐行解析键值和行号 |
| 59 | + * @param lang 语言文件名(不含扩展名),如 zh-cn |
| 60 | + * @returns { en: Record<string, {value: string, line: number}>, lang: Record<string, {value: string, line: number}> } |
| 61 | + */ |
| 62 | +function readLocaleFiles(lang = "zh-cn") { |
| 63 | + // 构造 locale 目录路径 |
| 64 | + const localeDir = path.join(__dirname, "./locale"); |
| 65 | + // 获取 en.ts 文件路径 |
| 66 | + const enFile = path.join(localeDir, "en.ts"); |
| 67 | + // 获取指定语言文件路径 |
| 68 | + const langFile = path.join(localeDir, `${lang}.ts`); |
| 69 | + /** |
| 70 | + * 解析指定的语言文件,提取每个 key 的值和所在行号 |
| 71 | + * @param file 文件路径 |
| 72 | + * @returns {Record<string, {value: string, line: number}>} |
| 73 | + */ |
| 74 | + const parse = (file) => { |
| 75 | + // 读取文件内容并按行分割 |
| 76 | + const lines = fs.readFileSync(file, "utf8").split(/\r?\n/); |
| 77 | + const result = {}; |
| 78 | + let inExport = false; |
| 79 | + for (let i = 0; i < lines.length; i++) { |
| 80 | + const line = lines[i]; |
| 81 | + // 检查是否进入 export default 块 |
| 82 | + if (!inExport && line.includes("export default")) { |
| 83 | + inExport = true; |
| 84 | + continue; |
| 85 | + } |
| 86 | + if (!inExport) continue; |
| 87 | + // 匹配 key: "value", 格式的行 |
| 88 | + const m = line.match(/^\s*([A-Z0-9_]+)\s*:\s*.*?,?$/); |
| 89 | + if (m) { |
| 90 | + let key = m[1]; |
| 91 | + // 保存 key 及其所在行号 |
| 92 | + result[key] = i + 1; |
| 93 | + } |
| 94 | + } |
| 95 | + return result; |
| 96 | + }; |
| 97 | + // 返回 en 和指定语言的解析结果 |
| 98 | + return { |
| 99 | + en: parse(enFile), |
| 100 | + lang: parse(langFile), |
| 101 | + }; |
| 102 | +} |
| 103 | + |
| 104 | +/** |
| 105 | + * 比较 en 和 lang 的 key/value,输出差异 |
| 106 | + */ |
| 107 | + |
| 108 | +function diffLang(lang = "zh-cn") { |
| 109 | + const { en, lang: l } = readLocaleFiles(lang); |
| 110 | + const allKeys = new Set([...Object.keys(en), ...Object.keys(l)]); |
| 111 | + const diffs = []; |
| 112 | + for (const key of allKeys) { |
| 113 | + if (!(key in en)) { |
| 114 | + diffs.push(`- ${key}: ${l[key]}`); |
| 115 | + } else if (!(key in l)) { |
| 116 | + diffs.push(`+ ${key}: ${en[key]}`); |
| 117 | + } else { |
| 118 | + if (en[key] !== l[key]) { |
| 119 | + diffs.push(`! ${key}: en: ${en[key]} <--> lang: ${l[key]}`); |
| 120 | + } |
| 121 | + } |
| 122 | + } |
| 123 | + return diffs; |
| 124 | +} |
| 125 | + |
| 126 | +// CLI |
| 127 | +if (require.main === module) { |
| 128 | + const lang = process.argv[2] || "zh-cn"; |
| 129 | + const diffs = diffLang(lang); |
| 130 | + if (diffs.length === 0) { |
| 131 | + console.log(`语言文件 ${lang} 与 en.ts 无差异`); |
| 132 | + } else { |
| 133 | + diffs.forEach((line) => { |
| 134 | + if (line.startsWith("+")) { |
| 135 | + // 绿色 |
| 136 | + console.log("\x1b[32m%s\x1b[0m", line); |
| 137 | + } else if (line.startsWith("-")) { |
| 138 | + // 红色 |
| 139 | + console.log("\x1b[31m%s\x1b[0m", line); |
| 140 | + } else if (line.startsWith("!")) { |
| 141 | + // 黄色 |
| 142 | + console.log("\x1b[33m%s\x1b[0m", line); |
| 143 | + } else { |
| 144 | + // 默认 |
| 145 | + console.log(line); |
| 146 | + } |
| 147 | + }); |
| 148 | + } |
| 149 | +} |
0 commit comments