83 lines
2.7 KiB
TypeScript
83 lines
2.7 KiB
TypeScript
/**
|
|
* 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/<locale>.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<string, unknown>;
|
|
|
|
/**
|
|
* 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.');
|