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

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>
}
PropertyTypeDescription
t(id, values?) => stringTranslate a message by ID or descriptor. Also usable as a tagged template: t`Hello ${name}`
d(value, style?) => stringFormat a date for the current locale
n(value, style?) => stringFormat a number for the current locale
format(message, values?) => stringDirect ICU interpolation (no catalog lookup)
loadMessages(locale, messages) => voidMerge messages into a locale catalog at runtime
getLocales() => string[]Return all locale codes with loaded messages
i18nFluentInstanceExtendedCore instance (escape hatch for advanced use)
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
te(key: string, locale?: string) => booleanCheck if a translation key exists for the given or current locale
tm(key: string, locale?: string) => CompiledMessage | undefinedGet the raw compiled message for a key without interpolation

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() 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 id
t('Welcome to my app')
t('Hello, {name}!', { name: 'World' })
t('You have {count} items', { count: 5 })
// Tagged template — compile-time optimized
t`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.

Format a date.

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

Format a number.

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

Direct ICU interpolation without catalog lookup.

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
idstring?Override the auto-generated message ID
contextstring?Message context used for identity and translator disambiguation
commentstring?Translator-facing comment preserved in extraction output
render(translation) => ReactNodeCustom render wrapper

Trans still works without the build plugin. The plugin only skips runtime extraction work.

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
idstring?Override the synthetic ICU message ID
contextstring?Message context used for identity and translator disambiguation
commentstring?Translator-facing comment preserved in extraction output
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

Plural still works without the build plugin. Runtime rendering performs normal catalog lookup through a synthetic ICU plural message.

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
idstring?Override the synthetic ICU message ID
contextstring?Message context used for identity and translator disambiguation
commentstring?Translator-facing comment preserved in extraction output
optionsRecord<string, ReactNode>?Preferred named options map. Takes precedence over direct case props
otherReactNode(required) Fallback when no match
[key]ReactNodeAny 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.

Date formatting component.

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

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>
}

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, 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'
},
})
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
Selectasync componentLocale-aware categorical selection
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()} format="long" />
import { NumberFormat } from '@/lib/i18n.server'
<NumberFormat value={1234.56} format="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 createFluentiCore() instance is created with the new locale
  3. 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.

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().