This document explains how sveltekit-i18n works internally and how the three packages interact with each other. Understanding the architecture will help you make informed decisions about which package to use and how to configure it for your needs.
- Package Overview
- Package Relationships
- Data Flow
- Loading Strategy
- When to Use Each Package
- Core Concepts
The sveltekit-i18n ecosystem consists of three main packages:
Role: Core i18n functionality
Responsibilities:
- Managing translation state (Svelte stores)
- Loading and caching translations
- Route matching logic
- Preprocessing translations
- Coordinating with parsers
What it doesn't include:
- Message interpolation (delegates to parsers)
- Default parser
Use when: You need custom parsers or maximum flexibility
Role: Complete solution with sensible defaults
Responsibilities:
- Everything from
@sveltekit-i18n/base - Pre-configured with
@sveltekit-i18n/parser-default - Simplified API
Dependencies: @sveltekit-i18n/base + @sveltekit-i18n/parser-default (no external dependencies)
Use when: You want the quickest setup and are happy with default parser syntax
Role: Message interpolation
Packages:
@sveltekit-i18n/parser-defaultβ Simple placeholder/modifier syntax@sveltekit-i18n/parser-icuβ ICU message format
Responsibilities:
- Interpolating variables into translation strings
- Formatting (numbers, dates, currencies)
- Conditional rendering (plurals, gender, etc.)
Use when: You need specific message syntax (included automatically with sveltekit-i18n or @sveltekit-i18n/base)
Here's how the packages relate to each other:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Your SvelteKit App β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Option 1: Use Complete Solution β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β sveltekit-i18n (lib) β β
β β ββββββββββββββββββββββββββββββββββββββββββ β β
β β β @sveltekit-i18n/base β β β
β β β (core functionality) β β β
β β ββββββββββββββββββββββββββββββββββββββββββ β β
β β ββββββββββββββββββββββββββββββββββββββββββ β β
β β β @sveltekit-i18n/parser-default β β β
β β β (included) β β β
β β ββββββββββββββββββββββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Option 2: Use Base with Custom Parser β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β @sveltekit-i18n/base β β
β β (you provide the parser) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β @sveltekit-i18n/parser-icu β β
β β or your custom parser β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
sveltekit-i18n
βββ @sveltekit-i18n/base
βββ @sveltekit-i18n/parser-default
@sveltekit-i18n/base
βββ (no dependencies)
@sveltekit-i18n/parser-default
βββ (no dependencies)
@sveltekit-i18n/parser-icu
βββ intl-messageformat
Here's how translations flow through the system:
import i18n from 'sveltekit-i18n';
const config = {
loaders: [
{
locale: 'en',
key: 'common',
routes: ['/'],
loader: async () => (await import('./en/common.json')).default,
},
],
};
const { t, locale, loadTranslations } = new i18n(config);What happens:
- i18n instance is created
- Loaders are registered (not executed yet)
- Svelte stores are initialized
- Parser is configured
// In +layout.js
await loadTranslations('en', '/');Flow:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. loadTranslations('en', '/') β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. Match loaders β
β - locale === 'en' β
β - routes includes '/' OR routes is undefined β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 3. Execute loader functions β
β loader() β returns translation data β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 4. Preprocess translations β
β - 'full': flatten to dot notation β
β - 'preserveArrays': flatten but keep arrays β
β - 'none': no changes β
β - custom function: your logic β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 5. Store in translations store β
β { en: { 'common.greeting': 'Hello' } } β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 6. Set locale β
β locale.set('en') β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Caching: Each loader runs only once per locale. Results are cached in memory.
<p>{$t('common.greeting', { name: 'Alice' })}</p>Flow:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. $t('common.greeting', { name: 'Alice' }) β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. Get current locale β
β locale.get() β 'en' β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 3. Lookup translation β
β translations['en']['common.greeting'] β
β β 'Hello, {{name}}!' β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 4. Parser.parse() β
β parse('Hello, {{name}}!', [{ name: 'Alice' }]) β
β β 'Hello, Alice!' β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 5. Return result β
β 'Hello, Alice!' β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
locale.set('cs');Flow:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. locale.set('cs') β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. Check if translations exist β
β translations['cs'] ? β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
βββββββββ΄βββββββββ
β β
βΌ βΌ
βββββββββββ ββββββββββββ
β Exists β β Missing β
ββββββ¬βββββ ββββββ¬ββββββ
β β
β βΌ
β βββββββββββββββββββββββ
β β Load translations β
β β (matching loaders) β
β ββββββ¬βββββββββββββββββ
β β
βββββββββββ΄βββββββββ
βΌ
βββββββββββββββββββββββββββββ
β 3. Update locale store β
β triggers reactivity β
βββββββββββββ¬ββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββ
β 4. All $t() re-evaluate β
β UI updates β
βββββββββββββββββββββββββββββ
The library uses SvelteKit's routing to determine which translations to load:
const config = {
loaders: [
// No routes specified β loads on every page
{
locale: 'en',
key: 'common',
loader: async () => (await import('./en/common.json')).default,
},
// Exact match β loads only on '/'
{
locale: 'en',
key: 'home',
routes: ['/'],
loader: async () => (await import('./en/home.json')).default,
},
// Regex β loads on matching routes
{
locale: 'en',
key: 'products',
routes: [/^\/products/],
loader: async () => (await import('./en/products.json')).default,
},
],
};Matching algorithm:
function shouldLoadTranslation(loader, currentRoute) {
// No routes specified β always load
if (!loader.routes || loader.routes.length === 0) {
return true;
}
// Check each route pattern
for (const route of loader.routes) {
if (typeof route === 'string') {
// Exact string match
if (currentRoute === route) return true;
} else if (route instanceof RegExp) {
// Regex match
if (route.test(currentRoute)) return true;
}
}
return false;
}Server-Side (SSR):
- Translations load during
+layout.jsor+page.jsload - Data is serialized and sent to client
- Hydration picks up from there
Client-Side:
- When locale changes, new translations load on client
- When navigating, route-specific translations load
- Loading state available via
$loadingstore
In-Memory Cache:
{
'en': {
'common': { /* translations */ },
'home': { /* translations */ },
},
'cs': {
'common': { /* translations */ },
},
}Cache Refresh:
- Client: Never refreshes (single session)
- Server: Configurable via
cacheoption (default: 24 hours)
const config = {
cache: 86400000, // 24 hours in milliseconds
// or: Number.POSITIVE_INFINITY (never refresh)
};import i18n from 'sveltekit-i18n';When:
- β You want the quickest setup
- β Default parser syntax is sufficient
- β You don't need custom parsers
- β You want zero dependencies
Best for: Most projects, rapid prototyping, simple to medium complexity
import i18n from '@sveltekit-i18n/base';
import parser from '@sveltekit-i18n/parser-icu';When:
- β You need ICU message format
- β You want to create a custom parser
- β You're migrating from another i18n library
- β You need specific message syntax
Best for: Complex projects, enterprise applications, specific requirements
The library uses Svelte stores for reactivity:
// Readable stores (read-only)
$t // Translation function
$locales // Available locales
$loading // Loading state
$initialized // Initialization state
// Writable stores (can be updated)
$locale // Current localeReactivity: When stores update, all components using them re-render automatically.
Transforms nested objects into flat dot notation:
Input:
{
"user": {
"profile": {
"name": "Name",
"email": "Email"
}
}
}Output (preprocess: 'full'):
{
"user.profile.name": "Name",
"user.profile.email": "Email"
}Why? Enables efficient lookups and simpler translation keys in code.
All parsers implement this interface:
interface Parser {
parse(
value: any, // Translation value
params: any[], // Parameters from $t()
locale: string, // Current locale
key: string // Translation key
): string;
}Example implementation:
const simpleParser = () => ({
parse: (value, params) => {
const vars = params[0] || {};
return String(value).replace(
/\{(\w+)\}/g,
(_, key) => vars[key] ?? key
);
},
});Namespaces organize translations into logical groups:
common β Shared UI, navigation, errors
home β Homepage content
products β Product-related text
checkout β Checkout flow
Benefits:
- Easier to manage
- Enables lazy loading
- Better code organization
- Multiple teams can work independently
sveltekit-i18n:
- Core: ~5KB (minified)
- Includes: base + parser-default
- No external dependencies
@sveltekit-i18n/base + parser-icu:
- Base: ~5KB (zero dependencies)
- Parser-ICU: ~15KB + intl-messageformat dependency
Best practices:
- Use route-based loading (don't load everything at once)
- Keep common translations small
- Use code splitting (dynamic imports)
- Enable server-side caching
- Translation lookup: O(1) (object property access)
- Parser execution: Varies by complexity
- Store updates: Svelte's efficient reactivity
The sveltekit-i18n architecture is designed to be:
- Modular β Use only what you need
- Flexible β Customize with parsers and config
- Performant β Lazy loading and efficient caching
- TypeScript β Complete type definitions
- Developer-friendly β Simple API, clear concepts
Choose the right package for your needs and leverage the loading strategies to build efficient, multilingual SvelteKit applications.
- Getting Started β Learn by building
- API Documentation β Complete reference
- Best Practices β Recommended patterns
- Parsers β Parser details