zintljs

Compile-time internationalization for Vite — no keys, no wrappers, and only the translations each screen actually needs.
2
Install
npm install zintljs
Latest Version:0.1.0-alpha.20
License:MIT
Last Updated:Sep 1, 2026
Links: GitHub  ·  npm
Maintainer: khalidfsh

Zintl logo

Zintl(𝐢𝟖𝐧)

Write your app in plain language. Ship it in every language.

npm version CI status node compatibility documentation GitHub repository


Zintl is a compile-time internationalization engine.

Most i18n libraries ask you to change how you write code — wrap every string in t(), invent a key for it, keep a dictionary in sync by hand. Zintl doesn't. You write normal strings; the compiler finds them, works out which ones each part of your app actually needs, and ships exactly those.

Install

npm install -D zintljs

or pnpm add -D zintljs · yarn add -D zintljs

Use

1. Add the plugin.

// vite.config.ts
import { defineConfig } from "vite";
import zintl from "zintljs/vite";

export default defineConfig({
  plugins: [zintl({ locales: ["en", "ar", "fr"] })],
});
Using Rsbuild instead?
// rsbuild.config.ts
import { defineConfig } from "@rsbuild/core";
import zintl from "zintljs/rsbuild";

export default defineConfig({
  plugins: [...zintl({ locales: ["en", "ar", "fr"] })],
});

Same plugin, same options — note the spread, since the Rsbuild entry point returns an array. Everything below applies unchanged. What's covered there.

2. Set a locale.

// src/main.ts
import { zintl } from "zintljs/macro";

const locale = new URLSearchParams(location.search).get("lang") ?? "en";
await zintl(locale);

document.querySelector("#app").innerHTML = `<h1>Welcome back!</h1>`;

That's the whole API. No keys, no wrappers, no dictionary to maintain.

What you pass matters. A variable ships every locale and lets users switch at runtime. A literal — zintl("fr") — tells the compiler this page is French: it bakes that locale in, emits no catalog chunk, and leaves every other locale out of the bundle, source language included. Use a literal for per-locale static builds, a variable whenever language is a choice.

zintljs/vite (or zintljs/rsbuild) is the plugin; zintljs/macro is what you call in app code. Importing the wrong one into your config gives you an async no-op rather than a plugin.

3. Run your dev server. Zintl extracts your strings and writes one file per locale, ready to fill in:

// zintl/src/main.fr.json
{
  "Welcome back!": ""
}

Fill it in and the page updates without a reload. Leave it empty and the production build refuses to ship — a blank string is a bug, not a fallback.

What you get

Only what each screen needs. Zintl works out which strings are reachable from which entry point, and splits catalogs along the same lines your bundler splits code. Opening the settings page downloads the settings translations, not all of them.

Nothing extra in the bundle. Plurals and grammar rules compile to plain JavaScript at build time — no ICU parser reaches the browser. Your source locale is never written to disk; the compiler already has those strings.

Translations that survive refactors. Identity is content-based, not path-based — so translations aren't attached to a file, a line, or a key you have to keep stable. Rename components, move files, restructure whole directories: the translations follow. Even a source string edited into a near-identical one keeps what it had, instead of being orphaned and sent back to translators.

Restructuring an app usually means a day of reconciling catalogs afterwards. Here it means zero.

Grammar where it belongs. Source files keep simple template literals; plural and gender rules live in the catalog, with a generated JSON schema so translators get autocomplete.

const msg = `You have ${count} items in your cart`;
{
  "You have {count} items in your cart": "{count, plural, =0 {سلتك فارغة} one {لديك عنصر واحد} other {لديك {count} عناصر}}"
}

Where it runs

Works with React, Vue, Svelte, and vanilla apps, on Vite 6, 7 or 8 or on Rsbuild 2 — install whichever you build with; both are optional peer dependencies. Node ^22.18.0 || >=24.11.0.

On Vite that covers client-rendered, server-rendered and multi-page apps, with all four frameworks. On Rsbuild it covers single-page and multi-page apps, also with all four, in production builds and in dev. Two exclusions there are worth knowing before you start: per-locale HTML fan-out (multiplex) and SSR are Vite-only, and combining them with Rsbuild fails your build with a clear error rather than doing nothing quietly. Vue components work with either <script setup> or the Options API, on both hosts. The full comparison.

That list is a starting point, not the design. The extractor carries no framework knowledge and the compiler is bundler-agnostic, so another framework or another build tool is something you add rather than something the core is rewritten around. More of both are coming.

Docs

Status

Alpha. The ideas are settled and the test suite is thorough, but the API can still move between releases. Pin your version, and please open an issue when something surprises you — early reports shape this more than anything else right now.

License

MIT © Khalid F. Shuhail

Related Packages

@mylukin/easy-i18n-js

Framework-agnostic i18n extraction tool with plugin support for Svelte, Vue, and...

5,836 0
@ai-i18n/vite

AI-powered i18n plugin for Vite with source-text extraction and browser runtime...

5,897 0
@inlang/cli

title: inlang CLI - Localization Automation for CI/CD description: Automate tran...

2,350,321 2,006
@the-i18n-kit/cli

Find missing translations, remove dead keys, rename across all locales at once —...

28,459 11
@ai-i18n/analyzer

Static i18n message analysis for ai-i18n tooling in Vite, Vue, and React project...

5,336 0

Version History

Pre-releases (14)
Version Released License
0.1.0-alpha.20 MIT
0.1.0-alpha.19 MIT
0.1.0-alpha.18 MIT
0.1.0-alpha.17 MIT
0.1.0-alpha.16 MIT