import fs from 'node:fs' import path from 'node:path' const root = process.cwd() const localesDir = path.join(root, 'src', 'i18n', 'locales') const locales = fs.readdirSync(localesDir).filter((entry) => { const fullPath = path.join(localesDir, entry) return fs.statSync(fullPath).isDirectory() }) if (!locales.includes('en')) { console.error('i18n-check: expected base locale directory "en"') process.exit(1) } function loadJson(filePath) { return JSON.parse(fs.readFileSync(filePath, 'utf8')) } function collectKeyPaths(obj, prefix = '') { if (!obj || typeof obj !== 'object' || Array.isArray(obj)) { return prefix ? [prefix] : [] } const keys = Object.keys(obj) if (keys.length === 0) { return prefix ? [prefix] : [] } const paths = [] for (const key of keys) { const nextPrefix = prefix ? `${prefix}.${key}` : key const value = obj[key] if (value && typeof value === 'object' && !Array.isArray(value)) { paths.push(...collectKeyPaths(value, nextPrefix)) } else { paths.push(nextPrefix) } } return paths } const baseLocaleDir = path.join(localesDir, 'en') const namespaceFiles = fs.readdirSync(baseLocaleDir).filter((file) => file.endsWith('.json')) let hasErrors = false for (const namespaceFile of namespaceFiles) { const baseData = loadJson(path.join(baseLocaleDir, namespaceFile)) const baseKeys = new Set(collectKeyPaths(baseData)) for (const locale of locales) { if (locale === 'en') continue const localeFile = path.join(localesDir, locale, namespaceFile) if (!fs.existsSync(localeFile)) { console.error(`i18n-check: missing namespace file ${locale}/${namespaceFile}`) hasErrors = true continue } const localeData = loadJson(localeFile) const localeKeys = new Set(collectKeyPaths(localeData)) for (const key of baseKeys) { if (!localeKeys.has(key)) { console.error(`i18n-check: missing key ${locale}/${namespaceFile}:${key}`) hasErrors = true } } for (const key of localeKeys) { if (!baseKeys.has(key)) { console.error(`i18n-check: extra key ${locale}/${namespaceFile}:${key}`) hasErrors = true } } } } if (hasErrors) { process.exit(1) } console.log('i18n-check: locale files are structurally consistent')