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".

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
Previous
Session
Next
Navigate