Skip to content

Lingua 3.0 Migration

Lingua 3.0 simplifies the constructor surface to two entry points and adds explicit failure defaults.

Summary of changes

BeforeAfter
createCatalogTranslator(catalog, { locale })createTranslator(catalog, { locale })
createTranslator(catalogs, { locale, fallback })Removed — use createI18n for multi-locale
createTranslationStore({ catalogs, locale })createI18n({ catalogs, locale, loadCatalog? })
hydrateTranslationStore(state)createI18n({ state })
TranslationStore / TranslationSnapshotI18n / I18nSnapshot
segments() / segmentsDynamic()parts() / partsDynamic()
onMissingKey / onMissingValue optionsmissing: 'throw' | 'key' | handler
@vielzeug/lingua/format (createFormatter)Retained as an optional standalone Intl facade
TranslationState version 3version 4
LinguaMissingResourceError (2.x)LinguaMissingCatalogError (unchanged from 2.x)
LinguaMissingKeyError, LinguaMissingValueError (new)

Replace constructor calls

ts
// Before — fixed catalog
const translator = createCatalogTranslator(catalog, { locale: 'fr' });

// After
const translator = createTranslator(catalog, { locale: 'fr' });
ts
// Before — locale-keyed catalogs
const translator = createTranslator({ en: enCatalog, fr: frCatalog }, { locale: 'fr', fallback: 'en' });

// After — eager catalogs preserve synchronous translation and fallback
const i18n = createI18n({
  catalogs: { en: enCatalog, fr: frCatalog },
  locale: 'fr',
  fallback: 'en',
});
i18n.translate('key');
ts
// Before — mutable store
const i18n = createTranslationStore({ catalogs: { en: enCatalog, fr: async () => frCatalog }, locale: 'en' });

// After
const i18n = createI18n({
  catalogs: { en: enCatalog },
  locale: 'en',
  loadCatalog: (locale) => import(`./locales/${locale}`).then((m) => m.default),
});
ts
// Before — hydration
const client = hydrateTranslationStore(server.serialize());

// After
const client = createI18n({ state: server.serialize() });

Replace segments with parts

segments() returned mixed primitive arrays. parts() returns a typed discriminated union.

ts
// Before
const segs = translator.segments('error', { values: { retry } });
// ['Try ', { href: '/retry' }, '.']

// After
const parts = translator.parts('error', { values: { retry } });
// [
//   { type: 'text', value: 'Try ' },
//   { type: 'value', value: { href: '/retry' } },
//   { type: 'text', value: '.' },
// ]

Replace missing handlers

ts
// Before
createTranslator(catalog, { onMissingKey: (key) => `[${key}]`, onMissingValue: (name) => `<${name}>` });

// After
createTranslator(catalog, { missing: ({ key, name }) => (name ? `<${name}>` : `[${key}]`) });

Keep or replace the format subpath

@vielzeug/lingua/format remains available when a shared, cached Intl facade improves application code. Direct Intl usage remains appropriate for isolated formatting.

ts
// Before
import { createFormatter } from '@vielzeug/lingua/format';
const fmt = createFormatter(() => i18n.locale);
fmt.currency(9.99, 'USD');

// After
new Intl.NumberFormat(i18n.locale, { style: 'currency', currency: 'USD' }).format(9.99);

Bump state version

TranslationState version changed from 3 to 4. Re-serialize server state before hydrating clients with the new API.

Review the Usage Guide and API Reference for the full catalog, translator, and i18n contracts.