Просмотр исходного кода

chore(i18n): extend parity gate to all locales with strict/info tiers

  Previously the script only inspected en/zh-CN/zh-TW, leaving de/fr/it/ja/pt-BR
  drift invisible. Now locales are auto-discovered from src/i18n/locales/, and a
  STRICT list (de, zh-CN, zh-TW — currently in parity) gates CI while the rest
  report informationally until their drift is caught up. ja notably has 27 real
  placeholder bugs worth fixing before promotion to strict.
maziggy 4 месяцев назад
Родитель
Сommit
8f9eb0d433
1 измененных файлов с 50 добавлено и 17 удалено
  1. 50 17
      frontend/scripts/check-i18n-parity.mjs

+ 50 - 17
frontend/scripts/check-i18n-parity.mjs

@@ -161,30 +161,63 @@ export function compareLocales(locales) {
   return { failed: reports.length > 0, reports };
 }
 
+// Strict locales fail CI when they drift from en. Everything else discovered
+// in the locales directory is reported informationally — promote a locale to
+// STRICT once its drift is caught up. en is implicitly the reference.
+const STRICT = ['de', 'zh-CN', 'zh-TW'];
+
 // Skip file IO / process.exit when imported as a library (e.g. from tests).
 const isMainModule = import.meta.url === url.pathToFileURL(process.argv[1] ?? '').href;
 if (isMainModule) {
-  const locales = {
-    en: loadLocale(path.join(localesDir, 'en.ts')),
-    'zh-CN': loadLocale(path.join(localesDir, 'zh-CN.ts')),
-    'zh-TW': loadLocale(path.join(localesDir, 'zh-TW.ts')),
-  };
-
-  const MAX_REPORT = 20;
-  const { failed, reports } = compareLocales(locales);
-  for (const { label, items } of reports) {
-    console.error(`\n[${label}] (${items.length})`);
-    items.slice(0, MAX_REPORT).forEach((i) => console.error(`  ${i}`));
-    if (items.length > MAX_REPORT) console.error(`  ... and ${items.length - MAX_REPORT} more`);
+  const discovered = fs
+    .readdirSync(localesDir)
+    .filter((f) => f.endsWith('.ts'))
+    .map((f) => f.slice(0, -3))
+    .sort();
+  if (!discovered.includes('en')) {
+    console.error(`No en.ts found in ${localesDir} — cannot run parity check without a reference locale.`);
+    process.exit(1);
   }
-
-  if (failed) {
-    console.error('\n❌ i18n parity check failed.');
+  const missingStrict = STRICT.filter((c) => !discovered.includes(c));
+  if (missingStrict.length) {
+    console.error(`STRICT locales declared but not found on disk: ${missingStrict.join(', ')}`);
     process.exit(1);
   }
+  const codes = ['en', ...discovered.filter((c) => c !== 'en')];
+  const locales = Object.fromEntries(
+    codes.map((c) => [c, loadLocale(path.join(localesDir, `${c}.ts`))]),
+  );
+
+  const MAX_REPORT = 20;
+  const strictSet = new Set(STRICT);
+  const printReports = (reports, header) => {
+    if (!reports.length) return;
+    console.error(`\n${header}`);
+    for (const { label, items } of reports) {
+      console.error(`\n[${label}] (${items.length})`);
+      items.slice(0, MAX_REPORT).forEach((i) => console.error(`  ${i}`));
+      if (items.length > MAX_REPORT) console.error(`  ... and ${items.length - MAX_REPORT} more`);
+    }
+  };
+
+  // Label prefix is "${code}:" — route reports to strict vs informational.
+  const { reports } = compareLocales(locales);
+  const codeOf = (label) => label.split(':', 1)[0];
+  const strictReports = reports.filter((r) => strictSet.has(codeOf(r.label)));
+  const infoReports = reports.filter((r) => !strictSet.has(codeOf(r.label)));
 
-  console.log(`All locales in parity (en / zh-CN / zh-TW):`);
+  printReports(strictReports, '=== STRICT locales (failures below fail CI) ===');
+  printReports(infoReports, '=== INFORMATIONAL locales (drift shown, does not fail CI) ===');
+
+  console.log('\nLocale leaf counts:');
   for (const [code, map] of Object.entries(locales)) {
-    console.log(`  ${code}: ${map.size} leaves`);
+    const tier = code === 'en' ? 'ref' : strictSet.has(code) ? 'strict' : 'info';
+    console.log(`  ${code.padEnd(6)} ${String(map.size).padEnd(6)} [${tier}]`);
+  }
+
+  if (strictReports.length > 0) {
+    console.error(`\n❌ i18n parity check failed (strict: ${STRICT.join(', ')}).`);
+    process.exit(1);
   }
+  console.log(`\n✓ Strict locales in parity (en / ${STRICT.join(' / ')}).`);
 }