Files
2026-07-13 13:12:33 +08:00

69 lines
2.5 KiB
JavaScript

// i18n key-parity + English-leakage guard. Wired into `check:architecture` so a
// non-en locale can never silently drift from the en key set (vue-i18n would
// fall back to en and hide the gap) or ship untranslated English strings.
//
// en.json is the authoritative key superset. For every other locale we fail on:
// - missing keys (present in en, absent here)
// - extra keys (present here, absent in en)
// - untranslated values (string identical to en and containing Latin letters)
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname, resolve } from 'node:path'
const here = dirname(fileURLToPath(import.meta.url))
const localesDir = resolve(here, '..', 'src', 'locales')
const OTHER_LOCALES = ['zh-Hans', 'ja', 'fr', 'de', 'es']
// English-leakage (value identical to en) is only a reliable "untranslated"
// signal for non-Latin scripts. fr/de/es legitimately share many words with
// English, so we enforce only KEY PARITY there and check leakage for zh-Hans.
const LEAKAGE_LOCALES = new Set(['zh-Hans'])
function load(name) {
return JSON.parse(readFileSync(resolve(localesDir, `${name}.json`), 'utf8'))
}
function flatten(obj, prefix = '', out = {}) {
for (const [k, v] of Object.entries(obj)) {
const key = prefix ? `${prefix}.${k}` : k
if (v && typeof v === 'object' && !Array.isArray(v)) flatten(v, key, out)
else out[key] = v
}
return out
}
const enFlat = flatten(load('en'))
const enKeys = Object.keys(enFlat)
let failed = false
for (const loc of OTHER_LOCALES) {
const flat = flatten(load(loc))
const keys = Object.keys(flat)
const missing = enKeys.filter((k) => !(k in flat))
const extra = keys.filter((k) => !(k in enFlat))
const untranslated = LEAKAGE_LOCALES.has(loc)
? enKeys.filter(
(k) => k in flat && typeof flat[k] === 'string' && flat[k] === enFlat[k] && /[A-Za-z]/.test(flat[k]),
)
: []
if (missing.length) {
failed = true
console.error(`[check-i18n] ${loc}: ${missing.length} missing key(s):\n ${missing.join('\n ')}`)
}
if (extra.length) {
failed = true
console.error(`[check-i18n] ${loc}: ${extra.length} extra key(s):\n ${extra.join('\n ')}`)
}
if (untranslated.length) {
failed = true
console.error(`[check-i18n] ${loc}: ${untranslated.length} untranslated (==en) value(s):\n ${untranslated.join('\n ')}`)
}
}
if (failed) {
console.error('[check-i18n] FAILED')
process.exit(1)
}
console.log(`[check-i18n] OK — ${OTHER_LOCALES.join(', ')} at full key parity with en, no English leakage`)