@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
JavaScript
/**
* 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();
}