@shelchin/svelte-i18n
v0.0.37
Published
The last Svelte i18n library you'll ever need. Type-safe, AI-powered, zero-config.
Maintainers
Readme
@shelchin/svelte-i18n
The last Svelte i18n library you'll ever need. Type-safe, zero-config, with seamless SSR/CSR support.
⚠️ 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
# 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:
# Run init command (auto-detects project type and generates config)
pnpm exec svelte-i18n init
# or
npx svelte-i18n initThis will:
- Create
src/translations/directory structure - Generate sample translation files (
locales/en.json,locales/zh.json) - Create
i18n.tsconfiguration file with type-safe setup - Generate TypeScript type definitions
The generated i18n.ts will look like:
// 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:
// 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:
// 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:
<!-- 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
<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:
// 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
# 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.tsValidate Translations
# Check for missing translations
pnpm exec svelte-i18n validate src/translations/localesExtract Translation Keys
# 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:
pnpm exec svelte-i18n generate-typesThis creates type definitions that provide autocomplete for all translation keys:
// 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):
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:
<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:
<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:
// 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:
// 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:
// 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:
// +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:
// 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:
// 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:
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:
npx svelte-i18n initThis 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:
# 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 svelteValidate Translations
Check for missing or extra keys across all locales:
# 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 zhGenerate TypeScript Types
Generate type definitions for translation keys:
# 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.
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.
i18n.t('welcome', { name: 'John' }); // "Welcome John!"
i18n.t('items.count', { count: 5 }); // "5 items"i18n.setLocale(locale)
Change the current locale (async).
await i18n.setLocale('zh'); // Switch to Chinesei18n.setLocaleSync(locale)
Change locale synchronously (for SSR).
i18n.setLocaleSync('zh'); // Immediate switchi18n.loadLanguage(locale, translations)
Dynamically load translations.
await i18n.loadLanguage('ja', japaneseTranslations);Properties
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.
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.
loadI18nSSR('en', ['en', 'zh'], {
cookieName: 'i18n-locale'
});setupI18nClient(i18n, data, options?)
Synchronous client setup for +layout.svelte.
const result = setupI18nClient(i18n, data, {
defaultLocale: 'en',
restoreFromStorage: true
});initI18nOnMount(i18n, data, options?)
Async initialization in onMount.
await initI18nOnMount(i18n, data, {
initFunction: async (i18n) => {
// Custom initialization
}
});Formatting Functions
All formatters are locale-aware and reactive:
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
// 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
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
# .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.json2. Use Type Safety
Always generate and use types:
// 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
{#if i18n.isLoading}
<LoadingSpinner />
{:else}
<Content />
{/if}4. Optimize Bundle Size
// ❌ 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
// 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 for details.
Development Setup
# 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
🙏 Acknowledgments
Built with ❤️ using:
- Svelte 5 - The magical disappearing framework
- SvelteKit - The fastest way to build Svelte apps
- TypeScript - JavaScript with syntax for types
- Vite - Next generation frontend tooling
Special thanks to all contributors who helped make this project better!
Documentation • Live Demo • Examples • Report Bug
Made with ❤️ by Shelchin
