Lightweight i18n library for Svelte 5 with runes support. Type-safe translations, SSR-ready, lazy loading, and Intl formatters.
npm install i18n-svelte-runes-lite> Lightweight, type-safe internationalization for Svelte 5 with Runes
✅ Zero Dependencies - Pure TypeScript + Svelte 5
✅ Type Safety - Full TypeScript autocomplete for translation keys
✅ Svelte 5 Native - Built with $state and $derived runes
✅ SSR Compatible - Context API support for SvelteKit
✅ Magic Hook - One-line setup for secure locale persistence
✅ Multi-Environment - Works in SvelteKit, Wails, and SPAs
✅ Auto Language Detection - Detects browser/OS language preference
✅ Lazy Loading - Load translations on-demand
✅ Namespace Support - Split translations by feature/route
✅ Intl Integration - Uses native Intl APIs
✅ Small Bundle - < 3KB gzipped
``typescript
// src/lib/i18n/index.ts
import { createI18n } from 'i18n-svelte-runes-lite';
import en from './locales/en.json';
import pl from './locales/pl.json';
// Export i18n instance for reactive access to i18n.locale
export const i18n = createI18n
translations: { en, pl },
initialLocale: 'en'
});
export const t = i18n.t;
export const setLocale = i18n.setLocale;
`
`svelte
Current: {i18n.locale}
$3
`svelte
``svelte
{t('welcome.title')}
Current: {i18n.locale}
`Documentation
| Guide | Description |
|-------|-------------|
| Migration Guide | Migrate from Svelte 4 or other i18n libraries |
| Magic Hook (Recommended) | One-line setup with automatic persistence |
| Singleton (Wails/SPA) | Use in desktop apps and SPAs |
| SvelteKit SSR | Use with server-side rendering |
| SvelteKit Complete | Full setup guide with zero flash |
| Namespaces | Split translations by feature/route |
| Lazy Loading | Load translations on-demand |
API Reference
$3
Creates a new i18n instance.
`typescript
const i18n = createI18n({
translations?: Record, // Eager load all
loaders?: Record Promise>, // Lazy load on demand
initialLocale?: string,
fallbackLocale?: string, // Persistence options
strategy?: 'auto' | 'bridge' | 'cookie' | 'localStorage' | 'none',
persistenceEndpoint?: string, // Default: '/__i18n/save'
reloadOnChange?: boolean, // Reload after bridge persistence
environment?: 'auto' | 'sveltekit' | 'wails' | 'spa',
// Dynamic locale loading hook (great for namespaced mode)
onLocaleChange?: (newLocale, oldLocale) => Promise,
// Namespace options (for splitting by feature)
namespaceLoaders?: Record,
defaultNamespace?: string, // Default: 'common'
ssrLoadedNamespaces?: Record // SSR hydration fix
});
`$3
Creates a SvelteKit server hook for automatic locale persistence.
`typescript
import { createI18nHook } from 'i18n-svelte-runes-lite/server';const i18nHook = createI18nHook({
fallbackLocale?: string, // Default: 'en'
supportedLocales?: string[], // For validation
cookieName?: string, // Default: 'locale'
endpoint?: string, // Default: '/__i18n/save'
cookieMaxAge?: number, // Default: 31536000 (1 year)
});
export const handle = i18nHook;
`$3
`typescript
// Reactive state
i18n.locale // Current locale (getter)
i18n.isLoadingLocale // Loading state for current locale (getter)
i18n.isLoadingNamespace // Loading state for any namespace (getter)// Actions
await i18n.setLocale('pl'); // Switch locale
await i18n.loadLocale('pl'); // Pre-load locale
// Namespace loading
await i18n.loadNamespace('dashboard'); // Load namespace
await i18n.loadNamespace('dashboard', 'pl'); // Load for specific locale
i18n.isNamespaceLoaded('dashboard'); // Check if loaded
i18n.getAvailableNamespaces(); // List available namespaces
i18n.addSsrLoadedNamespaces('en', ['common', 'admin']); // Mark SSR-loaded
// Translation
i18n.t('key.path', { param: 'value' }); // Translate with params
// Formatting
i18n.fmt.number(1234.56);
i18n.fmt.currency(99.99);
i18n.fmt.date(new Date());
// Utilities
i18n.supportedLocales; // Array of available locales
i18n.isLocaleSupported('pl'); // Check if locale exists
`Type Safety
Translation keys are automatically typed from your JSON schema:
`typescript
// locales/en.json
{
"nav": {
"dashboard": "Dashboard",
"settings": "Settings"
}
}// In your code:
t('nav.dashboard'); // ✅ Autocomplete
t('nav.missing'); // ❌ TypeScript error
`Pluralization
`typescript
// locales/en.json
{
"items": {
"zero": "No items",
"one": "{{count}} item",
"other": "{{count}} items"
}
}// In your code:
t('items', { count: 0 }); // "No items"
t('items', { count: 1 }); // "1 item"
t('items', { count: 5 }); // "5 items"
`Lazy Loading
Load translations on-demand to reduce bundle size:
`typescript
const i18n = createI18n({
translations: { en }, // Load default only
loaders: { // Load others on demand
pl: () => import('./locales/pl.json'),
de: () => import('./locales/de.json'),
fr: () => import('./locales/fr.json')
},
initialLocale: 'en'
});// Automatically loads when switching
await i18n.setLocale('pl');
`Auto Language Detection
The library automatically detects the user's preferred language from their browser or OS settings when no explicit preference is saved.
Detection priority:
| Environment | Priority Order |
|-------------|----------------|
| SvelteKit (SSR) | 1. Cookie → 2.
Accept-Language header → 3. Fallback |
| Wails/Desktop | 1. localStorage → 2. navigator.language → 3. Fallback |
| SPA | 1. Cookie → 2. navigator.language → 3. Fallback |How it works:
`typescript
// No initialLocale needed - auto-detects from browser/OS
const i18n = createI18n({
translations: { en, pl, de },
fallbackLocale: 'en' // Used when detection fails
});// User with Polish browser settings → i18n.locale === 'pl'
// User with German browser settings → i18n.locale === 'de'
// User with French browser settings → i18n.locale === 'en' (fallback)
`Language matching:
- Matches exact locales (
pl → pl)
- Falls back from regional to base (pl-PL → pl)
- Matches base to regional (pl → pl-PL if only pl-PL is available)
- Tries all languages in navigator.languages arrayOnce the user manually changes the locale (via
setLocale()), their preference is persisted and used on subsequent visits.Project Setup CLI
Automatically set up i18n in your Svelte/SvelteKit project:
`bash
npx i18n-runes init
`The CLI will:
- Detect your project type (SvelteKit, Wails/Desktop, or SPA)
- Ask for your supported languages
- Generate locale files with sample translations
- Create the i18n configuration file
- Update your Vite config if needed
> SvelteKit Default: For SvelteKit projects, the CLI now defaults to namespaced structure (
en/common.json instead of en.json). This provides better SSR support with client-side caching, parallel namespace loading, and the onLocaleChange hook for dynamic locale switching.$3
For CI/CD or scripts, use:
`bash
I18N_YES=1 npx i18n-runes init
`This uses defaults: English only,
src/lib/i18n/locales path.Trans Component
For simple translations with interpolation:
`svelte
`TransRich Component
For translations with components or rich formatting:
`svelte
{#snippet link(content)}
{content}
{/snippet}
{#snippet bold(content)}
{content}
{/snippet}
`Translation CLI
Automatically translate missing keys using OpenAI or any compatible LLM API.
$3
Add to your
package.json:`json
{
"scripts": {
"i18n:translate": "i18n-translate",
"i18n:translate:dry": "i18n-translate --dry-run"
},
"i18n": {
"localesDir": "src/lib/i18n/locales",
"sourceLang": "en"
}
}
`$3
Add your API key to
.env (automatically loaded):
`bash
OPENAI_API_KEY=sk-xxx
`Then run:
`bash
Preview what would be translated (no API call)
npm run i18n:translate:dryTranslate missing keys
npm run i18n:translate
`Using local LLM (Ollama):
`bash
In .env
OPENAI_BASE_URL=http://localhost:11434/v1/chat/completions
OPENAI_MODEL=llama3.2
OPENAI_API_KEY=ollama
`$3
Via
package.json "i18n" field:
`json
{
"i18n": {
"localesDir": "src/lib/i18n/locales",
"sourceLang": "en",
"batchSize": 20
}
}
`Or via
i18n.config.json:
`json
{
"localesDir": "src/lib/i18n/locales",
"sourceLang": "en",
"batchSize": 20,
"api": {
"url": "https://api.openai.com/v1/chat/completions",
"model": "gpt-4o-mini"
}
}
`$3
| Option | Description |
|--------|-------------|
|
--locales, -l | Path to locales directory |
| --source, -s | Source language (default: en) |
| --target, -t | Translate only this language |
| --dry-run, -d | Preview without making changes |
| --verbose, -v | Show detailed output |
| --no-backup` | Skip creating .bak files |MIT