Skip to content
fluenti

@fluenti/react

Provide i18n context to the component tree.

import { I18nProvider } from '@fluenti/react'
import en from './locales/compiled/en'
import zhCN from './locales/compiled/zh-CN'
<I18nProvider
locale="en"
fallbackLocale="en"
messages={{ en, 'zh-CN': zhCN }}
>
<App />
</I18nProvider>
PropTypeDescription
localestringInitial locale
fallbackLocalestring?Fallback locale
messagesAllMessagesCompiled message catalogs (from fluenti compile)
loadMessages(locale) => Promise<Messages>?Async locale loader for lazy loading
fallbackChainRecord<string, string[]>?Locale fallback chains
dateFormatsDateFormatOptions?Custom date format presets
numberFormatsNumberFormatOptions?Custom number format presets
missing(locale, id) => string?Missing message handler
<I18nProvider
locale="en"
fallbackLocale="en"
messages={{ en }}
loadMessages={(locale) => import(`./locales/compiled/${locale}.js`)}
>
<App />
</I18nProvider>

Access the i18n context from the nearest provider. Throws if used outside an <I18nProvider>.

import { useI18n } from '@fluenti/react'
function MyComponent() {
const { i18n, locale, setLocale, isLoading, loadedLocales, preloadLocale } = useI18n()
return <p>{i18n.t('Hello, {name}!', { name: 'World' })}</p>
}
PropertyTypeDescription
i18nFluentInstanceExtendedFluent instance with t(), d(), n(), format() methods
localestringCurrent locale
setLocale(locale) => Promise<void>Change locale (async if loading is needed)
isLoadingbooleanWhether a locale is being loaded
loadedLocalesstring[]Array of loaded locale codes
preloadLocale(locale) => Promise<void>Preload a locale without switching

Translate a message. O(1) hash lookup — safe to call directly in JSX without useMemo.

i18n.t('Welcome to my app')
i18n.t('Hello, {name}!', { name: 'World' })
i18n.t('You have {count} items', { count: 5 })

Format a date.

i18n.d(new Date()) // default format
i18n.d(new Date(), 'long') // named preset

Format a number.

i18n.n(1234.5) // default format
i18n.n(1234.5, 'currency') // named preset

Direct ICU interpolation without catalog lookup.

i18n.format('Hello {name}!', { name: 'World' })

Rich text with component interpolation. Write native HTML — no indexed placeholders.

import { Trans } from '@fluenti/react'
<Trans>Click <a href="/next">here</a> to continue.</Trans>
<Trans>This is <strong>important</strong> and <em>urgent</em>.</Trans>

The component extracts text content as the source message (e.g., Click <0>here</0> to continue.), looks up the translation, and reconstructs the React elements with cloneElement.

PropTypeDescription
childrenReactNodeSource-language content with HTML elements
tagstring?Wrapper element (default: 'span')

Locale-aware pluralization using Intl.PluralRules.

import { Plural } from '@fluenti/react'
<Plural value={count} zero="No items" one="# item" other="# items" />

Use # as a placeholder for the numeric value.

PropTypeDescription
valuenumber(required) The numeric value to pluralize on
zerostring?Exact zero match
onestring?CLDR “one” category
twostring?CLDR “two” category
fewstring?CLDR “few” category
manystring?CLDR “many” category
otherstring(required) Fallback for all other values
offsetnumber?Offset to subtract before selecting category
tagstring?Wrapper element (default: 'span')

Categorical value selection (gender, role, etc.).

import { Select } from '@fluenti/react'
<Select value={gender} male="He liked this" female="She liked this" other="They liked this" />
PropTypeDescription
valuestring(required) The value to select on
otherstring(required) Fallback when no match
tagstring?Wrapper element (default: 'span')
[key]stringAny additional props are treated as case options

Date formatting component.

import { DateTime } from '@fluenti/react'
<DateTime value={new Date()} style="long" />

Number formatting component.

import { NumberFormat } from '@fluenti/react'
<NumberFormat value={1234.5} style="decimal" />

Re-exported from @fluenti/core. Create lazy message descriptors for module-level constants.

import { msg } from '@fluenti/react'
const ROLES = {
admin: msg`Administrator`,
user: msg`Regular User`,
}
// Resolve at render time:
function RoleBadge({ role }) {
const { i18n } = useI18n()
return <span>{i18n.t(ROLES[role])}</span>
}

For React Server Components (Next.js App Router), Fluenti provides a server entry point that works without React Context.

