UNPKG

@shelchin/svelte-i18n

Version:

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

325 lines (310 loc) 12.5 kB
/** * Initialize i18n in a project */ import fs from 'fs'; import path from 'path'; import { detectProjectType, generateTypeDefinitions } from './utils.js'; // ANSI color codes for terminal output const colors = { reset: '\x1b[0m', bright: '\x1b[1m', green: '\x1b[32m', blue: '\x1b[34m', yellow: '\x1b[33m', red: '\x1b[31m' }; function log(message, color = 'reset') { console.log(`${colors[color]}${message}${colors.reset}`); } 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 initializeI18n(options) { const { translationsDir, localesDir, typesPath, namespace, isMain, typePrefix } = options; // Step 1: Create directories fs.mkdirSync(localesDir, { recursive: true }); fs.mkdirSync(path.dirname(typesPath), { recursive: true }); success(`Created ${localesDir}`); // Step 2: Create sample translation files if they don't exist const enPath = path.join(localesDir, 'en.json'); const zhPath = path.join(localesDir, 'zh.json'); const enTranslations = { welcome: 'Welcome', hello: 'Hello {name}!', navigation: { home: 'Home', about: 'About', contact: 'Contact' } }; const zhTranslations = { welcome: '欢迎', hello: '你好 {name}!', navigation: { home: '首页', about: '关于', contact: '联系' } }; if (!fs.existsSync(enPath)) { fs.writeFileSync(enPath, JSON.stringify(enTranslations, null, 2)); success(`Created ${enPath}`); } else { info(`${enPath} already exists, skipping...`); } if (!fs.existsSync(zhPath)) { fs.writeFileSync(zhPath, JSON.stringify(zhTranslations, null, 2)); success(`Created ${zhPath}`); } else { info(`${zhPath} already exists, skipping...`); } // Step 3: Generate TypeScript types try { generateTypeDefinitions(localesDir, typesPath, typePrefix); success(`Generated TypeScript types at ${typesPath}`); } catch (err) { error(`Failed to generate types: ${err.message}`); } // Step 4: Create i18n.ts configuration file const relativePath = path.relative(translationsDir, typesPath).replace(/\\/g, '/'); const typeImportPath = relativePath.startsWith('.') ? relativePath : `./${relativePath}`; const i18nContent = `/** * ${namespace === 'app' ? 'Application' : 'Library'} i18n configuration * Auto-generated by @shelchin/svelte-i18n CLI * * This file handles translation imports and i18n setup * Using unified API with type safety for translation keys */ import { createTypedUnifiedI18n${namespace !== 'app' ? ', initTypedI18n' : ''}, type UnifiedI18nConfig } from '@shelchin/svelte-i18n'; ${namespace !== 'app' ? `import { getI18nInstance } from '@shelchin/svelte-i18n'; import type { I18nInstance } from '@shelchin/svelte-i18n';` : ''} import type { ${typePrefix} } from '${typeImportPath.replace('.d.ts', '.js')}'; // ============================================ // Auto-scan and import translations from locales directory // ============================================ const translationModules = import.meta.glob('./locales/*.json', { eager: true, import: 'default' }); const translations: Record<string, unknown> = {}; // Extract language code from file path and build translations object for (const [path, module] of Object.entries(translationModules)) { // Extract language code from path like './locales/en.json' const match = path.match(/\\/([^/]+)\\.json$/); if (match && match[1]) { const langCode = match[1]; translations[langCode] = module; } } ${namespace !== 'app' ? `// Get package name const packageName = '${namespace}'; ` : ''} // ============================================ // Configure and initialize i18n // ============================================ const config: UnifiedI18nConfig = { namespace: '${namespace}',${isMain ? '\n\tisMain: true, // This is the main app instance' : ''} defaultLocale: '${namespace === 'app' ? 'en' : 'en'}', fallbackLocale: '${namespace === 'app' ? 'en' : 'en'}', translations, interpolation: { prefix: '{', suffix: '}' }, formats: { date: { year: 'numeric' as const, month: '${namespace === 'app' ? 'long' : 'short'}' as const, day: 'numeric' as const }, time: { hour: '2-digit' as const, minute: '2-digit' as const }, number: { minimumFractionDigits: 0, maximumFractionDigits: 2 }, currency: { style: 'currency' as const, currency: 'USD' } } }; // Create ${namespace === 'app' ? 'main app' : 'library'} i18n instance with type safety export const ${namespace === 'app' ? 'i18n' : 'libI18n'} = createTypedUnifiedI18n<${typePrefix}>(config); ${namespace !== 'app' ? `// Auto-initialize the library i18n when in browser if (typeof window !== 'undefined') { // Initialize asynchronously initTypedI18n(libI18n).catch((err) => { console.error('Failed to initialize library i18n:', err); }); } // Helper to get the effective i18n instance // Try to use app's package instance if available, otherwise use library's own export function getEffectiveLibI18n(): I18nInstance { try { // Try to get the app's package instance return getI18nInstance(packageName); } catch { // Fallback to library's own instance return libI18n as I18nInstance; } } // For backward compatibility export function getLibI18n() { return libI18n; }` : `// ============================================ // Export for use in application // ============================================ // Export getI18n function for typed access export function getI18n() { return i18n; } export default i18n;`} `; const i18nPath = path.join(translationsDir, 'i18n.ts'); fs.writeFileSync(i18nPath, i18nContent); success(`Created ${i18nPath}`); // Generate .d.ts file for applications (not for libraries since they use internal imports) if (namespace === 'app') { const dtsContent = `/** * ${namespace === 'app' ? 'Application' : 'Library'} i18n configuration * Auto-generated by @shelchin/svelte-i18n CLI * * This file handles translation imports and i18n setup * Using unified API with type safety for translation keys */ import type { TypedUnifiedI18nInstance } from '@shelchin/svelte-i18n'; import type { ${typePrefix} } from '${typeImportPath.replace('.d.ts', '.js')}'; export declare const i18n: TypedUnifiedI18nInstance<${typePrefix}>; export declare function getI18n(): TypedUnifiedI18nInstance<${typePrefix}>; export default i18n;`; const dtsPath = path.join(translationsDir, 'i18n.d.ts'); fs.writeFileSync(dtsPath, dtsContent); success(`Created ${dtsPath}`); } } export function init() { log('🚀 Initializing @shelchin/svelte-i18n...', 'bright'); console.log(); const projectRoot = process.cwd(); // Check if package.json exists const packageJsonPath = path.join(projectRoot, 'package.json'); if (!fs.existsSync(packageJsonPath)) { error('No package.json found. Please run this command in your project root.'); process.exit(1); } // Detect project type info('Detecting project type...'); const projectType = detectProjectType(projectRoot); if (projectType.isPackage && projectType.isApp) { log('📦 Detected: Package + Application (hybrid project)', 'yellow'); info('Will set up i18n for both library and application'); } else if (projectType.isPackage) { log('📦 Detected: Package/Library project', 'yellow'); } else { log('🎯 Detected: Application project', 'yellow'); } console.log(); // Initialize for application if (projectType.isApp) { info('Setting up application i18n...'); initializeI18n({ translationsDir: path.join(projectRoot, 'src/translations'), localesDir: path.join(projectRoot, 'src/translations/locales'), typesPath: path.join(projectRoot, 'src/types/app-i18n-generated.d.ts'), namespace: 'app', isMain: true, typePrefix: 'I18nPath' }); } // Initialize for package/library if (projectType.isPackage) { info('Setting up library i18n...'); initializeI18n({ translationsDir: path.join(projectRoot, 'src/lib/translations'), localesDir: path.join(projectRoot, 'src/lib/translations/locales'), typesPath: path.join(projectRoot, 'src/lib/types/lib-i18n-generated.d.ts'), namespace: projectType.packageName || '@shelchin/svelte-i18n', isMain: false, typePrefix: 'LibI18nPath' }); } // Update package.json with useful scripts info('Updating package.json scripts...'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); if (!packageJson.scripts) { packageJson.scripts = {}; } // Add helpful i18n scripts if they don't exist const scriptsToAdd = {}; if (projectType.isApp) { scriptsToAdd['i18n:check'] = 'svelte-i18n validate ./src/translations/locales'; scriptsToAdd['i18n:types'] = 'svelte-i18n generate-types'; } if (projectType.isPackage) { scriptsToAdd['i18n:check:lib'] = 'svelte-i18n validate ./src/lib/translations/locales'; scriptsToAdd['i18n:types:lib'] = 'svelte-i18n generate-types --lib'; } let scriptsAdded = false; for (const [key, value] of Object.entries(scriptsToAdd)) { if (!packageJson.scripts[key]) { packageJson.scripts[key] = value; scriptsAdded = true; } } if (scriptsAdded) { fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); success('Added i18n scripts to package.json'); } // Instructions for the user console.log(); log('═══════════════════════════════════════════════════════════════', 'green'); success('@shelchin/svelte-i18n initialized successfully!'); log('═══════════════════════════════════════════════════════════════', 'green'); console.log(); log('Next steps:', 'yellow'); if (projectType.isApp) { console.log(); log('For your APPLICATION:', 'blue'); console.log('1. Import i18n in your root layout (+layout.svelte):'); console.log(` ${colors.blue}import '../translations/i18n';${colors.reset}`); console.log(); console.log('2. Use translations in your components:'); console.log(` ${colors.blue}import { i18n } from '$lib/translations/i18n';${colors.reset}`); console.log(` ${colors.blue}const greeting = i18n.t('hello', { name: 'World' });${colors.reset}`); console.log(); console.log('3. Add more languages in:'); console.log(` ${colors.blue}src/translations/locales/${colors.reset}`); } if (projectType.isPackage) { console.log(); log('For your LIBRARY/PACKAGE:', 'blue'); console.log('1. Use translations in your library components:'); console.log(` ${colors.blue}import { libI18n } from '$lib/translations/i18n';${colors.reset}`); console.log(` ${colors.blue}const text = libI18n.t('welcome');${colors.reset}`); console.log(); console.log('2. Add more languages in:'); console.log(` ${colors.blue}src/lib/translations/locales/${colors.reset}`); } console.log(); console.log('4. Switch languages programmatically:'); console.log(` ${colors.blue}i18n.locale.set('zh');${colors.reset}`); console.log(); console.log('5. Generate/update TypeScript types:'); if (projectType.isApp) { console.log(` ${colors.blue}npm run i18n:types${colors.reset}`); } if (projectType.isPackage) { console.log(` ${colors.blue}npm run i18n:types:lib${colors.reset}`); } console.log(); log('📚 Documentation:', 'yellow'); console.log(' https://github.com/atshelchin/svelte-i18n'); console.log(); }