I18N API
primate/i18n exports the catalog helper used by locale modules. Configuring
those catalogs through primate/config creates the typed translator available
as app.i18n on the server and to frontend adapters in the browser.
import i18n from "primate/i18n";The entrypoint has no named exports. I18NConfig, I18NTranslator, and other
supporting shapes below describe application-visible inferred contracts. See
the I18N guide for setup, message syntax, and frontend examples.
i18n.locale()
function locale<const M>(messages: LocaleInput<M>): M;Declares one locale catalog while preserving literal keys and message strings for translation-key, parameter, and result inference. It returns the same catalog object without changing it at runtime.
Catalog values may be strings, nested objects, or arrays of those values.
Catalog key names cannot contain .; use nested objects to create dot-path
translation keys. TypeScript reports dotted literal keys, and translator
creation validates catalogs again at runtime.
// locales/en-US.ts
import i18n from "primate/i18n";
export default i18n.locale({
greeting: "Hello {name}",
account: {
title: "Account",
},
});
LocaleCatalog
Supporting catalog shape accepted by i18n.locale(). It is not
a named export.
type LocaleValue =
| string
| { [key: string]: LocaleValue }
| LocaleValue[];
type LocaleCatalog = Record<string, LocaleValue>;Nested object paths such as account.title and numeric array paths such as
onboarding.steps.0.title are resolved to a maximum type depth of six.
I18NConfig
The i18n member accepted by config(). This is an
inferred supporting type, not an export from primate/i18n.
type I18NConfig<C extends Record<string, LocaleCatalog>> = {
defaultLocale: keyof C & string;
locales: C;
currency?: string;
persist?: "cookie" | "localStorage" | "sessionStorage" | false;
};At least one catalog is required, and defaultLocale must name one of them.
Catalogs are validated when the application translator is created.
config.defaultLocale
defaultLocale: keyof C & string;Required fallback locale. It is also the initial browser locale and the final
server fallback when neither persistence nor Accept-Language selects a
configured locale.
config.locales
locales: C;Required locale-to-catalog map. Its keys become the locale union accepted by
translator.locale.set() and translator.with(). Translation keys and return
values are inferred from the configured catalog shapes; literal message
placeholders determine required parameter types.
At runtime, lookup first uses the active catalog, then the default catalog, then returns the key string when neither contains the path.
config.currency
currency?: string;ISO currency code passed to Intl.NumberFormat for {value:c} and
{value:currency} messages. Defaults to "USD".
config.persist
persist?: "cookie" | "localStorage" | "sessionStorage" | false;Locale persistence mode used by both server request resolution and the browser
translator. Defaults to "cookie".
"cookie"sends a keepaliveHEAD /request; the server accepts configured locales and writes the strict same-siteprimate-i18ncookie."localStorage"and"sessionStorage"use the browser storage with theprimate-i18nkey. Server requests continue to useAccept-Languageand the configured default.falsedisables restoration and persistence.
Storage access failures are ignored. A failed cookie request is reported with
console.warn and does not revert the active browser locale.
I18NTranslator
Callable typed facade exposed as app.i18n and
consumed by frontend i18n adapters. It is not a named primate/i18n export.
type I18NTranslator<C extends Record<string, LocaleCatalog>> = {
<K extends TranslationKey<C>>(
...args: TranslationArgs<C, K>
): TranslationResult<C, K>;
defaultLocale: keyof C & string;
locales: readonly (keyof C & string)[];
catalogs: C;
currency: string;
locale: {
get(): keyof C & string;
set(locale: keyof C & string): void;
};
restore(state?: RestoreState): void;
with(locale: keyof C & string): TranslationFunction<C>;
};
translator()
translator<K extends TranslationKey<C>>(
...args: TranslationArgs<C, K>
): TranslationResult<C, K>;Looks up a dot path in the active catalog and then the default catalog. String messages are interpolated and formatted; non-string catalog values are returned unchanged. A known message with placeholders requires a parameter object. A missing runtime key returns the key string.
app.i18n("greeting", { name: "Ada" });
app.i18n("account.title");
TranslationArgs
Supporting conditional tuple inferred from the literal message.
type TranslationArgs<C, K> = Message<C, K> extends MessageWithParams
? [key: K, params: TranslationParams<Message<C, K>>]
: [key: K];Unformatted placeholders infer string. Format specifications infer:
| Specification | Parameter type |
|---|---|
n, number, plural choices |
number |
d, date |
Date | number |
c, currency |
number |
o, ordinal |
number |
a, ago |
number milliseconds |
l, list |
string[] |
u(...), unit(...) |
number |
Double braces escape literal braces. Nullish unformatted runtime values render
as an empty string. Unsupported unit aliases throw; other Intl formatting
failures use plain-value fallbacks.
TranslationResult
type TranslationResult<C, K> = CatalogPathValue<C, K> extends string
? string
: CatalogPathValue<C, K>;String messages return formatted strings. Object and array paths preserve their inferred value type.
translator.defaultLocale
defaultLocale: keyof C & string;Configured fallback locale.
translator.locales
locales: readonly (keyof C & string)[];Configured locale names in catalog insertion order.
translator.catalogs
catalogs: C;Configured catalog map. The translator exposes the same catalog values used for lookup.
translator.currency
currency: string;Resolved currency code, including the "USD" default.
translator.locale.get()
get(): keyof C & string;On the server, returns the active request locale or defaultLocale outside a
request context. In cookie mode, request resolution prefers a valid persisted
cookie. It then tries an Accept-Language exact or language-base match and
finally the default.
In the browser, returns the translator's active in-memory locale. Frontend adapters make changes reactive according to their framework.
translator.locale.set()
set(locale: keyof C & string): void;In the browser, validates the locale, changes it immediately, and persists it
according to config.persist. An unknown runtime locale throws
Error('[i18n] Unknown locale "...".').
The server translator is read-only; calling set() throws
Error("[i18n] locale.set is not supported on server.").
translator.restore()
restore(state?: RestoreState): void;Restores browser locale state. Cookie mode reads state.locale; storage modes
read their configured browser storage; false does nothing. Missing, unknown,
and inaccessible saved values leave the current locale unchanged. The server
implementation is a no-op.
RestoreState
type RestoreState = {
locale?: string;
};Supporting state accepted by translator.restore().
It is not a named export from primate/i18n.
translator.with()
with(
locale: keyof C & string,
): TranslationFunction<C>;On the server, returns a translator bound to the requested configured locale
without changing request state. An unknown runtime locale throws
Error('[i18n] Unknown locale "...".').
The browser translator does not support with() and throws
Error("[i18n] with is not supported on client.").
Server and browser matrix
| Member | Server | Browser |
|---|---|---|
| translation call | request locale | active in-memory locale |
locale.get() |
request/default locale | active locale |
locale.set() |
throws | changes and persists |
restore() |
no-op | restores configured persistence |
with() |
returns bound translator | throws |