Install ParaglideJS and the ParaglideJS SvelteKit Adapter.
npx @inlang/paraglide-js init
npm i -D @inlang/paraglide-js-adapter-sveltekit
This will generate a messages/{lang}.json
file for each of your languages. This is where your translation files live.
Add the adapter-plugin to your vite.config.js
file. This will make sure to rerun the paraglide compiler when needed and add the link preprocessor.
import { paraglide } from "@inlang/paraglide-js-adapter-sveltekit/vite"
export default defineConfig({
plugins: [
paraglide({
//recommended
project: "./project.inlang",
outdir: "./src/lib/paraglide",
}),
sveltekit(),
],
})
Create a src/lib/i18n.js
file:
// src/lib/i18n.js
import { createI18n } from "@inlang/paraglide-js-adapter-sveltekit"
import * as runtime from "$lib/paraglide/runtime.js"
export const i18n = createI18n(runtime);
createI18n
will be your one-stop shop for configuring the adapter.
Add the ParaglideJS
component to your layout and pass it the i18n
instance.
<!-- src/routes/+layout.svelte -->
<script>
import { ParaglideJS } from '@inlang/paraglide-js-adapter-sveltekit'
import { i18n } from '$lib/i18n.js'
</script>
<ParaglideJS {i18n}>
<slot />
</ParaglideJS>
In your src/hooks.js
file, use the i18n
instance to add the reroute
hook:
import { i18n } from '$lib/i18n.js'
export const reroute = i18n.reroute()
The reroute hook was added in SvelteKit 2.3.0
In src/hooks.server.js
add the handle hook.
// src/hooks.server.js
import { i18n } from '$lib/i18n.js'
export const handle = i18n.handle()
This will make the language and text-direction on event.locals.paraglide
.
To set the lang
and dir
attributes on your <html>
tag add placeholders in src/app.html
. These placeholders will be replaced by the handle
hook.
<!-- src/app.html -->
<html lang="%paraglide.lang%" dir="%paraglide.textDirection%">
Visit /
to see your default language, and /{lang}
to see other languages. All links should be translated automatically.
Exclude routes from being translated with the exclude
option.
import { createI18n } from "@inlang/paraglide-js-adapter-sveltekit"
import * as runtime from "$lib/paraglide/runtime.js"
export const i18n = createI18n(runtime, {
// don't include the language or base path
exclude: ["/admin", "/login", /^\/user\/\d+$/],
})
Excluded routes will:
- Not have any
rel="alternate"
links added to them - Not have their Paths translated
- Not have links pointing to them translated
Make sure excluded pages are still wrapped in the <ParaglideJS>
so that outgoing links are still translated.
-
/en/about
for English -
/de/uber-uns
for German -
/fr/a-propos
for French
You can have different paths for each language with the pathnames
option.
Don't include the language or the base path.
import { createI18n } from "@inlang/paraglide-js-adapter-sveltekit"
import * as runtime from "$lib/paraglide/runtime.js"
import * as m from "$lib/paraglide/messages.js"
import { match as int } from "../params/int.js"
export const i18n = createI18n(runtime, {
pathnames: {
"/about" : {
en: "/about",
de: "/uber-uns",
fr: "/a-propos",
},
// You can use parameters
// All translations must use identical parameters and names
"/user/[id=int]/[...rest]" : {
en: "/user/[id=int]/[...rest]",
de: "/benutzer/[id=int]/[...rest]",
fr: "/utilisateur/[id=int]/[...rest]",
},
// Instead of a map, you can also pass a message-function
"/admin" : m.admin_path
}
// If you're using matchers in the pathnames, you need to pass them
matchers: { int }
})
Links are translated automatically using a preprocessor. This means that you can use the normal a
-tag and the adapter will translate it for you.
<a href="/about">{m.about()}</a>
<!-- will become on of -->
<a href="/en/about">{m.about()}</a>
<a href="/de/uber-uns">{m.about()}</a>
<a href="/fr/a-propos">{m.about()}</a>
If you want a link to be translated into a specific language set the hreflang
attribute.
<a href="/about" hreflang="de">{m.about()}</a>
<!-- Will always be german, regardless of the current language -->
<a href="/de/uber-uns" hreflang="de">{m.about()}</a>
Opt-out of translation by adding a data-no-translate
attribute.
<!-- this will never be translated -->
<a href="/about" data-no-translate>{m.about()}</a>
Other attributes that are also translated are:
- The
action
attribute onform
elements - The
formaction
attribute onbutton
elements
If you have other attributes that you want to be translated open an issue.
SvelteKit's goto
and redirect
cannot be translated automatically. Localize the URLs you pass to them with i18n.resolveRoute()
.
import { i18n } from '$lib/i18n.js'
import { redirect } from '@sveltejs/kit'
import { goto } from '$app/navigation'
redirect(i18n.resolveRoute("/about", "en"))
//Omitting the language argument uses the current languageTag()
goto(i18n.resolveRoute("/about"))
Language switchers are tricky because we need to dynamically translate the current URL path, which is itself translated. We need to get the untranslated version of the current path & translate it into the target language.
You can get the untranslated path using i18n.route()
// $page.url.pathname = "/base/de/uber-uns"
const route = i18n.route($page.url.pathname)
// route = "/base/about"
Use this to create a language switcher.
<script>
import { availableLanguageTags, languageTag } from "$lib/paraglide/runtime.js"
import { i18n } from '$lib/i18n.js'
import { page } from '$app/stores'
</script>
{#each availableLanguageTags as lang}
<!-- the hreflang attribute decides which language the link points to -->
<a
href={i18n.route($page.url.pathname)}
hreflang={lang}
aria-current={lang === languageTag() ? "page" : undefined}
>
{lang}
</a>
{/each}
Paraglide guesses the text direction using the Intl.Locale
API. This is not supported in all runtimes. Use the textDirection
option to provide the text direction yourself.
import { createI18n } from "@inlang/paraglide-js-adapter-sveltekit"
import * as runtime from "$lib/paraglide/runtime.js"
export const i18n = createI18n(runtime, {
textDirection: {
en: "ltr",
ar: "rtl",
},
})
On the server you can access the current language and text direction on event.locals.paraglide
.
On the client, you can call languageTag()
exported ./paraglide/runtime.js
.
### Using the Language on the Server
#### Avoiding Cross-Talk
SvelteKit does two kinds of work on the server: Loading and Rendering.
-
Loading includes running your
load
functions,actions
or server-hooks. -
Rendering is anything that happens in or is called from a
.svelte
file.
Loading is asynchronous & rendering is synchronous.
During the asynchronous loading, there is danger of crosstalk. If you aren't careful it's possible for one request to override the language of another request. You can avoid this by explicitly specifying which language a message should be in.
import * as m from "$lib/paraglide/messages.js"
export async function load({ locals }) {
const translatedText = m.some_message({ ...message_params }, { languageTag: locals.paraglide.lang })
return { translatedText }
}
During rendering there is no danger of crosstalk. You can safely use messages and the langaugeTag()
function.
You can tell a load function to re-run on language changes by calling depends("paraglide:lang")
.
export async function load({ depends }) {
// The Adapter automatically calls `invalidate("paraglide:lang")` whenever the langauge changes
// This tells SvelteKit to re-run this function whenever that happens
depends("paraglide:lang")
return await myLanguageSpecificData();
}
## Caveats
- Links in the same Layout Component as
<ParagldieJS>
will not be translated. This will also log a warning in development. - Messages are not reactive. Don't use them in server-side module scope.
- Side effects triggered by
data
will run on language changes even if the data didn't change. If the data is language-dependent the side effect will run twice.
The language state get's set when the <ParaglideJS>
component is mounted. Since you usually place it inside +layout.svelte
using messages in the layout's <script>
can cause issues.
<script>
import { ParaglideJS } from '@inlang/paraglide-js-adapter-sveltekit'
import { i18n } from '$lib/i18n.js'
//using messages here can cause hydration issues
</script>
<ParaglideJS {i18n}>
<!-- Using messages here is fine -->
<slot />
</ParaglideJS>
SvelteKit's reroute
hook currently doens't play well with Vercel (see sveltejs/kit#11879), which means that we need to slightly adapt the adapter setup to make it work when deployed to Vercel.
- Remove the
reroute
hook fromsrc/hooks.js
- Move the routes you want to localize
routes
into a[[locale]]
folder - Don't use translated
pathnames
We are working on contributing a fix for sveltejs/kit#11879, so this workaround will hopefully not be needed much longer.
- Translate parameters themselves
- More routing flexibility
- Domain Based routing
- Language Detection & Redirects
- Fix the Caveats
- Allow links in the same component as
- Run seamlessly when deployed to Vercel
- Fix side-effects that are triggered by page-
data
changing double triggering on language changes if they depend on the language.
Play around with it on StackBlitz