
Why Paraglide?
With Paraglide's treeshakeable messages, each page only loads the messages it actually uses. Incremental loading like this would usually take forever to get right, with Paraglide you get it for free.
Use it with your Favorite Framework
Paraglide is framework agnostic, but there are framework-specific libraries available. If there is one for your framework you will want to follow its documentation instead. If there isn't, read on.
People Love It
A few recent comments.
Getting started
To use Paraglide standalone without a framework, run the following command:
npx @inlang/paraglide-js@latest init
This will:
- Install necessary dependencies
- Generate a
messages/
folder where your translation files live - Add the Paraglide compiler to your
build
script in package.json
- Create necessary configuration files
Running the Paraglide compiler will generate a src/paraglide
folder. This folder contains all the code that you will use in your app.
Adding and Editing Messages
Messages are stored in messages/{lang}.json
. To add a message simply add a key-value pair. You can add parameters with curly braces.
// messages/en.json
{
"$schema": "https://inlang.com/schema/inlang-message-format",
+ "greeting": "Hello {name}!"
}
Make sure to re-run the paraglide compiler after editing your messages.
npx @inlang/paraglide-js compile --project ./project.inlang --outdir ./src/paraglide
If you are using Bundler, you can use one of the Bundler Plugins to recompile automatically.
Using Messages in Code
After running the compiler, you can import messages with import * as m from "./paraglide/messages"
.
import * as m from "./paraglide/messages.js"
import { setLanguageTag } from "./paraglide/runtime.js"
m.hello()
m.loginHeader({ name: "Samuel" })
To choose between messages at runtime create a map of messages and index into it.
import * as m from "./paraglide/messages.js"
const season = {
spring: m.spring,
summer: m.summer,
autumn: m.autumn,
winter: m.winter,
} as const
const msg = season["spring"]()
Setting the language
You can set the language tag by calling setLanguageTag()
. Any subsequent calls to either languageTag()
or a message function will use the new language tag.
import { setLanguageTag } from "./paraglide/runtime.js"
import * as m from "./paraglide/messages.js"
setLanguageTag("de")
m.hello()
setLanguageTag("en")
m.hello()
The language tag is global, so you need to be careful with it on the server to make sure multiple requests don't interfere with each other.
You will need to call setLanguageTag
on both the server and the client since they run in separate processes.
Reacting to language changes
Messages aren't reactive, so you will need to trigger a re-render when the language changes. You can register a callback using onSetLanguageTag()
. It is called whenever the language tag changes.
If you are using a framework-specific library this is done for you.
import { setLanguageTag, onSetLanguageTag } from "./paraglide/runtime.js"
import * as m from "./paraglide/messages.js"
onSetLanguageTag((newLanguageTag) => {
console.log(`The language changed to ${newLanguageTag}`)
})
setLanguageTag("de")
setLanguageTag("en")
A few things to know about onSetLanguageTag()
:
- You can only register one listener. If you register a second listener it will throw an error.
onSetLanguageTag
shouldn't be used on the server.
Getting a message in a specific language
You can import a message in a specific language from paraglide/messages/{lang}.js
.
import * as m from "./paraglide/messages/de.js"
m.hello()
If you want to force a language, but don't know which language ahead of time you can pass the languageTag
option as the second parameter to a message function. This is often handy on the server.
import * as m from "./paraglide/messages.js"
const msg = m.hello({ name: "Samuel" }, { languageTag: "de" })
Lazy-Loading
Paraglide consciously discourages lazy-loading translations since it seriously hurts
your Web Vitals. Learn more about why lazy-loading is bad & what to do instead in our blog post on lazy-loading.
If you want to do it anyway, lazily import the language-specific message files. Be careful with this.
const lazyGerman = await import("./paraglide/messages/de.js")
lazyGerman.hello()
Usage with a Bundler
If you are using a bundler you should use the corresponding plugin. The plugin will keep your Message Functions up-to-date by compiling whenever your messages change and before building your app.
Configuration
Most of the configuration is done in ./project.inlang/settings.json
, except for paraglide's output directory, which needs to be passed in when calling the compiler.
Languages
You can declare which languages you support in the languageTags
array.
{
"languageTags": ["en", "de"]
}
Create the corresponding messages/{lang}.json
files and get translating!
Moving the Translation Files
If you want your language files to be in a different location you can change the pathPattern
of the Inlang-Message-Format plugin.
// project.inlang/settings.json
"plugin.inlang.messageFormat": {
- "pathPattern": "./messages/{languageTag}.json"
+ "pathPattern": "./i18n/{languageTag}.json"
},
Lint Rules
If you're using the Sherlock VS Code extension you might see warnings about certain messages. Perhaps they're duplicates, perhaps they're missing in one language.
You can configure which lint-rules are active in ./project.inlang/settings.json
. Simply add or remove them from the modules
array.
Playground
Find examples of how to use Paraglide on CodeSandbox or in our GitHub repository.
Architecture
ParaglideJS leverages a compiler to generate vanilla JavaScript functions from your messages. We call these "message functions".
Message Functions are fully typed using JSDoc. They are exported individually from the messages.js
file making them tree-shakable. They aren't reactive, they just return a string.
This avoids many edge cases associated with reactivity, lazy-loading, and namespacing that other i18n libraries have to work around.
In addition to the message functions, ParaglideJS also emits a runtime. The runtime is used to set the language tag. It contains less than 50 LOC (lines of code) and is less than 300 bytes minified & gzipped.

