Svelte i18n Made Easy
Stores-based i18n with SvelteKit integration and runes support.
Connect SvelteKit to Better i18n in three steps
There is no Svelte-specific package to install: the zero-dependency core client feeds svelte-i18n directly.
Install the client and svelte-i18n
core fetches and caches the messages; svelte-i18n formats them and exposes the reactive store.
npm install @better-i18n/core svelte-i18n
# @better-i18n/core ships zero dependencies —
# it is the same client our React SDKs are built on.Load messages in the root layout
A SvelteKit load() runs on the server first, so the first paint is already translated and the browser never waits on a translation fetch.
import { createI18nCore } from '@better-i18n/core'
// Module scope: the 60s in-memory cache is shared across requests
// instead of being rebuilt on every navigation.
export const betterI18n = createI18nCore({
projectId: 'your-org/your-project', // Settings → General → Project ID
defaultLocale: 'en',
})
/** @type {import('./$types').LayoutLoad} */
export async function load({ params }) {
const locale = params.locale ?? 'en'
return {
locale,
messages: await betterI18n.getMessages(locale),
languages: await betterI18n.getLanguages(),
}
}Register them and read the store
addMessages takes the object core returned as-is, then $_ resolves keys reactively in every component.
<script>
import { addMessages, init, _ } from 'svelte-i18n'
export let data // from +layout.js
// The CDN returns { namespace: { key: value } } — the shape
// svelte-i18n already expects.
addMessages(data.locale, data.messages)
init({ fallbackLocale: 'en', initialLocale: data.locale })
</script>
<h1>{$_('home.title')}</h1>
<p>{$_('home.greeting', { values: { name: 'World' } })}</p>
<slot />How it works
Where a Svelte string comes from
No build step and no bundled locale files: the client resolves a locale at runtime and the edge answers it.
Read path — every locale load
The $_ store reads the messages svelte-i18n already holds.
0 network calls per render
getMessages(locale) inside load() — server-side on the first render.
60s TTL · 0 deps
Cloudflare worker answers from the nearest edge cache.
max-age=60 · always 200
The published translation files the sync worker wrote.
source of truth
Fallback chain — tried in order when a hop fails
Write path — dashboard to app
Proposal reviewed in the dashboard, glossary enforced.
MCP · dashboard · CLI
Sync worker writes the locale files to R2.
better-i18n publish
Fire-and-forget purge of the affected keys and the manifest.
non-critical by design
The next getMessages() past the TTL returns the new copy.
~60s worst case
Quick Start
Add i18n to your Svelte app with stores.
<script>
import { addMessages, locale } from 'svelte-i18n'
import { betterI18n } from '../routes/+layout.js'
export let languages = [] // from load()
async function switchTo(next) {
// Fetch on demand, register, then flip the store.
addMessages(next, await betterI18n.getMessages(next))
locale.set(next)
}
</script>
<select on:change={(e) => switchTo(e.currentTarget.value)}>
{#each languages as language}
<option value={language.code}>{language.name}</option>
{/each}
</select>Features
Related
Explore Other Framework Guides
Get started
Start Building with Svelte i18n
Free tier available. No credit card required.