API
This page describes the public building blocks exported by @nano_kit/intl: context helpers, message formats, translation utilities, and common composition patterns.
Context
Section titled “Context”intl($locale, loader)
Section titled “intl($locale, loader)”Creates an internationalization context with a bound messages method.
import { intl } from '@nano_kit/intl'
const { messages, $loading, $error } = intl( $locale, loader)The loader can return full translation data or load namespaces on demand.
messages(namespace, scheme?)
Section titled “messages(namespace, scheme?)”Creates reactive messages for a namespace.
const [$t, $pending, $error] = messages('home', { title: text(), attendees: plural('count')})Basic Formats
Section titled “Basic Formats”raw(fallback?)
Section titled “raw(fallback?)”Returns the input value as-is. Use it for objects, arrays, dictionaries, or values that do not need formatting.
const [$t] = messages('home', { categories: raw<Record<string, string>>()})
$t().categories?.conferencetext(fallback?)
Section titled “text(fallback?)”Formats string messages and optional string fallbacks.
const [$t] = messages('layout', { title: text('Untitled')})
$t().title// string | 'Untitled'uppercase(format), lowercase(format), capitalize(format)
Section titled “uppercase(format), lowercase(format), capitalize(format)”Transforms text formatter output with the active locale.
const [$t] = messages('home', { title: capitalize(text())})
$t().title// 'Hello'Parameterized Messages
Section titled “Parameterized Messages”params(params, preformat?)
Section titled “params(params, preformat?)”Replaces {name} placeholders with formatted values.
const [$t] = messages('home', { greeting: params({ name: text() })})
$t().greeting({ name: 'Ada'})params can preformat another formatter before replacing placeholders. That makes it possible to share parameters across nested formats.
const translations = { home: { mailbox: { one: '{name} has {count} message', other: '{name} has {count} messages' } }}
const [$t] = messages('home', { mailbox: params({ name: text() }, plural('count', forms({ one: text(), other: text() })))})
$t().mailbox({ name: 'Ada', count: 2})// 'Ada has 2 messages'Callable Formatter Messages
Section titled “Callable Formatter Messages”format(type)
Section titled “format(type)”Use format() when the message should format an application value instead of formatting a value from translation data. It binds another format to the current locale and returns a callable message.
const [$t] = messages('home', { formatPrice: format(number({ style: 'currency', currency: 'USD' })), eventDate: format(capitalize(datetime({ dateStyle: 'medium', timeZone: 'UTC' })))})
$t().formatPrice(12)// '$12.00'
$t().eventDate(new Date('2024-01-02T00:00:00.000Z'))// 'Jan 2, 2024'This is useful for dates, numbers, and other UI values that come from app state.
The formatter reads a signal or an accessor passed as the value, so the $t.eventDate($startsAt) shortcut is a computed message that follows $startsAt. A null value formats like a missing one, so data that is still loading needs no guard:
const $startsAt = signal<Date | null>(null)const $eventDate = $t.eventDate($startsAt)
$eventDate()// undefined
$startsAt(new Date('2024-01-02T00:00:00.000Z'))$eventDate()// 'Jan 2, 2024'Intl Formats
Section titled “Intl Formats”Intl formats can be used inside params(...) for translated templates, or wrapped with format(...) when the value comes from runtime state, API data, or a database.
A fallback is always passed together with options, for example number(0, {}) or datetime(fallback, false): a lone argument is always read as options.
number(fallback?, options?)
Section titled “number(fallback?, options?)”Formats numbers with Intl.NumberFormat.
const [$t] = messages('stats', { formatCount: format(number({ maximumFractionDigits: 1 }))})
$t().formatCount(1234.56)// '1,234.6'datetime(fallback?, options?)
Section titled “datetime(fallback?, options?)”Formats Date or timestamp values with Intl.DateTimeFormat.
const [$t] = messages('event', { formatDate: format(datetime({ dateStyle: 'medium', timeZone: 'UTC' }))})
$t().formatDate(new Date('2024-01-02T00:00:00.000Z'))// 'Jan 2, 2024'relativetime(fallback?, options?)
Section titled “relativetime(fallback?, options?)”Formats the distance from now to a date, a timestamp or a date string with Intl.RelativeTimeFormat, in the largest unit the distance fills: years, months, weeks, days, hours, minutes or seconds.
const [$t] = messages('event', { formatStartsIn: format(relativetime({ numeric: 'auto' }))})
$t().formatStartsIn(event.startsAt)// 'tomorrow' for an event that starts in 30 hourslargestUnit and smallestUnit bound the unit, and both set to one unit format in that unit only. With numeric: 'auto', a distance below the smallest unit reads as “now” when that unit is an hour or less, and as “today” or “this week” for calendar units, so a feed with smallestUnit: 'minute' shows “now” for anything newer than a minute:
const [$t] = messages('activity', { formatTime: format(relativetime({ numeric: 'auto', smallestUnit: 'minute' }))})
$t().formatTime(comment.createdAt)// 'now', then '5 minutes ago', 'yesterday', 'last week'relativeTo sets the moment to count from instead of the current time. It takes a signal or an accessor as well, so a message follows a ticking clock, for example one built on interval(). A month counts as 30 days and a year as 365: the unit comes from the distance, not from the calendar.
duration(fallback?, options?)
Section titled “duration(fallback?, options?)”Formats duration objects with Intl.DurationFormat.
const [$t] = messages('media', { formatRuntime: format(duration({ style: 'short' }))})
$t().formatRuntime({ hours: 1, minutes: 30})// '1 hr, 30 min'list(fallback?, options?)
Section titled “list(fallback?, options?)”Formats string iterables with Intl.ListFormat.
const [$t] = messages('filters', { formatTags: format(list({ type: 'conjunction' }))})
$t().formatTags(['react', 'ssr', 'signals'])// 'react, ssr, and signals'range(format, fallback?, options?)
Section titled “range(format, fallback?, options?)”Formats ranges through an Intl formatter that supports formatRange.
const [$t] = messages('price', { formatAmount: format(range(number, { style: 'currency', currency: 'USD' }))})
$t().formatAmount([1, 5])// '$1.00 – $5.00'Match And Plural
Section titled “Match And Plural”plural(param, forms?)
Section titled “plural(param, forms?)”Matches an LDML plural form with Intl.PluralRules. If forms(...) is omitted, plural reads forms directly from the translation input. A plain string translation serves every form, which suits locales without plural distinctions.
const [$t] = messages('home', { attendees: plural('count')})
$t().attendees({ count: 3})// '3 going'
/* Also works with the value itself. */$t().attendees(3)// '3 going'Use forms(...) when nested forms need their own formatters.
const [$t] = messages('home', { attendees: plural('count', forms({ one: text('{count} attendee'), other: text('{count} attendees') }))})
$t().attendees({ count: 1})// '1 attendee'Exact numeric forms are checked before plural rules.
const [$t] = messages('home', { guests: plural('count', forms({ 0: text('No guests'), one: text('{count} guest'), other: text('{count} guests') }))})
$t().guests({ count: 0})// 'No guests'match(param, cases?)
Section titled “match(param, cases?)”Matches a case by parameter value. If cases(...) is omitted, match reads cases directly from the translation input. A plain string translation serves every case.
const [$t] = messages('invite', { message: match('gender')})
$t().message({ gender: 'female'})// 'She invited female.'
/* Also works with the value itself. */$t().message('female')// 'She invited female.'Use cases(...) when cases need their own formatters.
const [$t] = messages('account', { status: match('status', cases({ active: text('Active {status}'), disabled: text('Disabled {status}') }))})
$t().status({ status: 'active'})// 'Active active'Use other(key) when unknown values should fall back to a known case instead of returning undefined.
const [$t] = messages('account', { role: match('role', other('user'))})
$t().role({ role: 'guest'})// 'User area'It also works together with cases(...).
const [$t] = messages('account', { role: match('role', cases({ admin: text(), user: text() }), other('user'))})
$t().role({ role: 'guest'})// 'User area'match can compose with plural when one case depends on another formatter.
const [$t] = messages('tasks', { message: plural('count', forms({ one: match('gender'), other: match('gender') }))})
$t().message({ gender: 'female', count: 3})// 'She has 3 tasks'match can also wrap plural when the translation shape starts with cases and nests forms inside them.
const [$t] = messages('tasks', { message: match('gender', cases({ male: plural('count', forms({ one: text('He has one task'), other: text('He has {count} tasks') })), female: plural('count', forms({ one: text('She has one task'), other: text('She has {count} tasks') })) }))})
$t().message({ gender: 'female', count: 3})// 'She has 3 tasks'Rich Text
Section titled “Rich Text”rich(tags) / rich(fallback, tags)
Section titled “rich(tags) / rich(fallback, tags)”Maps lightweight tags inside translated strings to rich chunks. Self-closing tags like <br/> are supported and mapped with empty chunks. Each tag handler also receives an unique index, usable as a key for framework nodes.
const [$t] = messages('docs', { message: rich('Read <link>the docs</link><br/>today', { link: (chunks, i) => <a key={i} href='/docs'>{chunks}</a>, br: (_, i) => <br key={i} /> })})
$t().message// ['Read ', <a href='/docs'>the docs</a>, <br />, 'today']markup(tags) / markup(fallback, tags)
Section titled “markup(tags) / markup(fallback, tags)”Maps lightweight tags to strings.
const [$t] = messages('docs', { message: markup('Read <strong>the docs</strong>', { strong: chunks => `<strong>${chunks}</strong>` })})
$t().message// 'Read <strong>the docs</strong>'Utilities
Section titled “Utilities”deflat(input, depth?)
Section titled “deflat(input, depth?)”Turns dotted object keys into nested objects.
const translations = deflat({ 'layout.title': 'Event Board', 'home.title': 'Find your next frontend event', 'home.attendees.one': '{count} going', 'home.attendees.other': '{count} going'})direction($locale) and getDirection(locale)
Section titled “direction($locale) and getDirection(locale)”Use direction($locale) when the UI needs to react to left-to-right and right-to-left locales.
import { direction } from '@nano_kit/intl'
const $dir = direction($locale)
$dir()// 'ltr' or 'rtl'getDirection(locale) is available for non-reactive code.