#!/usr/bin/env node // Verifies that all i18n translation files share the exact same set of keys. // Exits with a non-zero status (and prints the differences) if any key is // present in one language but missing in another, so CI can catch drift. import { readFileSync, readdirSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const scriptDir = dirname(fileURLToPath(import.meta.url)); const i18nDir = join(scriptDir, '..', 'src', 'assets', 'i18n'); function collectLeafKeys(value, prefix, keys) { for (const key of Object.keys(value)) { const fullKey = prefix ? `${prefix}.${key}` : key; const child = value[key]; const isNestedObject = child !== null && typeof child === 'object' && !Array.isArray(child); if (isNestedObject) { collectLeafKeys(child, fullKey, keys); } else { keys.add(fullKey); } } return keys; } function loadKeys(fileName) { const content = readFileSync(join(i18nDir, fileName), 'utf8'); const parsed = JSON.parse(content); return collectLeafKeys(parsed, '', new Set()); } const files = readdirSync(i18nDir).filter((name) => name.endsWith('.json')); if (files.length < 2) { console.log('i18n check: nothing to compare (fewer than two language files).'); process.exit(0); } const keysByFile = new Map(files.map((file) => [file, loadKeys(file)])); const allKeys = new Set(); for (const keys of keysByFile.values()) { for (const key of keys) { allKeys.add(key); } } let hasError = false; for (const [file, keys] of keysByFile) { const missing = [...allKeys].filter((key) => !keys.has(key)).sort(); if (missing.length > 0) { hasError = true; console.error(`\n[${file}] missing ${missing.length} key(s):`); for (const key of missing) { console.error(` - ${key}`); } } } if (hasError) { console.error('\ni18n check failed: translation files are out of sync.'); process.exit(1); } console.log(`i18n check passed: ${files.join(', ')} share ${allKeys.size} keys.`);