Localize a Svelte / SvelteKit app: complete guide with Localingos
SvelteKit's load functions and per-request server state pair cleanly with i18n once you know the patterns. This guide takes a fresh SvelteKit app to fully localized using svelte-i18n for the runtime and Localingos for the translation pipeline. About 20 minutes end to end, including SSR-safe locale handling.
Step 1 — Install
npm install svelte-i18n
npm install -g @localingos/cli
localingos login
Step 2 — Wire svelte-i18n
src/lib/i18n/index.ts:
import { init, register, locale } from 'svelte-i18n';
import { browser } from '$app/environment';
export const SUPPORTED = ['en', 'es', 'de', 'fr', 'ja', 'pt-BR', 'zh-CN', 'ko'] as const;
register('en', () => import('./locales/en.json'));
register('es', () => import('./locales/es.json'));
register('de', () => import('./locales/de.json'));
register('fr', () => import('./locales/fr.json'));
const initialLocale = browser
? (localStorage.getItem('locale') || window.navigator.language.split('-')[0])
: 'en';
init({
fallbackLocale: 'en',
initialLocale,
});
src/routes/+layout.svelte:
<script lang="ts">
import '$lib/i18n';
import { isLoading } from 'svelte-i18n';
</script>
{#if !$isLoading}
<slot />
{:else}
<div>Loading…</div>
{/if}
register() with a lazy import means each locale becomes its own chunk — only the active locale ships in the initial bundle.
Step 3 — Source of truth
src/lib/i18n/locales/en.json:
{
"welcome": "Welcome, {name}",
"cart": "{count, plural, one {# item in your cart} other {# items in your cart}}"
}
svelte-i18n uses ICU MessageFormat syntax for plurals — works in every CLDR locale automatically.
Step 4 — Configure Localingos
localingos.json:
{
"projectId": "your-project-id",
"source": { "path": "src/lib/i18n/locales/en.json", "locale": "en" },
"targets": {
"path": "src/lib/i18n/locales/{locale}.json",
"locales": ["es", "de", "fr", "ja", "pt-BR", "zh-CN", "ko"]
},
"placeholders": ["{name}", "{count}"],
"format": "icu"
}
format: "icu" tells Localingos to preserve ICU MessageFormat syntax exactly when translating plurals.
localingos sync
Step 5 — Use in components
<script lang="ts">
import { _ } from 'svelte-i18n';
export let userName: string;
export let itemCount: number;
</script>
<header>
<h1>{$_('welcome', { values: { name: userName } })}</h1>
<p>{$_('cart', { values: { count: itemCount } })}</p>
</header>
$_ is the reactive store version — components automatically re-render when the locale changes.
Step 6 — Language switcher
<script lang="ts">
import { locale } from 'svelte-i18n';
const LOCALES = { en: 'English', es: 'Español', de: 'Deutsch', fr: 'Français' };
function switchLocale(newLocale: string) {
locale.set(newLocale);
localStorage.setItem('locale', newLocale);
}
</script>
<select value={$locale} on:change={e => switchLocale((e.target as HTMLSelectElement).value)}>
{#each Object.entries(LOCALES) as [code, label]}
<option value={code}>{label}</option>
{/each}
</select>
Step 7 — SSR considerations
SvelteKit SSR runs in Node, so localStorage is unavailable. Detect the user's locale from the Accept-Language header in a +layout.server.ts:
// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = ({ request }) => {
const header = request.headers.get('accept-language') || '';
const preferred = header.split(',')[0]?.split(';')[0]?.split('-')[0] || 'en';
return { initialLocale: preferred };
};
Pass it through +layout.svelte to init({ initialLocale: data.initialLocale }). This avoids hydration mismatches between SSR-rendered HTML and client-side hydration.
Step 8 — Automate sync in CI
# .github/workflows/i18n.yml
name: i18n-sync
on:
push: { branches: [main], paths: ['src/lib/i18n/locales/en.json'] }
jobs:
sync:
runs-on: ubuntu-latest
permissions: { contents: write, pull-requests: write }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm install -g @localingos/cli
- run: localingos sync
env: { LOCALINGOS_API_KEY: '${{ secrets.LOCALINGOS_API_KEY }}' }
- uses: peter-evans/create-pull-request@v6
with:
branch: i18n/auto-sync
title: 'chore(i18n): sync translations'
commit-message: 'chore(i18n): sync translations'
Production checklist
- Locale-prefixed routes for SEO. Use SvelteKit's
[lang]dynamic segment:src/routes/[lang]/pricing/+page.svelte. Emit hreflang annotations in+layout.svelte. - Adapter compatibility. Works with adapter-static, adapter-node, adapter-vercel, adapter-cloudflare. No special config needed.
- RTL support. Set
<html dir>based on the active locale in+layout.svelte. - ICU support is built in. svelte-i18n parses MessageFormat natively — no extra runtime.
Wrap up
Your SvelteKit app handles 56 locales with ICU pluralization, server-side locale detection, lazy-loaded chunks, and CI-driven translation sync. Adding a language is one entry in localingos.json and one register() call.
Free tier covers small to mid-size SvelteKit apps end to end.