UNPKG

@shelchin/svelte-i18n

Version:

The last Svelte i18n library you'll ever need. Type-safe, AI-powered, zero-config.

245 lines (244 loc) 9.02 kB
#!/usr/bin/env node /** * Generate TypeScript type definitions from translation files * Automatically detects project type and generates appropriate types */ import fs from 'fs'; import path from 'path'; import { detectProjectType, generateTypeDefinitions } from './utils.js'; // ANSI color codes const colors = { reset: '\x1b[0m', green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', blue: '\x1b[34m' }; function success(message) { console.log(`${colors.green}${message}${colors.reset}`); } function error(message) { console.error(`${colors.red}${message}${colors.reset}`); } function info(message) { console.log(`${colors.blue}ℹ️ ${message}${colors.reset}`); } function warn(message) { console.log(`${colors.yellow}⚠️ ${message}${colors.reset}`); } /** * Validate translation files for consistency */ function validateTranslations(translationsDir, baseLocale = 'en') { if (!fs.existsSync(translationsDir)) { error(`Translations directory not found: ${translationsDir}`); return false; } const files = fs .readdirSync(translationsDir) .filter((f) => f.endsWith('.json')) .sort(); if (files.length === 0) { info('No translation files found'); return true; } // Load base translation const basePath = path.join(translationsDir, `${baseLocale}.json`); if (!fs.existsSync(basePath)) { error(`Base locale "${baseLocale}" not found`); return false; } const baseTranslation = JSON.parse(fs.readFileSync(basePath, 'utf8')); const baseKeys = new Set(); function extractKeys(obj, prefix = '') { for (const key in obj) { const fullPath = prefix ? `${prefix}.${key}` : key; if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) { extractKeys(obj[key], fullPath); } else { baseKeys.add(fullPath); } } } extractKeys(baseTranslation); // Validate other files let hasErrors = false; for (const file of files) { if (file === `${baseLocale}.json`) continue; const filePath = path.join(translationsDir, file); const translation = JSON.parse(fs.readFileSync(filePath, 'utf8')); const fileKeys = new Set(); function extractFileKeys(obj, prefix = '') { for (const key in obj) { const fullPath = prefix ? `${prefix}.${key}` : key; if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) { extractFileKeys(obj[key], fullPath); } else { fileKeys.add(fullPath); } } } extractFileKeys(translation); // Check for missing keys const missingKeys = Array.from(baseKeys).filter((k) => !fileKeys.has(k)); const extraKeys = Array.from(fileKeys).filter((k) => !baseKeys.has(k)); if (missingKeys.length > 0) { error(`${file}: Missing keys: ${missingKeys.join(', ')}`); hasErrors = true; } if (extraKeys.length > 0) { warn(`${file}: Extra keys: ${extraKeys.join(', ')}`); } } return !hasErrors; } export async function generateTypes(options = {}) { const projectRoot = process.cwd(); // Detect project type if not explicitly specified const projectType = detectProjectType(projectRoot); // Determine what to generate based on options and project type let generateApp = false; let generateLib = false; if (options.lib) { // Explicitly requested library types generateLib = true; } else if (options.translationsDir || options.outFile) { // Custom paths provided - use them const translationsDir = options.translationsDir || 'src/translations/locales'; const outFile = options.outFile || 'src/types/app-i18n-generated.d.ts'; info(`Generating types from ${translationsDir}...`); try { generateTypeDefinitions(path.join(projectRoot, translationsDir), path.join(projectRoot, outFile), 'I18nPath'); success(`Generated types at ${outFile}`); if (options.validate !== false) { info('Validating translations...'); const isValid = validateTranslations(path.join(projectRoot, translationsDir), options.defaultLocale || 'en'); if (!isValid) { error('Translation validation failed'); return false; } success('All translations are valid'); } return true; } catch (err) { error(`Failed to generate types: ${err.message}`); return false; } } else { // Auto-detect what to generate if (projectType.isApp) generateApp = true; if (projectType.isPackage) generateLib = true; } let allSuccess = true; // Generate application types if (generateApp) { const appTranslationsDir = path.join(projectRoot, 'src/translations/locales'); const appTypesPath = path.join(projectRoot, 'src/types/app-i18n-generated.d.ts'); if (fs.existsSync(appTranslationsDir)) { info('Generating application types...'); try { generateTypeDefinitions(appTranslationsDir, appTypesPath, 'I18nPath'); success(`Generated app types at ${appTypesPath}`); if (options.validate !== false) { info('Validating app translations...'); const isValid = validateTranslations(appTranslationsDir, options.defaultLocale || 'en'); if (!isValid) { error('App translation validation failed'); allSuccess = false; } else { success('App translations are valid'); } } } catch (err) { error(`Failed to generate app types: ${err.message}`); allSuccess = false; } } else { error('Application translations not found. Run "svelte-i18n init" first.'); allSuccess = false; } } // Generate library types if (generateLib) { const libTranslationsDir = path.join(projectRoot, 'src/lib/translations/locales'); const libTypesPath = path.join(projectRoot, 'src/lib/types/lib-i18n-generated.d.ts'); if (fs.existsSync(libTranslationsDir)) { info('Generating library types...'); try { generateTypeDefinitions(libTranslationsDir, libTypesPath, 'LibI18nPath'); success(`Generated library types at ${libTypesPath}`); if (options.validate !== false) { info('Validating library translations...'); const isValid = validateTranslations(libTranslationsDir, options.defaultLocale || 'en'); if (!isValid) { error('Library translation validation failed'); allSuccess = false; } else { success('Library translations are valid'); } } } catch (err) { error(`Failed to generate library types: ${err.message}`); allSuccess = false; } } else { error('Library translations not found. Run "svelte-i18n init" first.'); allSuccess = false; } } if (!generateApp && !generateLib) { error('No translations found. Run "svelte-i18n init" first to set up i18n.'); return false; } return allSuccess; } // CLI entry point if (import.meta.url === `file://${process.argv[1]}`) { const args = process.argv.slice(2); const options = {}; // Parse command line arguments for (let i = 0; i < args.length; i++) { switch (args[i]) { case '--dir': case '-d': options.translationsDir = args[++i]; break; case '--out': case '-o': options.outFile = args[++i]; break; case '--locale': case '-l': options.defaultLocale = args[++i]; break; case '--no-validate': options.validate = false; break; case '--lib': options.lib = true; break; } } generateTypes(options) .then((success) => { process.exit(success ? 0 : 1); }) .catch((error) => { console.error('Error:', error); process.exit(1); }); }