UNPKG

i18ntk

Version:

🚀 The fastest i18n toolkit with 97% performance boost! Zero-dependency, enterprise-grade internationalization for React, Vue, Angular, Python, Java, PHP & more. Features PIN protection, auto framework detection, 7+ UI languages, and comprehensive transla

138 lines (122 loc) 4.06 kB
#!/usr/bin/env node /** * validate-all-translations.js (Upgraded) * --------------------------------------- * Validates i18n translation files: * 1. All locales have same keys as English * 2. No missing or extra keys * 3. No placeholder markers * 4. No leftover country code prefixes in non-English locales * 5. No untranslated English values in non-English locales * * Usage: * node scripts/validate-all-translations.js \ * --i18n-dir=./ui-locales \ * --languages=en,de,es,fr,ru,ja,zh */ const fs = require('fs'); const path = require('path'); const argv = Object.fromEntries( process.argv.slice(2).map(a => { const m = a.match(/^--([^=]+)(?:=(.*))?$/); return m ? [m[1], m[2] === undefined ? true : m[2]] : [a, true]; }) ); const I18N_DIR = path.resolve(argv['i18n-dir'] || './ui-locales'); const LANGS = (argv.languages || 'en,de,es,fr,ru,ja,zh').split(',').map(s => s.trim()); const MARKER = argv.marker || '⚠️ TRANSLATION NEEDED ⚠️'; // ------------ helpers ------------ function readJSON(p) { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return {}; } } function flatten(obj, prefix = '') { const out = {}; if (obj && typeof obj === 'object' && !Array.isArray(obj)) { for (const [k, v] of Object.entries(obj)) { const full = prefix ? `${prefix}.${k}` : k; Object.assign(out, flatten(v, full)); } return out; } out[prefix] = obj; return out; } function listLocaleFile(lang) { const file = path.join(I18N_DIR, `${lang}.json`); if (fs.existsSync(file)) return file; throw new Error(`Locale file not found: ${file}`); } // ------------ validation ------------ function validate() { console.log(`🔍 Validating translations in: ${I18N_DIR}`); console.log(`🌐 Languages: ${LANGS.join(', ')}`); console.log(''); // Load EN baseline const enFlat = flatten(readJSON(listLocaleFile('en'))); const report = {}; LANGS.forEach(lang => { const langFile = listLocaleFile(lang); const flat = flatten(readJSON(langFile)); const missing = []; const extra = []; const markers = []; const countryCodeLeftovers = []; const englishLeftovers = []; // Compare keys for (const k of Object.keys(enFlat)) { if (!(k in flat)) { missing.push(k); } else { const val = flat[k]; if (typeof val === 'string') { // Placeholder marker check if (val.includes(MARKER)) { markers.push(k); } // Country code leftover check if (lang !== 'en' && /^\[[A-Z]{2}\]/.test(val.trim())) { countryCodeLeftovers.push(k); } // English leftover check if (lang !== 'en' && val.trim() === enFlat[k]?.trim()) { englishLeftovers.push(k); } } } } // Extra keys not in EN for (const k of Object.keys(flat)) { if (!(k in enFlat)) { extra.push(k); } } report[lang] = { missing, extra, markers, countryCodeLeftovers, englishLeftovers }; console.log(`📄 ${lang.toUpperCase()}:`); console.log(` Missing: ${missing.length}`); console.log(` Extra: ${extra.length}`); console.log(` Markers: ${markers.length}`); if (lang !== 'en') { console.log(` Country code leftovers: ${countryCodeLeftovers.length}`); console.log(` English leftovers: ${englishLeftovers.length}`); } console.log(''); }); const reportFile = path.join(I18N_DIR, 'validation-purity-report.json'); fs.writeFileSync(reportFile, JSON.stringify(report, null, 2), 'utf8'); console.log(`✅ Validation report saved: ${reportFile}`); console.log(` Review this file for full details of problematic keys.`); } // Run try { validate(); } catch (err) { console.error('❌ Validation failed:', err.message); process.exit(1); }