Create request-scoped i18n utilities. Uses React.cache() to isolate state per server request.

import { createServerI18n } from '@fluenti/react/server'
export const { setLocale, getI18n, Trans, Plural, DateTime, NumberFormat } = createServerI18n({
loadMessages: (locale) => import(`../locales/compiled/${locale}.js`),
fallbackLocale: 'en',
resolveLocale: async () => {
const { cookies } = await import('next/headers')
return (await cookies()).get('locale')?.value ?? 'en'
},
})
OptionTypeDescription
loadMessages(locale) => Promise<Messages>(required) Async message loader
fallbackLocalestring?Fallback when a key is missing
resolveLocale() => string | Promise<string>?Auto-detect locale when setLocale() wasn’t called (e.g. in Server Actions)
fallbackChainRecord<string, string[]>?Custom fallback chains per locale
dateFormatsDateFormatOptions?Custom date format presets
numberFormatsNumberFormatOptions?Custom number format presets
missing(locale, id) => string?Missing message handler
PropertyTypeDescription
setLocale(locale: string) => voidSet locale for the current request
getI18n() => Promise<FluentInstanceExtended>Get the i18n instance
Transasync componentRich text with component interpolation
Pluralasync componentLocale-aware pluralization
DateTimeasync componentDate formatting
NumberFormatasync componentNumber formatting

Set the locale for the current server request. Call once in your root layout before rendering.

app/layout.tsx
import { setLocale } from '@/lib/i18n.server'
import { cookies } from 'next/headers'
export default async function RootLayout({ children }) {
const locale = (await cookies()).get('locale')?.value ?? 'en'
setLocale(locale)
return <html lang={locale}><body>{children}</body></html>
}

Get a fully configured i18n instance for the current request. Messages are loaded lazily and cached per-request.

app/page.tsx
import { getI18n } from '@/lib/i18n.server'
export default async function Page() {
const { t, d, n } = await getI18n()
return <h1>{t('Welcome')}</h1>
}

Async server component for rich text. Same syntax as the client <Trans> — no i18n prop needed.

import { Trans } from '@/lib/i18n.server'
export default async function Page() {
return <Trans>Read the <a href="/docs">documentation</a> for more info.</Trans>
}
PropTypeDescription
childrenReactNodeSource-language content with HTML elements
idstring?Override auto-generated hash ID
commentstring?Context comment for translators
render(translation: ReactNode) => ReactNode?Custom render wrapper
import { Plural } from '@/lib/i18n.server'
<Plural value={count} zero="No items" one="# item" other="# items" />

Props are identical to the client <Plural>: value, zero, one, two, few, many, other, offset.

import { DateTime } from '@/lib/i18n.server'
<DateTime value={new Date()} style="long" />
import { NumberFormat } from '@/lib/i18n.server'
<NumberFormat value={1234.56} style="currency" />

Server Actions are independent requests that skip the layout tree, so setLocale() is never called. Provide resolveLocale to auto-detect the locale:

export const { setLocale, getI18n } = createServerI18n({
loadMessages: (locale) => import(`../locales/compiled/${locale}.js`),
resolveLocale: async () => {
const { cookies } = await import('next/headers')
return (await cookies()).get('locale')?.value ?? 'en'
},
})
app/actions.ts
'use server'
import { getI18n } from '@/lib/i18n.server'
export async function greetAction() {
const i18n = await getI18n() // resolveLocale is called automatically
return i18n.t('Hello from server action')
}

Re-exported from @fluenti/core for convenience:

ExportDescription
detectLocale(options)Detect locale from cookie, header, query, etc.
getSSRLocaleScript(locale)Generate <script> tag to inject locale into HTML
getHydratedLocale(fallback)Read the injected locale on the client
isRTL(locale)Check if a locale is right-to-left
getDirection(locale)Return 'rtl' or 'ltr'

React’s re-render cycle handles reactivity automatically. When setLocale() is called:

  1. The provider updates its internal state
  2. A new createFluent() instance is created with the new locale
  3. All child components re-render with updated translations

Since i18n.t() is an O(1) hash lookup, calling it directly in JSX is efficient — no useMemo needed.

When using @fluenti/vite-plugin with framework: 'react', the tagged template t`` “ is transformed at build time:

t`Hello ${name}` → __i18n.t('Hello {name}', { name })

The plugin auto-injects the __useI18n() internal hook. Unlike Vue (computed) and Solid (createMemo), React uses direct calls since re-renders handle reactivity.