#!/usr/bin/env node /** * Generate strongly-typed i18n key declarations from en-US locale modules. * * Usage: * node scripts/generate-i18n-types.js */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const LOCALES_DIR = path.resolve(__dirname, '../packages/desktop/src/renderer/services/i18n/locales'); const OUTPUT_FILE = path.resolve(__dirname, '../packages/desktop/src/renderer/services/i18n/i18n-keys.d.ts'); const OXFMT_BIN = path.resolve( __dirname, process.platform === 'win32' ? '../node_modules/.bin/oxfmt.exe' : '../node_modules/.bin/oxfmt' ); const i18nConfig = require('../packages/desktop/src/common/config/i18n-config.json'); const REFERENCE_LANGUAGE = i18nConfig.referenceLanguage; const REQUIRED_MODULES = i18nConfig.modules; function getAllKeys(obj, prefix = '') { const keys = []; if (typeof obj !== 'object' || obj === null) { return keys; } for (const [key, value] of Object.entries(obj)) { const fullKey = prefix ? `${prefix}.${key}` : key; if (typeof value === 'object' && value !== null) { keys.push(...getAllKeys(value, fullKey)); } else { keys.push(fullKey); } } return keys; } function collectReferenceKeys() { const allKeys = new Set(); for (const moduleName of REQUIRED_MODULES) { const moduleFile = path.join(LOCALES_DIR, REFERENCE_LANGUAGE, `${moduleName}.json`); if (!fs.existsSync(moduleFile)) { throw new Error(`Missing reference module: ${REFERENCE_LANGUAGE}/${moduleName}.json`); } const content = JSON.parse(fs.readFileSync(moduleFile, 'utf-8')); for (const key of getAllKeys(content)) { allKeys.add(`${moduleName}.${key}`); } } return Array.from(allKeys).sort(); } function buildI18nKeysDts(keys) { const keyUnion = keys.length > 0 ? keys.map((key) => ` | '${key}'`).join('\n') : ' | never'; return `/* eslint-disable */\n/**\n * AUTO-GENERATED FILE - DO NOT EDIT\n * Generated by scripts/generate-i18n-types.js\n */\n\nexport type I18nKey =\n${keyUnion};\n\nexport type I18nModule =\n${REQUIRED_MODULES.map((moduleName) => ` | '${moduleName}'`).join('\n')};\n`; } function generateI18nKeysDtsContent() { const keys = collectReferenceKeys(); return buildI18nKeysDts(keys); } function formatOutputFile(filePath) { const commands = [`bunx prettier --write "${filePath}"`]; if (fs.existsSync(OXFMT_BIN)) { commands.push(`"${OXFMT_BIN}" "${filePath}"`); } for (const command of commands) { try { execSync(command, { stdio: 'inherit' }); return; } catch { // Try the next available formatter. } } console.warn(`⚠️ Unable to auto-format ${path.relative(process.cwd(), filePath)}. Continuing without formatting.`); } function writeOutputFile(content) { const current = fs.existsSync(OUTPUT_FILE) ? fs.readFileSync(OUTPUT_FILE, 'utf-8') : null; if (current === content) { console.log(`✅ i18n key types are up to date: ${path.relative(process.cwd(), OUTPUT_FILE)}`); return; } fs.writeFileSync(OUTPUT_FILE, content, 'utf-8'); formatOutputFile(OUTPUT_FILE); console.log(`✅ i18n key types generated: ${path.relative(process.cwd(), OUTPUT_FILE)}`); } if (require.main === module) { const content = generateI18nKeysDtsContent(); writeOutputFile(content); } module.exports = { REQUIRED_MODULES, collectReferenceKeys, generateI18nKeysDtsContent, getAllKeys, };