Paraglide JS

There are three ways to invoke the Paraglide JS compiler:

  1. Via the Paraglide CLI
  2. Via a bundler plugin
  3. Programmatically

[!TIP]Bundler plugins are recommended because they automatically recompile when translation files change, integrate with your existing build process, and require no separate watch command. CLI compilation is better suited for CI/CD pipelines or projects without a bundler.

For all available options, see the Compiler Options Reference.

Via the Paraglide CLI

[!TIP]For a complete setup guide using CLI compilation with Express, Hono, Fastify, or Elysia, see Standalone Servers. For monorepo setups, see Monorepo Setup.

The recommended commands below emit .d.ts files for reliable editor updates and require TypeScript 5.6 or newer. Omit --emit-ts-declarations to use JavaScript/JSDoc inference instead.

To compile your messages via the CLI, run the following command:

npx @inlang/paraglide-js compile --project ./project.inlang --outdir ./src/paraglide --emit-ts-declarations

To watch files and recompile on change, add the --watch flag:

npx @inlang/paraglide-js compile --project ./project.inlang --outdir ./src/paraglide --emit-ts-declarations --watch

Use --help to see all available options:

npx @inlang/paraglide-js compile --help

Via a bundler plugin

All bundler plugins are exported from @inlang/paraglide-js:

import {
	paraglideVitePlugin,
	paraglideWebpackPlugin,
	paraglideRollupPlugin,
	paraglideRspackPlugin,
	paraglideRolldownPlugin,
	paraglideEsbuildPlugin,
	// ... and more plugins supported by unplugin
} from "@inlang/paraglide-js";

See unplugin for the full list of supported bundlers.

Vite

[!TIP]Vite is the ideal bundler for Paraglide. Vite's built-in tree-shaking (via Rollup) automatically eliminates unused messages, and HMR gives you instant feedback when editing translations. Setup is just one plugin—no extra configuration needed.

import { defineConfig } from "vite";
import { paraglideVitePlugin } from "@inlang/paraglide-js";

export default defineConfig({
	plugins: [
		paraglideVitePlugin({
			project: "./project.inlang",
			outdir: "./src/paraglide",
			emitTsDeclarations: true,
		}),
	],
});

Experimental per-locale builds

On Vite 8.x, TanStack Start and SvelteKit can emit a separate client asset graph for every project locale. Paraglide detects the framework, reads the locale list automatically, and specializes the compiled message functions at build time, so each graph contains only one locale's translations.

paraglideVitePlugin({
	project: "./project.inlang",
	outdir: "./src/paraglide",
	experimentalPerLocaleBuild: true,
});

TanStack Start must use the generated server entry:

// src/server.ts
export { default } from "./paraglide/tanstack-start.server.js";

SvelteKit keeps using its normal paraglideMiddleware server hook. Paraglide integrates with SvelteKit's renderer automatically, including SSR, prerendered HTML, preload headers, and client bootstrap imports.

The base locale keeps the canonical asset URLs. TanStack Start locale trees are emitted under /__paraglide/<locale-id>/; SvelteKit locale trees stay inside its immutable cache directory at /_app/immutable/__paraglide/<locale-id>/. The emitted paraglide-per-locale.json indexes the locale prefixes and asset mappings.

Locale switching must use full navigation (the default behavior of setLocale()), because a hydrated page cannot mix two specialized graphs. In SvelteKit, add data-sveltekit-reload to links which can change locale. If a client message call supplies a different options.locale, Paraglide warns and uses the locale already selected for that client graph.

The experiment intentionally supports a narrow, fail-fast configuration:

  • Vite 8.x and a tested framework version: TanStack Start 1.168.x or SvelteKit 2.69.x.
  • A global locale strategy whose first entry is url or cookie, without routeStrategies.
  • Root/default asset hosting. Do not configure Vite base, source maps, build.minify: false, or experimental.renderBuiltUrl.
  • TanStack Start must use the generated server entry shown above.
  • SvelteKit must use the default _app, empty kit.paths.base and kit.paths.assets, bundleStrategy: "split", and client router resolution. Service workers and SPA fallback pages are not supported; use SSR or prerender every localized page.

