← All @molecule/* packages · App templates
@molecule/app-i18n-react-i18nextProvider bond · i18n · App (browser) · v1.0.1 · Apache-2.0
react-i18next provider for molecule.dev
npm install @molecule/app-i18n-react-i18nextnpm · Source on GitHub · Implements @molecule/app-i18n
@molecule/app-i18n-react-i18next is a provider bond on the app (browser) side: it implements the i18n core interface (@molecule/app-i18n) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
import { setProvider } from '@molecule/app-i18n'
import { createReactI18nextProvider } from '@molecule/app-i18n-react-i18next'
const provider = createReactI18nextProvider({
defaultLocale: 'en',
locales: [
{ code: 'en', name: 'English', translations: { ... } },
{ code: 'fr', name: 'French', translations: { ... } },
],
})
setProvider(provider)Works with: @molecule/app-i18n
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
react-i18next provider for molecule.dev.
Implements the I18nProvider interface using i18next and react-i18next.
import { setProvider } from '@molecule/app-i18n'
import { createReactI18nextProvider } from '@molecule/app-i18n-react-i18next'
const provider = createReactI18nextProvider({
defaultLocale: 'en',
locales: [
{ code: 'en', name: 'English', translations: { ... } },
{ code: 'fr', name: 'French', translations: { ... } },
],
})
setProvider(provider)
provider
npm install @molecule/app-i18n-react-i18next @molecule/app-i18n @molecule/app-i18n-i18next i18next react react-i18next
npm install -D @types/react
DateFormatOptionsDate format options.
interface DateFormatOptions {
/**
* Date style.
*/
dateStyle?: 'full' | 'long' | 'medium' | 'short'
/**
* Time style.
*/
timeStyle?: 'full' | 'long' | 'medium' | 'short'
/**
* Custom format string (implementation-specific).
*/
format?: string
/**
* Relative time.
*/
relative?: boolean
}
I18nextProviderConfigConfiguration options for the i18next provider.
interface I18nextProviderConfig {
/**
* Default locale code.
*/
defaultLocale?: string
/**
* Fallback locale code.
*/
fallbackLocale?: string
/**
* Available locales with translations.
*/
locales?: LocaleConfig[]
/**
* Enable language detection.
*/
detection?: boolean
/**
* Language detection options.
*/
detectionOptions?: {
order?: (
'querystring' | 'cookie' | 'localStorage' | 'sessionStorage' | 'navigator' | 'htmlTag'
)[]
lookupQuerystring?: string
lookupCookie?: string
lookupLocalStorage?: string
lookupSessionStorage?: string
caches?: ('localStorage' | 'cookie')[]
}
/**
* Debug mode.
*/
debug?: boolean
/**
* Custom i18next initialization options.
*/
i18nextOptions?: Partial<InitOptions>
/**
* i18next plugins to apply before initialization.
*
* Each plugin is passed to `i18n.use()` before `i18n.init()`.
* Useful for framework integrations (e.g. react-i18next's `initReactI18next`).
*/
plugins?: unknown[]
}
I18nProvideri18n provider interface.
All i18n providers must implement this interface.
interface I18nProvider {
/**
* Gets the current locale.
*/
getLocale(): string
/**
* Sets the current locale.
*
* **Fleet contract:** every conformant provider (the core simple provider,
* `@molecule/api-i18n-simple`, `@molecule/app-i18n-i18next`, and
* `@molecule/app-i18n-react-i18next`) MUST throw `Error('Locale "<code>"
* not found')` when `locale` is not registered — via the constructor's
* `initialLocales`/`locales` config, `addLocale()`, or `addTranslations()`
* (all three register a locale). It must NOT silently degrade to
* fallback-locale text while `getLocale()` reports the unregistered code —
* that divergence makes a misconfigured locale switch indistinguishable
* from a working one until a user notices the wrong language on screen.
*/
setLocale(locale: string): Promise<void>
/**
* Gets all available locales.
*/
getLocales(): LocaleConfig[]
/**
* Adds a locale.
*/
addLocale(config: LocaleConfig): void
/**
* Removes a locale by code, notifying subscribers so language pickers
* built on `onLocaleChange` re-render their list. If the removed locale
* is currently active, the caller is responsible for switching to a
* fallback (e.g. `'en'`) BEFORE calling this — the provider will not
* auto-fall-back on its own.
*
* Returns `true` if the locale was registered and removed, `false`
* otherwise.
*/
removeLocale(code: string): boolean
/**
* Adds translations to a locale. Auto-creates the locale if it doesn't exist.
*
* **Fleet contract:** merges are DEEP, not a shallow spread — registering
* two calls (e.g. two modules) that share a top-level namespace key merges
* their subtrees instead of the second call clobbering the first's nested
* translations wholesale. `@molecule/api-i18n-simple` implements the same
* contract on the API side.
*/
addTranslations(locale: string, translations: Translations, namespace?: string): void
/**
* Translates a key with optional interpolation values and pluralization.
*
* **Fleet plural contract (matches i18next's own key resolution order):**
* when `options.count` is provided, the plural-suffixed key
* (`` `${key}_${pluralForm}` ``, e.g. `item_one`/`item_few`/…, falling back
* to `` `${key}_other` ``) is looked up FIRST and wins over the base `key`
* if BOTH are registered. Only when no plural-suffixed key exists at all
* does resolution fall back to the base key. A catalog that ships both
* `item` and `item_one`/`item_other` therefore pluralizes identically
* whichever provider is bonded.
*
* @returns The translated string, or the default value / key if not found.
*/
t(
key: string,
values?: InterpolationValues,
options?: {
defaultValue?: string
count?: number
},
): string
/**
* Checks if a translation key exists.
*
* **Fleet contract:** follows the SAME locale-resolution chain as `t()` —
* the active locale, then the English fallback — so `exists(key) === true`
* whenever `t(key)` would render real translated text (not the raw key or
* an inline `defaultValue`). Do not narrow this to "only the active
* locale's own catalog"; that made `exists()` return `false` for keys `t()`
* happily rendered via the English fallback, and the answer differed by
* provider.
*
* @returns `true` if the key has a translation.
*/
exists(key: string): boolean
/**
* Formats a number according to the current locale.
*
* @returns The locale-formatted number string.
*/
formatNumber(value: number, options?: NumberFormatOptions): string
/**
* Formats a date according to the current locale.
*
* @returns The locale-formatted date string.
*/
formatDate(value: Date | number | string, options?: DateFormatOptions): string
/**
* Formats a relative time (e.g. "2 hours ago").
*
* @returns The locale-formatted relative time string.
*/
formatRelativeTime(
value: Date | number,
options?: {
unit?: Intl.RelativeTimeFormatUnit
},
): string
/**
* Formats a list (e.g. "A, B, and C").
*
* @returns The locale-formatted list string.
*/
formatList(
values: string[],
options?: {
type?: 'conjunction' | 'disjunction' | 'unit'
},
): string
/**
* Subscribes to locale changes.
*
* @returns An unsubscribe function.
*/
onLocaleChange(listener: (locale: string) => void): () => void
/**
* Gets the text direction for the current locale.
*
* @returns `'ltr'` or `'rtl'`.
*/
getDirection(): 'ltr' | 'rtl'
/**
* Checks if a translation key exists (alias for exists).
*/
hasKey?(key: string): boolean
/**
* Checks if the provider is ready.
*/
isReady?(): boolean
/**
* Registers a callback for when the provider is ready.
*/
onReady?(callback: () => void): () => void
/**
* Registers a lazily-loaded content module for automatic reload on locale changes.
* All registered content is reloaded during `setLocale()` before listeners fire,
* ensuring content is available on the first re-render with no flash.
*
* Idempotent: registering the same module name twice is a no-op.
*/
registerContent?(module: string, loader: (locale: string) => Promise<void>): void
}
LocaleConfigConfiguration for a supported locale (code, display name, text direction, translations or lazy loader).
interface LocaleConfig {
/**
* Locale code (e.g., 'en-US', 'fr-FR').
*/
code: string
/**
* Display name (e.g., 'English (US)', 'Francais').
*/
name: string
/**
* Native display name.
*/
nativeName?: string
/**
* Text direction.
*/
direction?: 'ltr' | 'rtl'
/**
* Translations for this locale.
*/
translations?: Translations
/**
* Lazy loader for translations. Called on first setLocale() to this locale.
* When provided, translations can be omitted and will be loaded on demand.
*/
loader?: () => Promise<Translations>
}
NumberFormatOptionsNumber format options.
interface NumberFormatOptions {
/**
* Number style.
*/
style?: 'decimal' | 'currency' | 'percent' | 'unit'
/**
* Currency code (for currency style).
*/
currency?: string
/**
* Minimum fraction digits.
*/
minimumFractionDigits?: number
/**
* Maximum fraction digits.
*/
maximumFractionDigits?: number
/**
* Use grouping separators.
*/
useGrouping?: boolean
}
TranslationsTranslation key/value map.
interface Translations {
[key: string]: string | Translations
}
InterpolationValuesKey-value map of interpolation variables passed to a translation string (e.g. { name: 'World' }).
type InterpolationValues = Record<string, string | number | boolean | Date>
ReactI18nextProviderConfigConfiguration options for the react-i18next provider.
Identical to I18nextProviderConfig — the React-specific setup (initReactI18next plugin, Suspense) is handled automatically.
type ReactI18nextProviderConfig = I18nextProviderConfig
createReactI18nextProvider(config)Creates a React-specific i18n provider that wraps the base i18next provider with the
react-i18next plugin. Enables useSuspense by default for React Suspense integration.
function createReactI18nextProvider(
config?: I18nextProviderConfig,
): I18nProvider & { i18n: i18n; initialize: () => Promise<void> }
config — Same as I18nextProviderConfig plus optional React-specific overrides.Returns: An I18nProvider with the react-i18next plugin pre-registered.
useI18n()React hook that provides translation, locale switching, and formatting functions
using react-i18next under the hood. Wraps useTranslation() into the molecule i18n interface.
function useI18n(): {
t: (key: string, values?: InterpolationValues) => string
locale: string
setLocale: (locale: string) => Promise<unknown>
formatNumber: (value: number, options?: NumberFormatOptions) => string
formatDate: (value: Date | number | string, options?: DateFormatOptions) => string
}
Returns: An object with t (translate), locale, setLocale, formatNumber, and formatDate.
I18nextProviderconst I18nextProvider: React.FunctionComponent<I18nextProviderProps>
localeConfigToResourcesConverts an array of molecule LocaleConfig objects to the i18next resource bundle format.
Each locale's translations are placed under a translation namespace keyed by locale code.
const localeConfigToResources: (
locales: LocaleConfig[],
) => Record<string, { translation: Translations }>
providerDefault provider instance.
const provider: I18nProvider & { i18n: i18n; initialize: () => Promise<void> }
Transconst Trans: TransLegacy
useTranslationconst useTranslation: UseTranslationLegacy
Implements @molecule/app-i18n interface.
Setup function to register this provider with the core interface:
import { setProvider } from '@molecule/app-i18n'
import { provider } from '@molecule/app-i18n-react-i18next'
export function setupI18nReactI18next(): void {
setProvider(provider)
}
Peer dependencies:
@molecule/app-i18n ^1.0.1react ^18.0.0 || ^19.0.0@molecule/app-i18n@molecule/app-i18n-i18nexti18nextreactreact-i18nextStartup locale vs. detection: with detection: true (the default),
defaultLocale is only the FALLBACK — the actual startup locale is
whatever the browser detector resolves (querystring, then navigator, by
default). Apps that want a pinned startup locale must pass
detection: false (or an explicit i18nextOptions.lng).
setLocale() contract: createReactI18nextProvider() is a thin
wrapper over @molecule/app-i18n-i18next's createI18nextProvider(), so
its setLocale() THROWS for an unregistered locale — see that package's
remarks for the fleet-wide contract. The separate useI18n() hook below,
however, calls react-i18next's raw i18n.changeLanguage() directly and
is NOT an I18nProvider — it does not throw for an unregistered locale.
React Suspense: the provider sets react.useSuspense: true by default,
so components using useTranslation()/Trans may SUSPEND while i18next
initializes — wrap the app (or the i18n-using subtree) in a
<Suspense fallback={…}> boundary, or opt out with
i18nextOptions: { react: { useSuspense: false } }.