@shelchin/svelte-i18n
Version:
The last Svelte i18n library you'll ever need. Type-safe, AI-powered, zero-config.
852 lines (630 loc) • 20.6 kB
Markdown
# @shelchin/svelte-i18n
> The last Svelte i18n library you'll ever need. Type-safe, zero-config, with seamless SSR/CSR support.
[](https://www.npmjs.com/package/@shelchin/svelte-i18n)
[](https://opensource.org/licenses/MIT)
[](https://svelte.dev)
[](https://www.typescriptlang.org/)
[中文文档](./README-zh.md) • [Live Demo](https://atshelchin.github.io/svelte-i18n/) • [Examples](./src/routes)
> ⚠️ **Warning**: This library is currently in active development and is not recommended for production use yet. APIs may change in future releases. Documentation may be incomplete or contain errors.
## ✨ Features
### 🎯 Core Features
- **🔒 Full Type Safety** - Auto-generated TypeScript types for all translation keys
- **🚀 Zero Configuration** - Works out of the box with sensible defaults
- **📦 Optimized Bundle Size** - ~35KB gzipped with tree-shaking support
- **🌐 SSR/CSR Support** - Seamless server-side and client-side rendering
- **🔄 Hot Module Replacement** - Instant translation updates during development
- **🎨 Rich Formatting** - Built-in number, date, currency, and list formatting via native Intl API
- **📱 Smart Locale Detection** - From URL pathname, browser, cookies, or localStorage
### 🛠️ Developer Experience
- **🤖 Powerful CLI** - Extract keys, validate translations, generate types
- **🔍 Runtime Validation** - Catch translation errors during development
- **📚 Namespace Support** - Isolate translations for packages and libraries
- **🎯 Smart Fallbacks** - Graceful degradation with fallback locales
- **💾 Persistence** - Remember user's language preference across sessions
- **🌍 150+ Languages** - Built-in metadata for all major languages
### 🏗️ Architecture
- **🧩 Svelte 5 Native** - Built with runes from the ground up
- **🔌 Unified API** - Same API for both applications and npm packages
- **📊 Lazy Loading** - Load translations on-demand for better performance
- **🎛️ Configuration Inheritance** - Libraries automatically inherit app configuration
## 📦 Installation
```bash
# Install the package
pnpm add @shelchin/svelte-i18n
# or
npm install @shelchin/svelte-i18n
# or
yarn add @shelchin/svelte-i18n
```
## 🚀 Quick Start
### 1. Initialize i18n in your project
Run the initialization command to auto-generate configuration:
```bash
# Run init command (auto-detects project type and generates config)
pnpm exec svelte-i18n init
# or
npx svelte-i18n init
```
This will:
- Create `src/translations/` directory structure
- Generate sample translation files (`locales/en.json`, `locales/zh.json`)
- Create `i18n.ts` configuration file with type-safe setup
- Generate TypeScript type definitions
The generated `i18n.ts` will look like:
```typescript
// src/translations/i18n.ts (auto-generated)
import { createI18n } from '@shelchin/svelte-i18n';
import type { I18nPath } from './types/i18n-generated.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)) {
const match = path.match(/\/([^/]+)\.json$/);
if (match && match[1]) {
const langCode = match[1];
translations[langCode] = module;
}
}
// Create i18n instance with type safety
export const i18n = createI18n<I18nPath>({
namespace: 'app',
isMain: true,
translations,
defaultLocale: 'en',
fallbackLocale: 'en'
});
export default i18n;
```
### 2. Setup in SvelteKit
#### Configure `+layout.server.ts` for SSR:
```typescript
// src/routes/+layout.server.ts
import { loadI18nSSR } from '@shelchin/svelte-i18n';
import { i18n } from '$src/translations/i18n.js';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ request }) => {
const locale = await loadI18nSSR(i18n, request);
return {
locale
};
};
```
#### Configure `+layout.ts` for Universal Loading:
```typescript
// src/routes/+layout.ts
import { loadI18nUniversal } from '@shelchin/svelte-i18n';
import { i18n } from '$src/translations/i18n.js';
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = async ({ data }) => {
await loadI18nUniversal(i18n, data?.locale);
return {
locale: data?.locale
};
};
```
#### Configure `+layout.svelte` for Client:
```svelte
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
import { setupI18nClient } from '@shelchin/svelte-i18n';
import { i18n } from '$src/translations/i18n.js';
onMount(async () => {
await setupI18nClient(i18n);
});
</script>
<slot />
```
### 3. Use in Components
```svelte
<script lang="ts">
import { i18n } from '$src/translations/i18n.js';
import { LanguageSwitcher } from '@shelchin/svelte-i18n';
let name = $state('World');
// Type-safe translations with autocomplete
const welcome = i18n.t('welcome');
const hello = i18n.t('hello', { name });
</script>
<h1>{welcome}</h1>
<p>{hello}</p>
<!-- Direct usage -->
<nav>
<a href="/">{i18n.t('navigation.home')}</a>
<a href="/about">{i18n.t('navigation.about')}</a>
<a href="/contact">{i18n.t('navigation.contact')}</a>
</nav>
<!-- Language Switcher Component -->
<LanguageSwitcher {i18n} />
```
### 4. Use in Libraries/Packages
For library packages, use namespace to avoid conflicts:
```typescript
// In a library: src/lib/translations/i18n.ts
import { createI18n } from '@shelchin/svelte-i18n';
import type { LibI18nPath } from './types/i18n-generated.js';
// Auto-import translations
const translationModules = import.meta.glob('./locales/*.json', {
eager: true,
import: 'default'
});
const translations: Record<string, unknown> = {};
for (const [path, module] of Object.entries(translationModules)) {
const match = path.match(/\/([^/]+)\.json$/);
if (match && match[1]) {
translations[match[1]] = module;
}
}
export const libI18n = createI18n<LibI18nPath>({
namespace: 'my-ui-lib', // Use your package name
translations
});
// Usage in library component
libI18n.t('button.save');
```
## 🛠️ CLI Commands
### Generate TypeScript Types
```bash
# Generate types from translation files
pnpm exec svelte-i18n generate-types
# or with custom paths
pnpm exec svelte-i18n generate-types --dir ./src/translations/locales --out ./src/lib/types/i18n-generated.ts
```
### Validate Translations
```bash
# Check for missing translations
pnpm exec svelte-i18n validate src/translations/locales
```
### Extract Translation Keys
```bash
# Extract keys from source code
pnpm exec svelte-i18n extract ./src ./template.json
```
## 🎯 Type Safety
The `init` command automatically generates TypeScript types. To regenerate after changes:
```bash
pnpm exec svelte-i18n generate-types
```
This creates type definitions that provide autocomplete for all translation keys:
```typescript
// Auto-generated types in src/translations/types/i18n-generated.d.ts
export type I18nPath =
| 'welcome'
| 'hello'
| 'navigation.home'
| 'navigation.about'
| 'navigation.contact';
// Already configured in your i18n.ts with type safety
import type { I18nPath } from './types/i18n-generated.js';
export const i18n = createI18n<I18nPath>({
// ... config
});
// Now TypeScript ensures only valid keys are used
i18n.t('welcome'); // ✅ Valid
i18n.t('hello', { name: 'John' }); // ✅ Valid with params
i18n.t('invalid.key'); // ❌ TypeScript error
```
## 🌍 Formatting
Built-in formatters using native Intl API (zero dependencies):
```typescript
const i18n = getI18n();
// Numbers
i18n.formatNumber(1234567.89); // "1,234,567.89" (en) / "1.234.567,89" (de)
i18n.formatNumber(0.15, 'percent'); // "15%"
i18n.formatNumber(123456789, 'compact'); // "123M"
// Currency (auto-detects based on locale)
i18n.formatCurrency(99.99); // "$99.99" (en-US) / "99,99 €" (de-DE)
i18n.formatCurrency(99.99, 'EUR'); // "€99.99"
// Dates
i18n.formatDate(new Date()); // "1/15/2024" (en-US) / "15.1.2024" (de)
i18n.formatDate(new Date(), 'full'); // "Monday, January 15, 2024"
// Time
i18n.formatTime(new Date()); // "3:30 PM" / "15:30"
// Relative Time
i18n.formatRelativeTime(-2, 'day'); // "2 days ago"
i18n.formatRelativeTime(3, 'hour'); // "in 3 hours"
// Lists
i18n.formatList(['Apple', 'Banana', 'Orange']); // "Apple, Banana, and Orange"
```
## 🎨 Components
### Language Switcher
Pre-built, accessible language switcher component:
```svelte
<script>
import { LanguageSwitcher } from '@shelchin/svelte-i18n';
import { i18n } from '../app/i18n';
</script>
<!-- Default switcher -->
<LanguageSwitcher {i18n} />
<!-- With custom styling and position -->
<LanguageSwitcher
{i18n}
class="my-custom-class"
position="top-left"
showFlags={true}
showLabels={true}
/>
```
### Validation Popup (Dev Only)
Shows translation errors during development:
```svelte
<script>
import { ValidationPopup } from '@shelchin/svelte-i18n';
import { i18n } from '../app/i18n';
</script>
{#if import.meta.env.DEV}
<ValidationPopup {i18n} />
{/if}
```
## 📚 Advanced Features
### URL-based Locale Detection
Automatically detect locale from URL pathname:
```typescript
// Supports patterns like:
// /zh/about -> Chinese
// /en-US/products -> American English
// /de-DE/contact -> German
export const load: LayoutLoad = async ({ data, url }) => {
// The url parameter enables pathname locale detection
return await loadI18nUniversal(i18n, data, url);
};
```
### Dynamic Translation Loading
Load translations dynamically for code splitting:
```typescript
// Option 1: Dynamic imports
async function loadTranslations(locale: string) {
const translations = await import(`../translations/${locale}.json`);
await i18n.loadLanguage(locale, translations.default);
}
// Option 2: Fetch from API
async function fetchTranslations(locale: string) {
const response = await fetch(`/api/translations/${locale}`);
const translations = await response.json();
await i18n.loadLanguage(locale, translations);
}
```
### Namespace Support for Libraries
Libraries can have isolated translations that don't conflict with the app:
```typescript
// In your library (my-ui-lib)
export const libI18n = createI18n({
namespace: 'my-ui-lib',
translations: {
en: { button: { save: 'Save', cancel: 'Cancel' } },
zh: { button: { save: '保存', cancel: '取消' } }
}
});
// Library translations are automatically namespaced
libI18n.t('button.save'); // Uses "my-ui-lib.button.save" internally
// Libraries automatically inherit app's locale
// When app switches to 'zh', library also switches to 'zh'
```
### SSR with Cookie Persistence
Server-side rendering with locale persistence:
```typescript
// +layout.server.ts
import type { LayoutServerLoad } from './$types';
import { loadI18nSSR } from '@shelchin/svelte-i18n';
export const load: LayoutServerLoad = async ({ cookies }) => {
const locale = cookies.get('i18n-locale') || 'en';
return loadI18nSSR(locale, ['en', 'zh', 'ja']);
};
```
### Pluralization
Handle plural forms correctly for all languages:
```typescript
// English: 0 = plural, 1 = singular, 2+ = plural
"items.count": "No items | One item | {count} items"
// Polish: Complex plural rules
"items.count": "Brak elementów | Jeden element | {count} elementy | {count} elementów"
// Usage
i18n.t('items.count', { count: 0 }); // "No items"
i18n.t('items.count', { count: 1 }); // "One item"
i18n.t('items.count', { count: 5 }); // "5 items"
```
### Interpolation
Dynamic values in translations:
```typescript
// Basic interpolation
"welcome": "Welcome {name}!"
i18n.t('welcome', { name: 'John' }); // "Welcome John!"
// Nested values
"user.greeting": "Hello {user.firstName} {user.lastName}"
i18n.t('user.greeting', {
user: { firstName: 'John', lastName: 'Doe' }
}); // "Hello John Doe"
// Custom interpolation markers
const i18n = createI18n({
interpolation: {
prefix: '{{',
suffix: '}}'
}
});
// Now use: "welcome": "Welcome {{name}}!"
```
### Runtime Validation
Catch translation issues during development:
```typescript
const i18n = createI18n({
translations,
validateInDev: true, // Enable validation
validateOptions: {
checkInterpolation: true, // Verify {variables} match
checkPluralization: true, // Verify plural forms
checkHTML: false, // Allow HTML in translations
checkMissing: true, // Report missing keys
checkExtra: true // Report extra keys
}
});
// Shows validation popup in development with errors
```
## 🛠️ CLI Tools
### Initialize Project
Set up i18n in your project interactively:
```bash
npx svelte-i18n init
```
This will:
- Create translation directories
- Generate initial config files
- Set up type definitions
- Create example translations
### Extract Translation Keys
Scan your code and extract all translation keys:
```bash
# Extract from source code
npx svelte-i18n extract ./src ./translations/template.json
# Specify file extensions
npx svelte-i18n extract ./src ./translations/template.json js ts svelte
```
### Validate Translations
Check for missing or extra keys across all locales:
```bash
# Basic validation
npx svelte-i18n validate ./translations
# Strict validation (exit with error code)
npx svelte-i18n validate ./translations --strict
# Use specific base locale
npx svelte-i18n validate ./translations --base zh
```
### Generate TypeScript Types
Generate type definitions for translation keys:
```bash
# Generate for app translations (default)
npx svelte-i18n generate-types
# Custom paths
npx svelte-i18n generate-types \
--dir ./translations \
--out ./src/types/i18n.ts \
--locale en
# Skip validation of other locales
npx svelte-i18n generate-types --no-validate
```
## 📖 API Reference
### Core Functions
#### `createI18n<TPath>(config)`
Creates a typed i18n instance.
```typescript
const i18n = createI18n<TranslationPaths>({
translations, // Translation data
defaultLocale: 'en', // Default locale
fallbackLocale: 'en', // Fallback for missing translations
namespace: 'app', // Namespace (for libraries)
isMain: true, // Is main app instance?
validateInDev: true, // Enable dev validation
interpolation: {
// Interpolation options
prefix: '{',
suffix: '}'
}
});
```
#### `i18n.t(key, params?)`
Get translated text with optional interpolation.
```typescript
i18n.t('welcome', { name: 'John' }); // "Welcome John!"
i18n.t('items.count', { count: 5 }); // "5 items"
```
#### `i18n.setLocale(locale)`
Change the current locale (async).
```typescript
await i18n.setLocale('zh'); // Switch to Chinese
```
#### `i18n.setLocaleSync(locale)`
Change locale synchronously (for SSR).
```typescript
i18n.setLocaleSync('zh'); // Immediate switch
```
#### `i18n.loadLanguage(locale, translations)`
Dynamically load translations.
```typescript
await i18n.loadLanguage('ja', japaneseTranslations);
```
### Properties
```typescript
i18n.locale; // Current locale ('en')
i18n.locales; // Available locales (['en', 'zh', 'ja'])
i18n.isLoading; // Loading state (true/false)
i18n.errors; // Validation errors (dev only)
i18n.meta; // Language metadata (direction, native name, etc.)
```
### SvelteKit Integration
#### `loadI18nUniversal(i18n, data, url?, options?)`
Universal load function for +layout.ts.
```typescript
await loadI18nUniversal(i18n, data, url, {
storageKey: 'i18n-locale', // localStorage key
cookieName: 'i18n-locale', // Cookie name
defaultLocale: 'en', // Default locale
detectFromPath: true // Detect from URL path
});
```
#### `loadI18nSSR(locale, locales, options?)`
Server-side load function for +layout.server.ts.
```typescript
loadI18nSSR('en', ['en', 'zh'], {
cookieName: 'i18n-locale'
});
```
#### `setupI18nClient(i18n, data, options?)`
Synchronous client setup for +layout.svelte.
```typescript
const result = setupI18nClient(i18n, data, {
defaultLocale: 'en',
restoreFromStorage: true
});
```
#### `initI18nOnMount(i18n, data, options?)`
Async initialization in onMount.
```typescript
await initI18nOnMount(i18n, data, {
initFunction: async (i18n) => {
// Custom initialization
}
});
```
### Formatting Functions
All formatters are locale-aware and reactive:
```typescript
formatNumber(value, style?, options?)
formatCurrency(value, currency?, options?)
formatDate(date, style?, options?)
formatTime(date, style?, options?)
formatRelativeTime(value, unit, options?)
formatList(items, style?, options?)
```
### Utility Functions
```typescript
// Detect browser language
detectBrowserLanguage(); // 'en-US'
// Validate translation schema
validateSchema(translations, options);
// Merge translation objects
mergeTranslations(target, source);
// Get available locales from registry
getAvailableLocales(registry);
// Check if locale is available
isLocaleAvailable(registry, 'zh');
```
## 🔧 Configuration
### Full Configuration Options
```typescript
interface I18nConfig {
// Basic
defaultLocale?: string; // Default: 'en'
fallbackLocale?: string; // Default: same as defaultLocale
supportedLocales?: string[]; // Auto-detected if not set
// Features
validateInDev?: boolean; // Default: true
loadingDelay?: number; // Default: 200ms
namespace?: string; // Default: 'app'
isMain?: boolean; // Default: true for 'app'
// Formatting
interpolation?: {
prefix?: string; // Default: '{'
suffix?: string; // Default: '}'
escapeValue?: boolean; // Default: false
};
pluralization?: {
separator?: string; // Default: '|'
};
// Validation
validateOptions?: {
checkInterpolation?: boolean;
checkPluralization?: boolean;
checkHTML?: boolean;
checkMissing?: boolean;
checkExtra?: boolean;
};
}
```
### Environment Variables
```bash
# .env
VITE_I18N_DEFAULT_LOCALE=en
VITE_I18N_FALLBACK_LOCALE=en
VITE_I18N_SUPPORTED_LOCALES=en,zh,ja,de,fr
VITE_I18N_DEBUG=true
```
## 🎯 Best Practices
### 1. Structure Your Translations
```
src/
translations/
en.json # English (base)
zh.json # Chinese
ja.json # Japanese
locales/ # Alternative structure
en/
common.json
errors.json
forms.json
```
### 2. Use Type Safety
Always generate and use types:
```typescript
// Generate types after translation changes
npm run i18n:types
// Import and use
import type { I18nPath } from '$lib/types/i18n-generated';
export const i18n = createI18n<I18nPath>({ ... });
```
### 3. Handle Loading States
```svelte
{#if i18n.isLoading}
<LoadingSpinner />
{:else}
<Content />
{/if}
```
### 4. Optimize Bundle Size
```typescript
// ❌ Don't import all translations statically
import * as allTranslations from './translations';
// ✅ Import only needed or use dynamic imports
import en from './translations/en.json';
const zh = await import('./translations/zh.json');
```
### 5. Test Your Translations
```typescript
// Run validation in CI/CD
npm run i18n:validate
// Test with different locales
npm run dev -- --locale=zh
```
## 🤝 Contributing
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
### Development Setup
```bash
# Clone the repository
git clone https://github.com/atshelchin/svelte-i18n.git
# Install dependencies
pnpm install
# Start development server
pnpm dev
# Run tests
pnpm test
# Build library
pnpm build
```
## 📄 License
MIT © [Shelchin](https://github.com/atshelchin)
## 🙏 Acknowledgments
Built with ❤️ using:
- [Svelte 5](https://svelte.dev) - The magical disappearing framework
- [SvelteKit](https://kit.svelte.dev) - The fastest way to build Svelte apps
- [TypeScript](https://www.typescriptlang.org) - JavaScript with syntax for types
- [Vite](https://vitejs.dev) - Next generation frontend tooling
Special thanks to all [contributors](https://github.com/atshelchin/svelte-i18n/graphs/contributors) who helped make this project better!
---
<div align="center">
**[Documentation](https://github.com/atshelchin/svelte-i18n#readme)** •
**[Live Demo](https://atshelchin.github.io/svelte-i18n/)** •
**[Examples](https://github.com/atshelchin/svelte-i18n/tree/main/src/routes)** •
**[Report Bug](https://github.com/atshelchin/svelte-i18n/issues)**
Made with ❤️ by [Shelchin](https://github.com/atshelchin)
</div>