@fluenti/next
v0.6.3
Published
Next.js plugin for Fluenti — withFluenti, I18nProvider, t`` transforms for App Router and Pages Router
Maintainers
Readme
@fluenti/next
Compile-time i18n for Next.js. App Router native. RSC + streaming + server actions out of the box.
// Server Component — zero client JS
import { t } from '@fluenti/react'
export default async function Page() {
return <h1>{t`Welcome, ${user.name}!`}</h1>
}// Client Component — same syntax
'use client'
import { t } from '@fluenti/react'
export default function Counter() {
return <p>{t`You have ${count} items in your cart.`}</p>
}No runtime parsing. Messages are compiled at build time, and the generated server/runtime helpers are tree-shaken with your app code.
Features
- App Router native — first-class RSC, streaming SSR, and server actions support
- Next.js 14 & 15 compatible (
next >= 14.0.0) t\`` tagged templates — write messages inline, extract them with the CLI- Binding-aware transforms — the webpack loader rewrites tagged templates only for proven Fluenti bindings
I18nProvider— async server component that sets up both server and client i18n in one placewithLocale()— per-component locale isolation in RSC- ICU MessageFormat — plurals, selects, nested arguments, custom formatters
- Per-locale generated modules — server/runtime helpers are generated per project, while the client provider statically bundles configured locale catalogs for RSC hydration
- Cookie-based locale detection — reads
localecookie in server components by default
Quick Start
1. Install
pnpm add @fluenti/next @fluenti/core @fluenti/react
pnpm add -D @fluenti/cli2. Configure next.config.ts
import type { NextConfig } from 'next'
import { withFluenti } from '@fluenti/next'
const nextConfig: NextConfig = {
reactStrictMode: true,
}
export default withFluenti()(nextConfig)withFluenti() adds a webpack loader that rewrites direct-import authoring APIs in supported client/server scopes and generates a server module for RSC i18n. It reads your fluenti.config.ts automatically.
You can pass overrides directly:
export default withFluenti({
locales: ['en', 'ja', 'zh-CN'],
defaultLocale: 'en',
})(nextConfig)3. Set up I18nProvider in your root layout
// app/layout.tsx
import { cookies } from 'next/headers'
import { I18nProvider } from '@fluenti/next'
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const cookieStore = await cookies()
const locale = cookieStore.get('locale')?.value ?? 'en'
return (
<html lang={locale}>
<body>
<I18nProvider locale={locale}>
{children}
</I18nProvider>
</body>
</html>
)
}I18nProvider is an async server component. It initializes the server-side i18n instance (via React.cache) and wraps children in a client-side I18nProvider for hydration.
4. Use t\`` in your pages
Server Component (default in app/):
// app/rsc/page.tsx
import { t } from '@fluenti/react'
export default async function RSCPage() {
return (
<div>
<h1>{t`Server rendered`}</h1>
<p>{t`This page is a React Server Component.`}</p>
</div>
)
}Client Component:
// app/page.tsx
'use client'
import { t, useI18n } from '@fluenti/react'
export default function Home() {
const { setLocale, preloadLocale } = useI18n()
const name = 'World'
return <h1>{t`Hello, ${name}!`}</h1>
}import { t } is the primary compile-time API. It supports tagged templates and descriptor calls, but not t('message.id') lookup. In Next apps using withFluenti(), ordinary authoring imports come from @fluenti/react on both the client and the server. For runtime lookup or full imperative access, use await getI18n() on the server or useI18n() on the client.
5. Extract and compile messages
# Extract messages from source files
pnpm fluenti extract
# Translate your PO files, then compile
pnpm fluenti compileNext.js-Specific Features
React Server Components
Server components use t\`with zero client-side JavaScript. The loader detects server context automatically (files inapp/without'use client'`).
For direct access to the i18n instance in RSC:
import { setLocale, getI18n } from '@fluenti/next'
export default async function Page({ searchParams }) {
const params = await searchParams
if (params.lang) setLocale(params.lang)
const { t, locale } = await getI18n()
return <p>{t`Current server locale: ${locale}`}</p>
}Streaming SSR
Works with Suspense boundaries — streamed content is translated on the server:
import { Suspense } from 'react'
async function SlowContent() {
const { getI18n } = await import('@fluenti/next')
const { t } = await getI18n()
await fetchData()
return <p>{t`Streamed content loaded!`}</p>
}
export default async function StreamingPage() {
return (
<Suspense fallback={<p>Loading...</p>}>
<SlowContent />
</Suspense>
)
}Server Actions
Direct-import t works in 'use server' functions:
'use server'
import { t } from '@fluenti/react'
export async function greetAction(): Promise<string> {
return t`Hello from server action`
}Metadata i18n
Translate Next.js metadata using the server i18n instance:
import { getI18n } from '@fluenti/next'
export async function generateMetadata() {
const i18n = await getI18n()
return {
title: i18n.t('My App — Internationalized'),
description: i18n.t('A fully localized Next.js application'),
}
}Middleware Locale Detection
Detect locale from headers, cookies, or URL and set it via cookie:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const SUPPORTED_LOCALES = ['en', 'ja', 'zh-CN']
const DEFAULT_LOCALE = 'en'
export function middleware(request: NextRequest) {
const response = NextResponse.next()
// Already has a locale cookie
if (request.cookies.get('locale')?.value) {
return response
}
// Detect from Accept-Language header
const acceptLang = request.headers.get('accept-language') ?? ''
const detected = acceptLang
.split(',')
.map((s) => s.split(';')[0]!.trim())
.find((lang) => SUPPORTED_LOCALES.includes(lang))
response.cookies.set('locale', detected ?? DEFAULT_LOCALE)
return response
}Per-Component Locale Isolation
Render a subtree in a different locale using withLocale():
import { withLocale } from '@fluenti/next/server'
export default async function Page() {
return (
<div>
<h1>{t`Main content`}</h1>
{await withLocale('ja', async () => (
<JapaneseWidget />
))}
</div>
)
}API Reference
withFluenti(config?)
Wraps your Next.js config with Fluenti support. Accepts an optional WithFluentConfig:
| Option | Type | Description |
|--------|------|-------------|
| locales | string[] | Override locales from fluenti.config.ts |
| defaultLocale | string | Override source locale |
| compiledDir | string | Override compiled messages directory |
| serverModule | string | Custom server module path (skip auto-generation) |
| resolveLocale | () => string \| Promise<string> | Custom locale resolver for server actions |
| dateFormats | DateFormatOptions | Custom date format styles |
| numberFormats | NumberFormatOptions | Custom number format styles |
| fallbackChain | Record<string, Locale[]> | Fallback chain per locale |
I18nProvider
Async server component (imported from @fluenti/next). Place in your root layout.
| Prop | Type | Description |
|------|------|-------------|
| locale | string? | Active locale. Defaults to defaultLocale from config. |
| children | ReactNode | Application tree |
withLocale(locale, fn)
Server utility (imported from @fluenti/next/server). Executes fn with a temporarily switched locale.
Generated Server Module
@fluenti/next exports:
| Export | Description |
|--------|-------------|
| I18nProvider | Async server component for layouts |
| setLocale(locale) | Set the request-scoped locale |
| getI18n() | Get the i18n instance (async) |
| t | Compile-time translation API, preserved for advanced/server-specific imports |
| Trans | Server component for rich text |
| Plural | Server component for plurals |
| Select | Server component for categorical selection |
| DateTime | Server component for date formatting |
| NumberFormat | Server component for number formatting |
Documentation
Full docs at fluenti.dev.
