/** * Translation aggregation script. * * Scans all `translations.json` files alongside components/widgets/views * in apps/web/src, merges them with the shared common messages, and writes * a combined per-locale message file to apps/web/messages/.json. * * Usage: * pnpm i18n:aggregate * * During development, run this after adding new translation keys. * A watch-mode version should be wired into the dev workflow (future work). */ import { readFileSync, writeFileSync, readdirSync, statSync, existsSync } from 'fs'; import { join, dirname, relative } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const repoRoot = join(__dirname, '..'); const webSrc = join(repoRoot, 'apps/web/src'); const messagesDir = join(repoRoot, 'apps/web/messages'); const locales = ['en']; type TranslationTree = Record; /** * Recursively finds all `translations.json` files within a directory. */ function findTranslationFiles(dir: string): string[] { const results: string[] = []; for (const entry of readdirSync(dir)) { const full = join(dir, entry); const stat = statSync(full); if (stat.isDirectory()) { results.push(...findTranslationFiles(full)); } else if (entry === 'translations.json') { results.push(full); } } return results; } /** * Deep-merges two translation trees. Conflicts are overwritten by `source`. */ function merge(target: TranslationTree, source: TranslationTree): TranslationTree { const result = { ...target }; for (const [key, value] of Object.entries(source)) { if (typeof value === 'object' && value !== null && !Array.isArray(value)) { result[key] = merge( (result[key] as TranslationTree | undefined) ?? {}, value as TranslationTree, ); } else { result[key] = value; } } return result; } for (const locale of locales) { const baseFile = join(messagesDir, `${locale}.json`); let base: TranslationTree = existsSync(baseFile) ? (JSON.parse(readFileSync(baseFile, 'utf-8')) as TranslationTree) : {}; const translationFiles = findTranslationFiles(webSrc); for (const file of translationFiles) { const raw = JSON.parse(readFileSync(file, 'utf-8')) as TranslationTree; const localeData = (raw[locale] ?? raw) as TranslationTree; base = merge(base, localeData); const rel = relative(repoRoot, file); console.log(` merged: ${rel}`); } writeFileSync(baseFile, JSON.stringify(base, null, 2) + '\n', 'utf-8'); console.log(`✓ ${locale}: wrote ${baseFile}`); } console.log('Translation aggregation complete.');