Paraglide compiles your source translations into JavaScript message functions. With the Vite plugin, compilation runs during development and production builds. Generated output is Git-ignored by default and does not need to be committed. Translation files and project configuration remain the source of truth.
From translation edit to deployment
- Edit your source catalog, such as
messages/en.json. - The Vite plugin regenerates message functions while developing, or when you build.
- Type-check your app against the generated functions.
- The production bundler removes unreferenced message functions.
- Deploy the app's production output.
For a complete plugin configuration and a language picker, follow the React + Vite guide. Use your framework's guide for SSR and localized routes.
Should I commit generated files?
Normally, no. The compiler's emitGitIgnore option defaults to true and writes a .gitignore into the generated output directory. That file ignores everything in the directory:
# ignore everything because the directory is auto-generated by inlang paraglide-js
# for more info visit https://paraglidejs.com
*
For the React guide's outdir: "./src/paraglide", confirm the rule in your app after running compilation:
git check-ignore -v src/paraglide/messages.js
Commit source translations, project.inlang/settings.json, bundler configuration, and your lockfile. You do not need a separate manual regeneration-and-commit step before each push when using the Vite plugin. Generated-file merge conflicts are therefore not an inherent part of the default workflow. Source translation files can still conflict like other version-controlled files.
If you set emitGitIgnore: false, you choose how to manage generated output. Also, .gitignore does not untrack files already committed to Git. If migrating an existing project to ignored output, review which generated paths should be removed from the index while retaining source files.
Compilation and type checking in CI
A fresh checkout may not contain generated imports. Compile before running a TypeScript check that resolves them. For a Vite application whose scripts are build and typecheck, the sequence is:
npm ci
npx paraglide-js compile --project ./project.inlang --outdir ./src/paraglide --emit-ts-declarations
npm run typecheck
npm run build
This assumes the compiler is installed in your app and that project and outdir match your configuration. The CLI command emits declarations for a TypeScript app; use your app's TypeScript checking command, such as tsc -b or tsc --noEmit. npm ci uses your committed npm lockfile; use the equivalent frozen installation for another package manager.
If your compilation relies on options configured only in the Vite plugin, run the Vite build before type checking instead, or pass the same options to the standalone compiler. See compiler configuration for the available options. The plugin continues to compile messages as part of normal builds.
Does tree-shaking remove unused messages?
Yes, when the production bundler can determine which message functions your app uses. Import and call statically known messages:
import { m } from "./paraglide/messages.js";
m.greeting({ name: "World" });
Avoid unrestricted runtime lookup such as m[key]() or copying the entire message object, because it can make all messages reachable. Use an explicit map for a finite set of choices; see dynamic messages.
The repository includes a small reproduction script. It builds a used message, adds a unique unused message in two locales, recompiles, and verifies that:
- The unused message exists in generated JavaScript.
- Its text is absent from the production JavaScript.
- Every production JavaScript file has the same hash before and after.
- The compiler emits its default
.gitignore.
Run it from a checkout with the repository's frozen dependencies installed:
pnpm --filter @inlang/paraglide-js-benchmark check:tree-shaking
The probe uses the benchmark's pinned Paraglide and Vite versions. It verifies unused-message removal in a minimal Vite production build, not every framework, import pattern, or compiler setting. Output and its JSON summary stay in the benchmark's ignored .work/tree-shaking-probe directory.
Does this load only the current language?
Unused-message removal and active-language delivery are different optimizations. In the default output, retained messages include translations for all configured locales. That can be useful for subsequent switches but costs more initial bytes than loading only the active language in some apps.
The benchmark shows both the savings from eliminating unused messages and the scenarios where HTTP i18next transfers less data. It also compares experimental per-locale builds with message modules. Per-locale builds are the recommended approach for shipping only the active language; check their deployment and framework constraints before adopting that configuration. There is no universal locale-count threshold at which one loading strategy wins.
Can translations change without a deployment?
Compiled translation changes require a new app build and deployment. This includes changing the text of an existing compiled message or introducing a new message function. Switching among languages already included in a build works at runtime.
If translators must publish arbitrary new keys independently of app releases, evaluate a runtime catalog loader. You can also use Paraglide for application UI and let a CMS return already-localized article or product content. See choosing a React i18n library for the trade-offs.
