wangzhibo
2026-08-30 c8515170fd399b4d2dfaebb59128d4674e58915e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
const fs = require('fs');
const path = require('path');
 
const rootDir = path.resolve(__dirname, '..');
const locales = [
  { name: 'zh_CN', file: 'src/i18n/zh_CN.js' },
  { name: 'zh_TW', file: 'src/i18n/zh_TW.js' },
  { name: 'en', file: 'src/i18n/en.js' },
  { name: 'de', file: 'src/i18n/de.js' },
  { name: 'vi', file: 'src/i18n/vi.js' },
  { name: 'pt_BR', file: 'src/i18n/pt_BR.js' },
];
 
function readLocale(locale) {
  const filePath = path.join(rootDir, locale.file);
  const lines = fs.readFileSync(filePath, 'utf8').split(/\r?\n/);
  const keys = [];
  const seen = new Map();
  const duplicates = [];
 
  lines.forEach((line, index) => {
    const match = line.match(/^\s*['"]([^'"]+)['"]\s*:/);
    if (!match) {
      return;
    }
 
    const key = match[1];
    keys.push(key);
    if (!seen.has(key)) {
      seen.set(key, []);
    }
    seen.get(key).push(index + 1);
  });
 
  seen.forEach((lineNumbers, key) => {
    if (lineNumbers.length > 1) {
      duplicates.push({ key, lineNumbers });
    }
  });
 
  return {
    ...locale,
    filePath,
    keys,
    keySet: new Set(keys),
    duplicates,
  };
}
 
function diffKeys(left, right) {
  return [...left].filter((key) => !right.has(key)).sort();
}
 
function formatKeyList(keys) {
  return keys.length ? keys.map((key) => `    - ${key}`).join('\n') : '';
}
 
function main() {
  const results = locales.map(readLocale);
  const base = results.find((locale) => locale.name === 'zh_CN');
  const errors = [];
 
  results.forEach((locale) => {
    locale.duplicates.forEach(({ key, lineNumbers }) => {
      errors.push(
        `${locale.file} has duplicate key "${key}" on lines ${lineNumbers.join(', ')}`
      );
    });
  });
 
  results
    .filter((locale) => locale !== base)
    .forEach((locale) => {
      const missing = diffKeys(base.keySet, locale.keySet);
      const extra = diffKeys(locale.keySet, base.keySet);
 
      if (missing.length) {
        errors.push(`${locale.file} is missing keys from ${base.file}:\n${formatKeyList(missing)}`);
      }
 
      if (extra.length) {
        errors.push(`${locale.file} has keys that do not exist in ${base.file}:\n${formatKeyList(extra)}`);
      }
    });
 
  if (errors.length) {
    console.error(`i18n check failed with ${errors.length} issue(s):`);
    console.error(errors.join('\n\n'));
    process.exit(1);
  }
 
  const keyCount = base.keySet.size;
  console.log(`i18n check passed: ${results.length} locale files, ${keyCount} keys each.`);
}
 
main();