Paraglide JS

Paraglide JS

Tool
i18n library for SvelteKit

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

Source code

Getting started

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',
+			strategy: ['url', 'cookie', 'baseLocale'],
+		})
	]
});

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;

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-renderering 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.

<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>

If you use the static adapter with ssr = false (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;

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());