Paraglide rejects unsupported combinations during configuration or build. It performs the required final Oxc minification itself and does not emit client source maps.

Webpack

// webpack.config.js
const { paraglideWebpackPlugin } = require("@inlang/paraglide-js");

module.exports = {
	plugins: [
		paraglideWebpackPlugin({
			project: "./project.inlang",
			outdir: "./src/paraglide",
			emitTsDeclarations: true,
		}),
	],
};

Rollup

// rollup.config.js
import { paraglideRollupPlugin } from "@inlang/paraglide-js";

export default {
	plugins: [
		paraglideRollupPlugin({
			project: "./project.inlang",
			outdir: "./src/paraglide",
			emitTsDeclarations: true,
		}),
	],
};

TypeScript Configuration

Paraglide compiles to JavaScript with JSDoc type annotations. The recommended configurations above also emit .d.ts files so editors reliably refresh message keys when generated files change. Declaration emission requires TypeScript 5.6 or newer.

If you disable declaration emission for faster compilation, enable TypeScript support for the generated JSDoc types with allowJs in your tsconfig.json:

{
	"compilerOptions": {
		"allowJs": true
	}
}

Emitting .d.ts declarations

Emit TypeScript declaration files when your project doesn't support allowJs (e.g., publishing a library), or when your editor reports newly added message functions as missing until its language server restarts:

npx @inlang/paraglide-js compile --project ./project.inlang --outdir ./src/paraglide --emit-ts-declarations

Or via bundler plugin / programmatic API:

paraglideVitePlugin({
	project: "./project.inlang",
	outdir: "./src/paraglide",
	emitTsDeclarations: true,
});

[!NOTE]Emitting declarations requires TypeScript 5.6 or newer and is slower than JSDoc-based types. The framework setup examples enable it for reliable language-server updates. Set emitTsDeclarations: false (or use --no-emit-ts-declarations) and allowJs: true when faster compilation matters more in your project.

[!NOTE]With TypeScript 5 and 6, declarations are generated with the in-process compiler API. TypeScript 7+ no longer provides that API, so Paraglide invokes its tsc CLI in a child process instead. The output is semantically equivalent, but differs cosmetically between the two (quote style, declaration ordering, export declare const vs export const) — expect .d.ts churn when switching TypeScript majors.

Generated Output

The compiler generates the following file structure in your outdir:

paraglide/
  messages/
    hello_world/        # One folder per message (default structure)
      index.js
      en.js
      de.js
  messages.js           # Re-exports all message functions
  runtime.js            # Locale management (getLocale, setLocale, etc.)
  server.js             # Server middleware (paraglideMiddleware)
  .gitignore            # Ignores generated files
  README.md             # Documentation for LLMs

Key files:

FilePurpose
messages.jsImport message functions: import * as m from "./paraglide/messages.js"
runtime.jsLocale utilities: getLocale(), setLocale(), locales, baseLocale
server.jsServer middleware: paraglideMiddleware()

The outputStructure option controls how messages are organized. See Compiler Options for details.

Programmatically

The Paraglide compiler can be invoked programmatically via the compile function.

import { compile } from "@inlang/paraglide-js";

await compile({
	project: "./project.inlang",
	outdir: "./src/paraglide",
	emitTsDeclarations: true,
});

Lower-level API

Use compileProject when you need control over the output, such as:

  • Writing files to a custom directory structure
  • Post-processing the generated code
  • Integrating with a custom build system

This requires the @inlang/sdk package:

npm install @inlang/sdk
import { compileProject } from "@inlang/paraglide-js";
import { loadProjectFromDirectory } from "@inlang/sdk";
import { writeFile, mkdir } from "node:fs/promises";
import { join } from "node:path";

const inlangProject = await loadProjectFromDirectory({
	path: "./project.inlang",
});

const output = await compileProject({
	project: inlangProject,
});

// Write files to a custom location
const outdir = "./custom/paraglide";
await mkdir(outdir, { recursive: true });

for (const [filename, content] of Object.entries(output)) {
	await writeFile(join(outdir, filename), content);
}