Initial commit
This commit is contained in:
82
scripts/aggregate-translations.ts
Normal file
82
scripts/aggregate-translations.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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.');
|
||||
Reference in New Issue
Block a user