Version v3.0.0 Size 2.6 KB gzip Dependencies Zero dependencies
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 });| Feature | Lingua | i18next | FormatJS |
|---|---|---|---|
| Bundle size | 2.6 KB | Varies by selected modules | Varies by selected modules |
| Zero runtime dependencies | |||
| Explicit plural catalog nodes | Convention/config dependent | ICU-message dependent | |
| Static and lazy locale catalogs | Plugin/config dependent | Application-defined | |
| Immutable locale snapshots | Application-defined | Application-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/linguash
npm install @vielzeug/linguash
yarn add @vielzeug/linguaQuick 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()andsubscribe()expose immutable translator revisions.serialize()andstateoption transfer resolved SSR catalogs.missing: 'throw' | 'key' | handlercontrols failure behaviour.validateCatalog()remains an isolated subpath tool.