Paraglide consists of four main parts:
Part | Description |
---|
Compiler | Compiles messages into tree-shakable message functions |
Messages | The compiled tree-shakable message functions |
Runtime | A runtime that resolves the language tag of the current user |
Adapter | (optional) An adapter that adjusts the runtime for different frameworks |
Compiler
The compiler loads an Inlang project and compiles the messages into tree-shakable and typesafe message functions.
Input
{
"hello": "Hello {name}!"
}
Output
export const hello = (params) => `Hello ${params.name}!`
Messages
By convention, we import the compiled functions with a wildcard import.
import * as m from "../paraglide/messages.js"
Bundlers like Rollup, Webpack, or Turbopack tree-shake the messages that are not used, so using a wildcard import is perfectly fine.
Writing an Adapter
An "Adapter" is a library that integrates with a framework's lifecycle and does two things:
- Calls
setLanguageTag()
at appropriate times to set the language - Reacts to
onSetLanguageTag()
, usually by navigating or relading the page.
This example adapts Paraglide to a fictitious full-stack framework.
import {
setLanguageTag,
onSetLanguageTag,
type AvailableLanguageTag,
} from "../paraglide/runtime.js"
import { isServer, isClient, request, render } from "@example/framework"
import { detectLanguage } from "./utils.js"
if (isServer) {
const detectLanguage = (request: Request): AvailableLanguageTag => {
}
setLanguageTag(() => detectLanguage(request))
}
if (isClient) {
setLanguageTag(() => document.documentElement.lang)
onSetLanguageTag((newLanguageTag) => {
window.location.pathname = `/${newLanguageTag}${window.location.pathname}`
})
}
render((page) => (
<html lang={request.languageTag}>
<body>{page}</body>
</html>
))
Roadmap
Of course, we're not done yet! We plan on adding the following features to Paraglide JS soon:
Talks
Tooling
Paraglide JS is part of the Inlang ecosystem and integrates nicely with all the other Inlang-compatible tools.
As a developer, you will love the Sherlock VS Code extension.
If you are working with translators or designers you will find these tools useful:
- Fink - An Online UI for editing translations. Changes made in Fink are committed to a translation branch or submitted via pull request.
- Parrot - A Figma Plugin for previewing translations right in your Figma designs. This avoids any layout issues that might occur due to different text lengths in different languages.
Pricing