Skip to content
Domphy

API Reference

createI18n

createI18n<TLocale extends string, TMessages>(options): I18nInstance

Options

OptionTypeDescription
globalKeystringUnique key on globalThis — deduplicates instance across Vite chunks
namespacestringi18next resource namespace
localesRecord<TLocale, Record<string, unknown>>Translation objects keyed by locale code
defaultLocaleTLocaleFallback locale
interpolation{ escapeValue?: boolean } (optional)i18next interpolation options — escapeValue defaults to true (i18next's safe default); pass false to disable HTML escaping globally

TMessages is a separately-supplied generic used only to type t()'s key argument (via FlattenKeys<TMessages>) — it isn't structurally checked against locales, so passing a TMessages shape that doesn't match your locales values won't be caught by TypeScript.

Returns: I18nInstance

MemberSignatureDescription
t(key, opts?) → stringStatic translation
t(listener, key, opts?) → stringReactive translation — re-renders on setLocale
localeState<TLocale>Reactive locale state — read with (l) => i18n.locale.get(l) to bind UI
currentLocale(listener) → TLocaleReactive read of current locale code — sugar for locale.get(listener)
exists(key) → booleanReturns true if the key exists in the active locale's translations
initI18n(locale?) → Promise<void>Initialize i18next with the given locale (defaults to defaultLocale)
setLocale(locale) → Promise<void>Switch locale and trigger reactive re-renders
getLocale() → TLocaleGet current locale (non-reactive)
detectLocale(opts?) → TLocaleDetect locale from URL path prefix or localStorage

runWithI18n

runWithI18n<T>(fn: () => T): T

Runs fn in a fresh request-locale scope. On the client this is a no-op (globalThis is the single locale). On the server, wrap each request so initI18n / t / getLocale stay isolated — Node HTTP already provides a per-request async context, so the wrapper is mainly for tests and frameworks that do not.

initI18n / setLocale on the server do not change the shared store's language; t(), getLocale(), currentLocale(), exists(), and locale.get() read the request locale when one is bound.

detectLocale options

detectLocale({
  storageKey?: string   // localStorage key to read persisted locale from
  pathSegment?: boolean // check first URL path segment (/vi/...) — default true
})

Priority order: URL path segment first, then storageKey (localStorage), then defaultLocale.

currentLocale — reactive locale code

currentLocale(listener) is shorthand for locale.get(listener). Use it wherever you need the active locale code as a reactive value:

const { currentLocale } = i18n

// Render locale-aware content reactively
const LocaleTag = {
  span: (l) => currentLocale(l).toUpperCase(),   // "EN" / "FR" / "VI"
}

// Pass locale to Intl APIs reactively
const Price = (amount: number) => ({
  span: (l) =>
    new Intl.NumberFormat(currentLocale(l), { style: "currency", currency: "USD" }).format(amount),
})

exists — key presence check

exists(key) returns true if the key is present in the active locale's translation resource. Useful for conditional rendering when a translation may be optional:

const { t, exists } = i18n

// Only render a help tooltip if a key is defined
const MaybeHelp = exists("form.emailHelp")
  ? { span: t("form.emailHelp") }
  : null

Type safety

Pass your translation object as a generic parameter to get fully typed keys:

const en = {
  nav: { home: "Home", about: "About" },
  button: { save: "Save", cancel: "Cancel" },
} as const

const { t } = createI18n<"en" | "fr", typeof en>({ ... })

t("nav.home")     // ✓
t("button.save")  // ✓
t("nav.missing")  // ✗ TypeScript error