Internationalization
@basaltkit/i18n resolves the locale from the request context (per user, then per tenant), translates typed message catalogs with interpolation and CLDR plurals, and formats numbers, currency, dates, relative time and lists through the native Intl APIs. Zero dependencies.
Setup
import { i18nPlugin, I18N, defineMessages } from '@basaltkit/i18n'
const en = defineMessages({
greeting: 'Hi {name}',
notes: { one: '{count} note', other: '{count} notes' },
})
const pt = defineMessages({
greeting: 'Olá {name}',
notes: { one: '{count} nota', other: '{count} notas' },
})
i18nPlugin({ locales: { en, pt }, defaultLocale: 'en' })Translate
const i18n = app.container.get(I18N)
i18n.in('pt').t('greeting', { name: 'Ada' }) // 'Olá Ada'
i18n.in('en').t('notes', { count: 3 }) // '3 notes' (plural via Intl.PluralRules)Inside a request, i18n.t(...) uses the context locale automatically — ctx().user.locale, then ctx().tenant.locale (store a locale field on those records, or pass a custom resolveLocale). A regional locale negotiates down to an available catalog (pt-BR → pt), then falls back to defaultLocale, then to the key itself.
Format
Each translator carries Intl-based formatters in the same locale:
const l = i18n.in('en')
l.n(1234.5) // '1,234.5'
l.currency(9.9, 'USD') // '$9.90'
l.date(new Date(), { dateStyle: 'long' })
l.relativeTime(-1, 'day') // '1 day ago'
l.list(['a', 'b', 'c']) // 'a, b, and c'Pair it with @basaltkit/mailer / @basaltkit/notifications to render outbound content in the recipient's locale.