Paraglide JS is SvelteKit's official i18n integration.
It's a compiler-based i18n library that emits tree-shakable translations, leading to up to 70% smaller i18n bundle sizes compared to runtime based libraries.
- Fully type-safe with IDE autocomplete
- SEO-friendly localized URLs with the i18n routing strategy
- Works with CSR, SSR, and SSG
Getting started
Via the Svelte CLI
npx sv add paraglide
Manually
Install paraglide js
npx @inlang/paraglide-js@latest init
Add the paraglideVitePlugin() to vite.config.js.
[!NOTE]You can define strategy however you need.
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
+import { paraglideVitePlugin } from '@inlang/paraglide-js';
export default defineConfig({
plugins: [
sveltekit(),
+ paraglideVitePlugin({
+ project: './project.inlang',
+ outdir: './src/lib/paraglide',
+ emitTsDeclarations: true,
+ experimentalPerLocaleBuild: true,
+ strategy: ['url', 'cookie', 'baseLocale'],
+ })
]
});
emitTsDeclarations keeps the Svelte and TypeScript language servers in sync when you add or rename message keys while the dev server is running.
Add %lang% and %dir% to src/app.html.
See https://svelte.dev/docs/kit/accessibility#The-lang-attribute for more information.
<!doctype html>
-<html lang="en">
+<html lang="%lang%" dir="%dir%">
...
</html>
Add the paraglideMiddleware() to src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
import { paraglideMiddleware } from '$lib/paraglide/server';
import { getTextDirection } from '$lib/paraglide/runtime';
// creating a handle to use the paraglide middleware
const paraglideHandle: Handle = ({ event, resolve }) =>
paraglideMiddleware(event.request, ({ request: localizedRequest, locale }) => {
event.request = localizedRequest;
return resolve(event, {
transformPageChunk: ({ html }) => {
return html
.replace('%lang%', locale)
.replace('%dir%', getTextDirection(locale));
}
});
});
export const handle: Handle = paraglideHandle;
With experimentalPerLocaleBuild: true, this same hook also gives SvelteKit's
renderer the request-local locale used to select the client asset graph. No
additional SvelteKit adapter option is needed. The feature requires Vite 8 and
is currently tested with SvelteKit 2.69.x. The base locale keeps the normal
_app/immutable URLs; other locales are emitted below
_app/immutable/__paraglide/<locale-id>/ so SvelteKit adapters retain their
immutable caching behavior.
The experiment intentionally supports SvelteKit's default asset and router
layout: Vite 8.x, kit.appDir: "_app", empty kit.paths.base and
kit.paths.assets, kit.output.bundleStrategy: "split", and
kit.router.resolution: "client". The global locale strategy must start with
url or cookie; routeStrategies, service workers, Vite base, source
maps, and build.minify: false are rejected. Unsupported combinations fail
during configuration or build.
Add a reroute hook in src/hooks.ts
IMPORTANT: The reroute() function must be exported from the src/hooks.ts file, not src/hooks.server.ts.
import type { Reroute } from '@sveltejs/kit';
import { deLocalizeUrl } from '$lib/paraglide/runtime';
export const reroute: Reroute = (request) => {
return deLocalizeUrl(request.url).pathname;
};
Usage
import { m } from '$lib/paraglide/messages.js';
import { getLocale, setLocale } from '$lib/paraglide/runtime.js';
// Use messages
m.greeting({ name: 'World' }); // "Hello World!"
// Get and set locale
getLocale(); // "en"
setLocale('de'); // switches to German
Learn more about messages, parameters, and locale management →
Static site generation (SSG)
Enable pre-rendering by adding the following line to routes/+layout.ts:
// routes/+layout.ts
+export const prerender = true;
Then add a locale switcher in routes/+layout.svelte to generate all pages during build time. SvelteKit crawls anchor tags during the build and is, thereby, able to generate all pages statically. If you already have a visible locale switcher that links to every locale variant, nothing extra is required. Add data-sveltekit-reload (see paraglide-js#472) so locale switches trigger a full reload and the new locale is applied.
Full reloads are required by experimentalPerLocaleBuild: client-side
navigation must not mix one locale's hydrated graph with another locale's
messages. Add data-sveltekit-reload to every link that can cross locales;
setLocale() already reloads by default.
<script>
import { page } from '$app/state';
import { resolve } from '$app/paths';
import { locales, localizeHref } from '$lib/paraglide/runtime';
</script>
<nav class="locale-switcher" aria-label="Languages">
{#each locales as locale}
<a href={resolve(localizeHref(page.url.pathname, { locale }))} data-sveltekit-reload>
{locale}
</a>
{/each}
</nav>
<slot></slot>
experimentalPerLocaleBuild does not support the static adapter's SPA fallback
mode (ssr = false with a fallback page): one fallback document cannot select
a locale-specific client graph. Use SSR or prerender every localized page. If
you disable experimentalPerLocaleBuild and use SPA mode, make asset paths
absolute to avoid locale-prefixed 404s (see paraglide-js#503):
// svelte.config.js
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
+ paths: {
+ relative: false
+ }
}
};
export default config;
Non-default bundle strategies and SvelteKit service workers are not compatible with this experiment.
Troubleshooting
URL and locale getting out of sync
If you use SSR with localized URLs, remember that the initial document request runs on the server. The server cannot read localStorage, so a strategy like:
["localStorage", "preferredLanguage", "url", "baseLocale"]
can still redirect the first request based on preferredLanguage or url before hydration. If a stored override must affect the first request too, include a cookie strategy:
["localStorage", "cookie", "preferredLanguage", "url", "baseLocale"]
Use shouldRedirect() in the root +layout.svelte only if you also want to re-sync the URL after client-side navigations. It does not replace the server-side middleware for the first page load. See the client-side redirects guide.
Disabling AsyncLocalStorage
If you're deploying to Vercel Edge or to Cloudflare Workers with Node.js compatibility enabled, keep AsyncLocalStorage enabled. Those runtimes support it today, so disableAsyncLocalStorage is no longer part of the recommended SvelteKit setup.
disableAsyncLocalStorage remains available as a compatibility fallback for runtimes that do not provide AsyncLocalStorage or node:async_hooks but still isolate each request.
[!WARNING]Only use this fallback when your runtime guarantees per-request isolation. Using it in a multi-request server environment could leak locale state between concurrent requests.
See AsyncLocalStorage in the Middleware Guide if you need that escape hatch.
No locale OR different locale when calling messages outside of .server.ts files
If you call messages on the server outside of load functions or hooks, you might run into issues with the locale not being set correctly. This can happen if you call messages outside of a request context.
// hello.ts
import { m } from './paraglide/messages.js';
// 💥 there is no url in this context to retrieve
// the locale from.
console.log(m.hello());
