Skip to content
lingua logoLinguai18n
Framework-neutral locale catalogs, typed translations, and explicit plural messages.
Version
v3.0.0
Size
2.6 KB gzip
Dependencies
Zero dependencies
BrowserNode ≥22SSRDeno
createI18ncreateTranslatorLinguaErrorLinguaDisposedErrorLinguaInvalidCatalogError View all 11 exports

Why Lingua?

Lingua separates immutable translation from mutable locale state. Use createTranslator for one fixed-locale catalog, or createI18n when locale changes at runtime.

ts
// Before
const message = catalogs[locale]?.inbox?.[count === 1 ? 'one' : 'other'] ?? 'inbox';

// After
const output = i18n.translate('inbox', { count });
FeatureLinguai18nextFormatJS
Bundle size2.6 KBVaries by selected modulesVaries by selected modules
Zero runtime dependencies
Explicit plural catalog nodesConvention/config dependentICU-message dependent
Static and lazy locale catalogsPlugin/config dependentApplication-defined
Immutable locale snapshotsApplication-definedApplication-defined

Use Lingua when you need a compact TypeScript runtime with explicit catalog structure, deterministic fallback, and framework-neutral subscriptions.

Consider i18next or FormatJS when you need their plugin ecosystems, message extraction pipelines, or framework-specific integrations.

Installation

sh
pnpm add @vielzeug/lingua
sh
npm install @vielzeug/lingua
sh
yarn add @vielzeug/lingua

Quick Start

Create an i18n instance with an eager default catalog and a loader for other locales.

ts
import { createI18n } from '@vielzeug/lingua';

const en = { inbox: { plural: { one: 'One message', other: '{count} messages' } } };
const i18n = createI18n({
  catalogs: { en },
  locale: 'en',
  loadCatalog: (locale) => import(`./locales/${locale}.ts`).then((m) => m.default),
});

try {
  console.log(i18n.translate('inbox', { count: 3 }));
  await i18n.setLocale('de');
  console.log(i18n.translate('inbox', { count: 1 }));
} finally {
  i18n.dispose();
}

Features

  • createTranslator() compiles one immutable fixed-locale catalog.
  • createI18n() manages eager catalogs, locale changes, and optional lazy loading.
  • translate() renders text and plural messages through explicit catalog nodes.
  • translateDynamic() makes runtime-key lookup explicit.
  • parts() returns typed discriminated parts for framework content.
  • load() deduplicates loading and resolves the active locale plus configured fallbacks.
  • getSnapshot() and subscribe() expose immutable translator revisions.
  • serialize() and state option transfer resolved SSR catalogs.
  • missing: 'throw' | 'key' | handler controls failure behaviour.
  • validateCatalog() remains an isolated subpath tool.

Documentation

See Also

  • Ripple adapts Lingua snapshots into reactive application state.
  • Courier can fetch locale catalogs before passing them to Lingua loaders.
  • Wayfinder can drive locale selection from route state.