Skip to main content

i18n-keyless vs Lingui: which to pick in 2026

· 8 min read
Founder of i18n-keyless

Lingui is the "modern i18next" — a lightweight, TypeScript-first i18n library with compile-time message extraction and full ICU MessageFormat support. Where i18next grew organically over a decade of plugins, Lingui was designed from scratch with a cleaner API: macros that look like regular function calls, a compiler that extracts messages at build time, and catalogs that stay in sync automatically.

i18n-keyless skips the catalog entirely. There are no message IDs, no extraction step, no .po or .json files to maintain. The source string is the key, translations are AI-generated, and the whole pipeline disappears.

This is an honest comparison. Lingui is a genuinely good library and we'll tell you when it's the better choice.

TL;DR

i18n-keylessLingui
Setup time~5 minutes~30 minutes (plus extraction pipeline)
Translation keysNone — source string is the keyAuto-extracted message IDs
Locale filesNone.po or .json catalogs, one per locale
AI translationBuilt inNone — integrate a TMS or MT service
ICU MessageFormatLimitedFull support (plurals, select, nested)
Extraction stepNoneRequired — lingui extract in CI
Bundle sizeSmall runtimeVery small (messages compile away)
Best forMVPs, multi-framework, engineer-drivenApps needing ICU, compile-time checks, small bundles

Pick i18n-keyless if you want multilingual in an afternoon and don't need ICU plural rules. Pick Lingui if you need full ICU MessageFormat, compile-time guarantees, and the smallest possible runtime bundle.

The code difference

Lingui setup

npm install @lingui/core @lingui/react @lingui/cli @lingui/macro @lingui/vite-plugin
// lingui.config.ts
export default {
locales: ["en", "fr", "de", "es"],
catalogs: [{ path: "src/locales/{locale}", include: ["src/"] }],
sourceLocale: "en",
};
// In a component
import { Trans, t } from "@lingui/macro";

function Checkout() {
return (
<div>
<h1><Trans>Your cart</Trans></h1>
<button><Trans>Confirm payment</Trans></button>
<input placeholder={t`Search products`} />
</div>
);
}

Then run lingui extract to scan the code and generate catalog files:

npx lingui extract
# Creates src/locales/en.po, src/locales/fr.po, etc.

Each .po file needs translations filled in — by a translator, a TMS, or a machine translation pass. Then compile:

npx lingui compile
# Generates optimized runtime catalogs

i18n-keyless setup

npm install i18n-keyless-react
import { I18nKeylessText, useTranslation } from "i18n-keyless-react";

function Checkout() {
const placeholder = useTranslation("Search products");
return (
<div>
<h1><I18nKeylessText>Your cart</I18nKeylessText></h1>
<button><I18nKeylessText>Confirm payment</I18nKeylessText></button>
<input placeholder={placeholder} />
</div>
);
}

No extraction. No catalogs. No compile step. Translations are AI-generated the first time a string is encountered, cached, and overridable from the dashboard.

Where Lingui genuinely wins

Full ICU MessageFormat

Lingui handles every ICU construct — plurals with CLDR rules, select, selectordinal, nested arguments:

<Plural value={count} zero="No items" one="1 item" other="# items" />
<Trans>
{gender, select, male {He} female {She} other {They}} liked your post
</Trans>

Polish (4 plural forms), Arabic (6 plural forms), Russian — Lingui gets them right because it follows the CLDR specification. i18n-keyless uses string interpolation and relies on the AI to produce the correct plural form per language. For pluralization-heavy apps, Lingui is more precise.

Compile-time extraction

lingui extract scans your codebase and finds every translatable string automatically. You know at build time:

  • Which strings exist.
  • Which locales are missing translations.
  • Which strings were added or removed since the last extraction.

This is a real safety net. With i18n-keyless, a string exists when it's first rendered — there's no upfront inventory.

Tiny runtime bundle

Lingui compiles message catalogs into optimized JavaScript. The runtime is minimal — messages are resolved from a precompiled lookup, not fetched from a server. For apps where every kilobyte matters (mobile web, poor connections), Lingui's approach produces smaller bundles.

i18n-keyless includes a small runtime SDK and makes an API call to fetch translations (cached locally after the first load). It's not heavy, but it's not zero.

Macro-based DX

Lingui's macros (t, <Trans>, <Plural>) look like regular code. The compiler transforms them at build time, so there's no runtime overhead for the extraction. It's elegant — you write natural-looking JSX and the tooling handles the rest.

Active, growing community

Lingui has a dedicated community, good documentation, and active maintenance. It's increasingly recommended as the "modern alternative to react-intl" — and rightfully so.

Where i18n-keyless wins

No extraction pipeline

Lingui requires an extract → translate → compile pipeline. That's three CLI commands, a CI step, and a process for getting translations into the catalog files. Skip a step and you ship untranslated strings.

i18n-keyless has no pipeline. Write a string, it translates itself.

No catalog files to manage

Lingui keeps .po or .json catalogs in your repo — one per locale. These need to be committed, reviewed, merged, and kept in sync. A new locale means a new file to maintain.

i18n-keyless stores translations in its backend. No files in your repo, no merge conflicts, no "which branch has the latest French translations?" questions.

AI translation is built in

Adding a new language to a Lingui project means: run lingui extract, get empty catalog entries for the new locale, fill them with translations (human or machine), run lingui compile, deploy. Adding a new language to i18n-keyless means: add the language code to the supported array.

Works beyond React

Lingui is primarily a React library (with growing Svelte and Vue support). i18n-keyless has SDKs for React, React Native, Vue, Angular, Svelte, Node.js, Laravel, Rails, Flutter, Go, Python, Swift, and Kotlin — same dashboard, same API key, same translations across your entire stack.

No bundler plugin required

Lingui needs a Vite/webpack/Babel plugin for its macros to work. i18n-keyless is a regular npm package — install it, import it, call init(). No build config to touch.

When to choose Lingui

✅ You need full ICU MessageFormat with CLDR plural rules. ✅ Compile-time extraction and type safety are important to your team. ✅ Bundle size is a critical constraint (mobile web, offline-first). ✅ You want an open-source, self-contained solution with no external API dependency. ✅ You have translators filling catalog files (or a TMS connected to Lingui).

When to choose i18n-keyless

✅ You want multilingual in a day, not after setting up an extraction pipeline. ✅ You don't have (or don't want) translators in the loop. ✅ Your product spans multiple frameworks and languages. ✅ You want to add locales frequently without touching catalogs each time. ✅ Simplicity matters more than compile-time guarantees on message IDs.

FAQ

Does Lingui support AI translation?

Not built in. You can pipe extracted catalogs through a machine translation API (Google Translate, DeepL, or an LLM), but you'd build and maintain that pipeline yourself. Some TMS platforms (Crowdin, Phrase) have Lingui integrations that include MT.

Is i18n-keyless as type-safe as Lingui?

No. Lingui's macros produce typed message descriptors — a missing or mistyped key is a compile error. i18n-keyless uses source strings as keys, so a typo creates a new translation rather than a build failure. The trade-off: less compile-time safety, more code readability.

What about ICU MessageFormat?

i18n-keyless supports simple value interpolation (replace) but doesn't model CLDR plural rules natively. For most SaaS UI strings, the AI produces correct plurals per language. For apps where locale-specific plural forms (Arabic, Polish, Russian) are critical and must be deterministic, Lingui's ICU support is more reliable.

Can I use both together?

Yes — keep Lingui for existing screens, use i18n-keyless for new features. The libraries don't conflict. Over time, you can replace <Trans> wrappers with <I18nKeylessText> and drop the extraction pipeline.

Next steps