( English | 繁體中文 )
svelte-tiny-i18n is a lightweight, type-safe, and reactive i18n (internationalization) library for Svelte and SvelteKit, built entirely on Svelte Stores.
This library is "headless," meaning it only provides the core logic and Svelte stores, leaving the UI and component integration entirely up to the developer.
svelte-tiny-i18n is for developers who value extreme lightweightness, zero dependencies, and zero build-time configuration, while still enjoying a native Svelte store experience and instant TypeScript inference.
Its key advantage: Zero-config type safety. You define your supported locales (e.g., ['en', 'es']) in a single config file, and TypeScript immediately provides type-checking and autocompletion for your setLocale function (e.g., setLocale('es') is valid, setLocale('fr') is an error) — all without running a code generator.
This makes it the ideal choice for small-to-medium projects and Svelte purists.
Assuming a project structure like this:
/src
├── /lib
│ └── i18n.ts <- 1. Config File
└── /routes
└── /[lang]
├── +layout.ts <- 2. SvelteKit Integration
├── +page.svelte <- 3. Usage
└── /about
└── +page.svelte
src/lib/i18n.ts (Config File)
import { createI18nStore, defineI18nConfig, type inferSupportedLocale } from 'svelte-tiny-i18n';
const config = defineI18nConfig({
supportedLocales: ['en', 'es'],
defaultLocale: 'en',
localStorageKey: 'lang',
initialTranslations: [
{
hello: { en: 'Hello', es: 'Hola' }
}
]
});
export const i18n = createI18nStore(config);
export type SupportedLocale = inferSupportedLocale<typeof i18n>;
src/routes/[lang]/+layout.ts (SvelteKit Integration)
import { i18n } from '$lib/i18n';
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = ({ params }) => {
// 'lang' comes from your route, e.g., /[lang]/
i18n.setLocale(params.lang);
return {};
};
src/routes/[lang]/+page.svelte (Usage)
<script lang="ts">
import { i18n } from '$lib/i18n';
const { t, setLocale } = i18n;
</script>
<h1>{$t('hello')}</h1>
<button on:click={() => setLocale('es')}>Español</button>
npm install svelte-tiny-i18n
pnpm add svelte-tiny-i18n
yarn add svelte-tiny-i18n
The best way to use svelte-tiny-i18n is to create a dedicated singleton instance.
Create a file at /src/lib/i18n.ts (or your preferred location).
// /src/lib/i18n.ts
import {
createI18nStore,
defineI18nConfig,
type inferSupportedLocale,
type inferPartialTranslationEntry,
type inferTranslationEntry
} from 'svelte-tiny-i18n';
// 1. Define the config
const i18nConfig = defineI18nConfig({
// Define all supported languages
supportedLocales: ['en', 'es', 'zh-TW'],
// Default language
defaultLocale: 'en',
// Key for storing the language in localStorage
localStorageKey: 'my-app-language',
// (Optional) Log warnings to the console if a key is not found
devLogs: true,
// Define initial, global translations
initialTranslations: [
{
hello: {
en: 'Hello, {name}!',
es: '¡Hola, {name}!',
'zh-TW': '你好, {name}!'
},
goodbye: {
en: 'Goodbye',
es: 'Adiós',
'zh-TW': '再見'
}
}
]
});
// 2. Create and export the i18n instance
export const i18n = createI18nStore(i18nConfig);
// 3. (Optional) Export inferred types for app-wide type safety
export type SupportedLocale = inferSupportedLocale<typeof i18n>;
export type TranslationEntry = inferTranslationEntry<typeof i18n>;
export type PartialTranslationEntry = inferPartialTranslationEntry<typeof i18n>;
Use the derived store $t to get translations, and the locale store to read or setLocale to set the language.
<script lang="ts">
import { i18n } from '$lib/i18n';
// Destructure the stores and functions
const { t, locale, setLocale } = i18n;
</script>
<h1>{$t('hello', { name: 'World' })}</h1>
<nav>
<p>Current language: {$locale}</p>
<button on:click={() => setLocale('en')}>English</button>
<button on:click={() => setLocale('es')}>Español</button>
<button on:click={() => setLocale('zh-TW')}>繁體中文</button>
</nav>
<p>{$t('a.missing.key')}</p>
To make the i18n state available on both the server and client, and to initialize from a URL parameter (e.g., /es/about), use it in your root +layout.ts.
// /src/routes/+layout.ts
import { i18n } from '$lib/i18n';
import type { LayoutLoad } from './$types';
// This load function runs on both SSR and CSR
export const load: LayoutLoad = ({ params }) => {
// 'lang' must match your route parameter, e.g., /[lang]/
const { lang } = params;
// The setLocale function validates the lang
// and sets the 'locale' store.
i18n.setLocale(lang);
// You can optionally return lang, but the store itself is already set
return { lang };
};
Note: Your SvelteKit route structure must be similar to /src/routes/[lang]/... for params.lang to be available.
You don't need to load all translations at startup. You can load them on demand in a page's +page.ts or +layout.ts using extendTranslations.
Define page-specific translations:
// /src/locales/profile.ts
import type { PartialTranslationEntry } from '$lib/i18n';
export const profileTranslations: PartialTranslationEntry = {
'profile.title': {
en: 'My Profile',
es: 'Mi Perfil',
'zh-TW': '個人資料'
},
'profile.edit_button': {
en: 'Edit'
// Missing 'es' and 'zh-TW' is allowed!
}
};
Load them in the page's loader:
// /src/routes/profile/+page.ts
import { i18n } from '$lib/i18n';
import { profileTranslations } from '$locales/profile';
import type { PageLoad } from './$types';
export const load: PageLoad = () => {
// Dynamically add the translations for this page
i18n.extendTranslations([profileTranslations]);
// You can also await an async import
// const { jsonTranslations } = await import('$locales/profile.json');
// i18n.extendTranslations([jsonTranslations]);
};
The new translations are now merged into the store and available via the $t function.
svelte-tiny-i18n is designed to be the "sweet spot" for Svelte developers who need a simple, fast, and type-safe solution without the overhead of larger libraries.
writable and derived), it integrates seamlessly into the Svelte reactivity model.extendTranslations), and simple variable substitution.<html> lang attribute; see the FAQ for a recipe.)| Dimension | svelte-tiny-i18n (This) |
typesafe-i18n |
svelte-i18n |
|---|---|---|---|
| Bundle Size | Tiny (<1kb) | Tiny (~1kb) | Medium (~15kb+) |
| Core Mechanism | Zero-dependency Svelte Stores + Simple String Replace | Build-time Generator | Runtime ICU Parser |
| Type Safety | High (Instant Inference) | Very High (Code-Gen) | Medium (Manual setup) |
| Setup Complexity | Very Low (Single config file) | Medium (Requires generator setup) | Low (Install and use) |
| Adv. Formatting | Simple {var} replacement only |
Yes (Plurals, Dates, Numbers) | Yes (Full ICU Support) |
| Key Trade-Off | Trades ICU features for extreme lightness & zero-config type safety. | Trades setup simplicity for the strongest type safety (incl. args). | Trades bundle size for the most powerful ICU features. |
svelte-i18n is a great choice. It uses the industry-standard formatjs and ICU syntax.$t('key', { arg: 'val' })) and are willing to set up a code generator, typesafe-i18n is excellent.svelte-tiny-i18n is the ideal choice if you value:This library intentionally trades complex ICU formatting (which svelte-i18n provides) and argument-level type safety (which typesafe-i18n provides) for extreme simplicity, minimal size, and zero-config type safety.
createI18nStore(config)Creates the core i18n instance. Returns an object containing stores and functions.
defineI18nConfig(config)A helper function for defining your I18nConfig that provides full type safety and inference.
i18n)When you call createI18nStore, you get an object with:
t: (Read-only derived store) The translation function.$t('key')$t('key', { placeholder: 'value' })locale: (Readable store) The currently active language code (e.g., en). This store is readable; to update it, use the setLocale() function.setLocale(lang: string | null | undefined): A function to safely set the initial language, typically called from the root +layout.ts.lang is a supported language, it sets the locale store.lang is invalid (or null/undefined), it's ignored, and the locale store keeps its current value.extendTranslations(newTranslations: PartialTranslationEntry[]): Merges new translations (an array) into the main store and triggers an update.supportedLocales: (Read-only readonly string[]) The array of supported languages from your config.defaultLocale: (Read-only string) The default language from your config.localStorageKey: (Read-only string) The localStorage key from your config.For robust type safety in your app, you can import type helpers directly from svelte-tiny-i18n.
inferSupportedLocale<typeof i18n>: Infers the union of supported language codes (e.g., 'en' | 'es' | 'zh-TW').inferTranslationEntry<typeof i18n>: Infers the full translation entry type (e.g., { en: string; es: string; 'zh-TW': string; }).inferPartialTranslationEntry<typeof i18n>: Infers the type for translation files (e.g., { [key: string]: { en?: string; es?: string; 'zh-TW'?: string; } }).Example:
// /src/lib/i18n.ts
// ... (as shown in "Quick Start")
export type SupportedLocale = inferSupportedLocale<typeof i18n>;
// /src/components/SomeComponent.svelte
import { i18n } from '$lib/i18n';
import type { SupportedLocale } from '$lib/i18n';
// The 'lang' variable is now type-checked
function setLanguage(lang: SupportedLocale) {
i18n.setLocale(lang);
}
setLanguage('en'); // OK
setLanguage('fr'); // TypeScript Error
A: It's even simpler. You don't need the +layout.ts file or the i18n.setLocale() step.
The store will automatically initialize its language in the browser by checking localStorage and navigator.language. You can change the language at any time by simply calling i18n.setLocale('new_lang') in your components.
<html> lang attribute or handle RTL (Right-to-Left) languages?A: This library is "headless," so it doesn't modify the DOM for you. You can easily manage this yourself by subscribing to the locale store in your root layout component (e.g., +layout.svelte for SvelteKit or App.svelte for Svelte/Vite).
Here is an example for a SvelteKit project that sets both the lang and dir attributes:
<script lang="ts">
import { i18n } from '$lib/i18n';
const { locale } = i18n;
// Define which of your supported locales are RTL
// (e.g., Arabic 'ar', Hebrew 'he')
const rtlLocales: string[] = ['ar', 'he'];
$: if (typeof document !== 'undefined') {
const direction = rtlLocales.includes($locale) ? 'rtl' : 'ltr';
// Dynamically set attributes on <html>
document.documentElement.lang = $locale;
document.documentElement.dir = direction;
}
</script>
<slot />