@fluenti/react
<I18nProvider>
Section titled “<I18nProvider>”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>| Prop | Type | Description |
|---|---|---|
locale | string | Initial locale |
fallbackLocale | string? | Fallback locale |
messages | AllMessages | Compiled message catalogs (from fluenti compile) |
loadMessages | (locale) => Promise<Messages>? | Async locale loader for lazy loading |
fallbackChain | Record<string, string[]>? | Locale fallback chains |
dateFormats | DateFormatOptions? | Custom date format presets |
numberFormats | NumberFormatOptions? | Custom number format presets |
missing | (locale, id) => string? | Missing message handler |
Lazy loading
Section titled “Lazy loading”<I18nProvider locale="en" fallbackLocale="en" messages={{ en }} loadMessages={(locale) => import(`./locales/compiled/${locale}.js`)}> <App /></I18nProvider>useI18n()
Section titled “useI18n()”Access the i18n context from the nearest provider. Throws if used outside an <I18nProvider>.
Use useI18n() when you need the full runtime API (d, n, locale, setLocale, te, tm, etc.). For translation-only code, prefer import { t } instead.
import { useI18n } from '@fluenti/react'
function MyComponent() { const { t, d, n, locale, setLocale, isLoading, loadedLocales, preloadLocale } = useI18n() return <p>{t('Hello, {name}!', { name: 'World' })}</p>}Return values
Section titled “Return values”| Property | Type | Description |
|---|---|---|
t | (id, values?) => string | Translate a message by ID or descriptor. Also usable as a tagged template: t`Hello ${name}` |
d | (value, style?) => string | Format a date for the current locale |
n | (value, style?) => string | Format a number for the current locale |
format | (message, values?) => string | Direct ICU interpolation (no catalog lookup) |
loadMessages | (locale, messages) => void | Merge messages into a locale catalog at runtime |
getLocales | () => string[] | Return all locale codes with loaded messages |
i18n | FluentInstanceExtended | Core instance (escape hatch for advanced use) |
locale | string | Current locale |
setLocale | (locale) => Promise<void> | Change locale (async if loading is needed) |
isLoading | boolean | Whether a locale is being loaded |
loadedLocales | string[] | Array of loaded locale codes |
preloadLocale | (locale) => Promise<void> | Preload a locale without switching |
te | (key: string, locale?: string) => boolean | Check if a translation key exists for the given or current locale |
tm | (key: string, locale?: string) => CompiledMessage | undefined | Get the raw compiled message for a key without interpolation |
t (compile-time import)
Section titled “t (compile-time import)”import { t } from '@fluenti/react' is the primary compile-time translation API.
import { t } from '@fluenti/react'
function Hero({ name }: { name: string }) { return <h1>{t`Hello ${name}`}</h1>}Supported forms:
t`Hello ${name}`t({ message: 'Hello {name}', context: 'hero' }, { name })
imported t is not a runtime lookup API. It does not support t('message.id'). Use useI18n().t() when you need ID lookup, imperative access, or the full runtime surface.
Without the Fluenti build plugin or Next loader, imported t throws at runtime. The runtime-capable fallback APIs are useI18n().t() and the translation components below.
useI18n().t(messageOrId, values?)
Section titled “useI18n().t(messageOrId, values?)”useI18n().t() is the runtime lookup API. It supports plain message strings, explicit IDs, descriptors, and tagged-template usage on the hook binding:
// Function call — explicit message or idt('Welcome to my app')t('Hello, {name}!', { name: 'World' })t('You have {count} items', { count: 5 })
// Tagged template — compile-time optimizedt`Welcome to my app`t`Hello ${name}, you have ${count} items`The Vite plugin (or Next.js loader) keeps useI18n().t() as the runtime API. For compile-time syntax, prefer the direct-import t export.
d(value, style?)
Section titled “d(value, style?)”Format a date.
d(new Date()) // default formatd(new Date(), 'long') // named presetn(value, style?)
Section titled “n(value, style?)”Format a number.
n(1234.5) // default formatn(1234.5, 'currency') // named presetformat(message, values?)
Section titled “format(message, values?)”Direct ICU interpolation without catalog lookup.
format('Hello {name}!', { name: 'World' })<Trans>
Section titled “<Trans>”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.
| Prop | Type | Description |
|---|---|---|
children | ReactNode | Source-language content with HTML elements |
id | string? | Override the auto-generated message ID |
context | string? | Message context used for identity and translator disambiguation |
comment | string? | Translator-facing comment preserved in extraction output |
render | (translation) => ReactNode | Custom render wrapper |
Trans still works without the build plugin. The plugin only skips runtime extraction work.
<Plural>
Section titled “<Plural>”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.
| Prop | Type | Description |
|---|---|---|
value | number | (required) The numeric value to pluralize on |
id | string? | Override the synthetic ICU message ID |
context | string? | Message context used for identity and translator disambiguation |
comment | string? | Translator-facing comment preserved in extraction output |
zero | string? | Exact zero match |
one | string? | CLDR “one” category |
two | string? | CLDR “two” category |
few | string? | CLDR “few” category |
many | string? | CLDR “many” category |
other | string | (required) Fallback for all other values |
offset | number? | Offset to subtract before selecting category |
Plural still works without the build plugin. Runtime rendering performs normal catalog lookup through a synthetic ICU plural message.
<Select>
Section titled “<Select>”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" />| Prop | Type | Description |
|---|---|---|
value | string | (required) The value to select on |
id | string? | Override the synthetic ICU message ID |
context | string? | Message context used for identity and translator disambiguation |
comment | string? | Translator-facing comment preserved in extraction output |
options | Record<string, ReactNode>? | Preferred named options map. Takes precedence over direct case props |
other | ReactNode | (required) Fallback when no match |
[key] | ReactNode | Any additional props are treated as case options |
Select still works without the build plugin. Runtime rendering performs normal catalog lookup through a synthetic ICU select message and reconstructs rich content when present.
@fluenti/react/components remains available when you want an explicit components-only import path.
<DateTime>
Section titled “<DateTime>”Date formatting component.
import { DateTime } from '@fluenti/react'
<DateTime value={new Date()} format="long" /><NumberFormat>
Section titled “<NumberFormat>”Number formatting component.
import { NumberFormat } from '@fluenti/react'
<NumberFormat value={1234.5} format="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 { t } = useI18n() return <span>{t(ROLES[role])}</span>}Server Components (@fluenti/react/server)
Section titled “Server Components (@fluenti/react/server)”For React Server Components (Next.js App Router), Fluenti provides a server entry point that works without React Context.
createServerI18n(config)
Section titled “createServerI18n(config)”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, Select, 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' },})Config
Section titled “Config”| Option | Type | Description |
|---|---|---|
loadMessages | (locale) => Promise<Messages> | (required) Async message loader |
fallbackLocale | string? | Fallback when a key is missing |
resolveLocale | () => string | Promise<string>? | Auto-detect locale when setLocale() wasn’t called (e.g. in Server Actions) |
fallbackChain | Record<string, string[]>? | Custom fallback chains per locale |
dateFormats | DateFormatOptions? | Custom date format presets |
numberFormats | NumberFormatOptions? | Custom number format presets |
missing | (locale, id) => string? | Missing message handler |
Returns
Section titled “Returns”| Property | Type | Description |
|---|---|---|
setLocale | (locale: string) => void | Set locale for the current request |
getI18n | () => Promise<FluentInstanceExtended> | Get the i18n instance |
Trans | async component | Rich text with component interpolation |
Plural | async component | Locale-aware pluralization |
Select | async component | Locale-aware categorical selection |
DateTime | async component | Date formatting |
NumberFormat | async component | Number formatting |
setLocale(locale)
Section titled “setLocale(locale)”Set the locale for the current server request. Call once in your root layout before rendering.
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>}getI18n()
Section titled “getI18n()”Get a fully configured i18n instance for the current request. Messages are loaded lazily and cached per-request.
import { getI18n } from '@/lib/i18n.server'
export default async function Page() { const { t, d, n } = await getI18n() return <h1>{t('Welcome')}</h1>}Server <Trans>
Section titled “Server <Trans>”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>}| Prop | Type | Description |
|---|---|---|
children | ReactNode | Source-language content with HTML elements |
id | string? | Override auto-generated hash ID |
comment | string? | Context comment for translators |
render | (translation: ReactNode) => ReactNode? | Custom render wrapper |
Server <Plural>
Section titled “Server <Plural>”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.
Server <DateTime>
Section titled “Server <DateTime>”import { DateTime } from '@/lib/i18n.server'
<DateTime value={new Date()} format="long" />Server <NumberFormat>
Section titled “Server <NumberFormat>”import { NumberFormat } from '@/lib/i18n.server'
<NumberFormat value={1234.56} format="currency" />resolveLocale (Server Actions)
Section titled “resolveLocale (Server Actions)”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' },})'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')}SSR Utilities
Section titled “SSR Utilities”Re-exported from @fluenti/core for convenience:
| Export | Description |
|---|---|
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' |
Reactivity
Section titled “Reactivity”React’s re-render cycle handles reactivity automatically. When setLocale() is called:
- The provider updates its internal state
- A new
createFluentiCore()instance is created with the new locale - All child components re-render with updated translations
Since t() is an O(1) hash lookup, calling it directly in JSX is efficient — no useMemo needed.
Vite Plugin Integration
Section titled “Vite Plugin Integration”When using @fluenti/react/vite-plugin, the plugin uses AST scope analysis to detect direct-import t and transforms it at build time:
import { t } from '@fluenti/react't`Hello ${name}` → t('Hello {name}', { name })Only Fluenti’s imported t is transformed. Other variables named t are left untouched. Runtime lookup remains on useI18n().t().