Files
Recordly/scripts/i18n-check.mjs
webadderall 0c4d4c9517 feat(i18n): add app-wide localization framework and translator guide
- Add namespace-based JSON locale structure (en/es)
- Add I18n provider/context with locale persistence and fallback logic
- Add i18n parity checker script (npm run i18n:check)
- Wire app root/title shell to translation API
- Add TRANSLATION_GUIDE.md for contributors
2026-03-14 19:28:11 +11:00

86 lines
2.3 KiB
JavaScript

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')