@comvi/next
App Router pieces on top of @comvi/react: a composed i18n instance, a
next.config plugin, server helpers, locale middleware, navigation and routing, in seven entries.
Exports
Section titled “Exports”| Entry point | Values | Types |
|---|---|---|
@comvi/next |
createNextI18n, nextPlugin, nextPluginLazy, createI18n, I18n, icuCompiler, loader, attachLoader, plugins, definePlugin, devtools |
CreateNextI18nOptions, CreateNextI18nResult, UseInstanceIdentityError, NextComposedI18n, NextPluginOptions, LazyPluginModule, LazyPluginLoader, RoutingConfig, LocalePrefixMode, MiddlewareConfig, GetI18nOptions, I18nProviderProps, DevtoolsOptions, every @comvi/core type |
@comvi/next/plugin |
withComvi |
WithComviOptions, WithComvi, NextConfigFn |
@comvi/next/server |
getI18n, ComviProvider, getLocale, loadMessages, setRequestLocale, setI18n, createNextI18nFromInstance, createI18n, icuCompiler, loader, attachLoader, plugins, definePlugin, devtools, nextPlugin, nextPluginLazy |
ComviProviderProps, NextServerInstance, CreateNextI18nFromInstanceOptions, CreateNextI18nFromInstanceResult, ServerI18n, ServerTranslationFunction, TranslationFunction, HasTranslationOptions, GetI18nOptions, LoadMessagesOptions, MessagesMap, NextRoutingOptions, NextPluginOptions, LazyPluginModule, LazyPluginLoader, DevtoolsOptions |
@comvi/next/client |
every @comvi/react hook and T, the Next-aware I18nProvider, createI18n, the same installers as /server, nextPlugin, nextPluginLazy |
UseI18nReturn, UseI18nLoaderReturn, UseI18nPluginsReturn, UseSetLocaleTransitionReturn, UseFormattersReturn, TProps, ComponentMap, I18nProviderProps, MessagesMap, NextPluginOptions, LazyPluginModule, LazyPluginLoader, DevtoolsOptions |
@comvi/next/middleware |
createMiddleware |
MiddlewareConfig, LocaleCookieConfig, LocaleCookieOptions, LocaleDetectionConfig, LocaleDetectionSource, ResolveAcceptLanguage |
@comvi/next/navigation |
Link, usePathname, useLocalizedRouter |
LocalizedLinkProps, LocalizedRouter |
@comvi/next/routing |
defineRouting, hasLocale, createGetPathname |
RoutingConfig, LocalePrefixMode, LocaleCookieConfig, LocaleCookieOptions, GetPathnameOptions |
createNextI18n(options)
Section titled “createNextI18n(options)”function createNextI18n<D>(options: CreateNextI18nOptions<D>): CreateNextI18nResult<D>
interface CreateNextI18nResult<D, C extends NextComposedI18n<D> = NextComposedI18n<D>> { readonly i18n: C; readonly routing: Required<RoutingConfig>; use<T extends C>(installer: (i18n: C) => T): CreateNextI18nResult<D, T>;}type NextComposedI18n<D> = I18n<D> & Omit<I18nLoaderApi, 'registerLoader'> & I18nPluginApi & { registerLoader(loader: LoaderFn): void; registerLoader(importMap: LoaderImportMap): void;}| Option | Type | Default | Meaning |
|---|---|---|---|
locales |
readonly string[] |
required | Supported locales |
defaultLocale |
string |
required | Default locale |
localePrefix |
'always' | 'as-needed' | 'never' |
'as-needed' |
URL prefix mode |
pathnames |
RoutingConfig['pathnames'] |
none | Localized slugs |
localeCookie |
RoutingConfig['localeCookie'] |
'NEXT_LOCALE' |
The locale cookie, kept in result.routing |
apiKey |
string |
none | Forwarded to the i18n instance |
ns |
string[] |
none | Namespaces init() loads; omitted, only defaultNs loads |
messages |
I18nOptions['messages'] |
none | Inline catalogs |
defaultParams |
DefaultTranslationParams |
none | Interpolation defaults |
fallbackLocale |
string | string[] |
defaultLocale |
Fallback chain |
defaultNs |
string |
'default' |
Default namespace |
devMode |
boolean |
NODE_ENV === 'development' |
Read by plugins |
compiler |
MessageCompiler |
none | Pass icuCompiler for ICU |
basicHtmlTags |
string[] |
none | Tags rendered without a handler |
onMissingKey |
I18nOptions['onMissingKey'] |
none | Miss hook |
The instance comes composed: ambient tags, the loader with both registerLoader overloads, the
plugin capability and devtools discovery. use(f) is f(result.i18n); an installer returning
another object throws code: 'E_USE_INSTANCE_IDENTITY'.
nextPlugin(plugin, options?)
Section titled “nextPlugin(plugin, options?)”function nextPlugin(plugin: I18nPlugin, options?: NextPluginOptions): <T extends I18n<any>>(i18n: T) => T & I18nPluginApifunction nextPluginLazy(load: LazyPluginLoader, options?: NextPluginOptions): <T extends I18n<any>>(i18n: T) => T & I18nPluginApi
type LazyPluginLoader = () => Promise<I18nPlugin | { default: I18nPlugin }>| Option | Type | Default | Meaning |
|---|---|---|---|
runtime |
'client' | 'server' |
both | Which runtime runs the plugin |
environment |
'all' | 'development' | 'production' |
'all' |
Which build runs it |
required, timeout, onError |
PluginOptions |
Forwarded to registerPlugin |
Both attach the plugin capability if absent and register the scoped plugin; nextPluginLazy also
defers the import to the runtime the scope names. Compose localeDetector only through
nextPlugin(localeDetectorPlugin(), { runtime: 'client' }): the server instance is process-wide, so a detector there runs once at the first init(), detects nothing, and never runs per request.
withComvi(options)
Section titled “withComvi(options)”From @comvi/next/plugin, Node only: export default withComvi({ config: './src/i18n/config.ts' })(nextConfig).
function withComvi(options: { config: string }): WithComvi
interface WithComvi { (nextConfig?: NextConfig): NextConfig; (nextConfig: NextConfigFn): NextConfigFn; // the (phase, { defaultConfig }) form, sync or async}It aliases the internal specifier @comvi/next/config to your module, under webpack and under
Turbopack, keeping your own webpack function and every alias already there; the server helpers and
<ComviProvider> read the i18n export through that alias. config names the module exporting i18n
(required) and routing (optional), resolved from the directory next runs in; in a monorepo pass an
absolute path, such as fileURLToPath(new URL('./src/i18n/config.ts', import.meta.url)). A missing
path throws while next.config loads, naming the resolved path. The module is evaluated in the server
build and in the client build, so it must be client-safe; a server-only loader belongs to the
manual setup. Test runners and serverExternalPackages never see the alias:
Tests.
Server helpers
Section titled “Server helpers”From @comvi/next/server, which imports server-only: a Client Component importing it fails the build.
function getI18n(options?: { locale?: string; ns?: string }): Promise<ServerI18n>function getLocale(): Promise<string>function loadMessages(locale: string, options?: { namespaces?: string[] }): Promise<MessagesMap>
interface ServerI18n { t: TranslationFunction; hasTranslation(key: string, options?: { ns?: string; locale?: string }): boolean; locale: string;}type MessagesMap = Record<string, Record<string, TranslationValue>> // @comvi/core's, re-exported by /server and /client| Helper | Meaning |
|---|---|
getI18n({ locale, ns? }) |
Binds t, hasTranslation and locale to the locale you pass; neither reads nor sets the request locale, so two calls in one request may name different locales |
getI18n() |
The same, with the locale from the request store (<ComviProvider> or setRequestLocale()) or the middleware header; throws [@comvi/next] Locale not set with neither |
getLocale() |
The request locale from the same two sources. The x-comvi-locale header counts only when its value is in routing.locales, whether routing comes from the config module or from createNextI18nFromInstance(factory, routing), or, with no routing, is a well-formed BCP 47 tag, singleton subtags included; any other value is ignored and routing.defaultLocale applies. With neither source, or an ignored header and no routing, it throws [@comvi/next] Unable to determine locale. |
loadMessages(locale, options?) |
Preloads and returns a map keyed "locale:namespace", for a manual client provider or a namespace getI18n() does not load; with no loader returns what is cached |
getI18n() loads one namespace (options.ns or the instance’s default) for that locale and
every locale in the fallbackLocale chain, then returns a synchronous t; another namespace
needs loadMessages() first. A locale routing.locales does not list still renders, from
the fallback chain, with one development warning per locale: notFound() stays the layout’s job.
generateMetadata, not-found.tsx and anything outside the <ComviProvider> tree must pass the
locale; the no-argument form needs an established request context.
<ComviProvider>
Section titled “<ComviProvider>”From @comvi/next/server, for the locale layout of an app with withComvi in next.config.
function ComviProvider(props: ComviProviderProps): Promise<JSX.Element>
interface ComviProviderProps { locale: string; namespaces?: string[]; // default: the instance's default namespace children: React.ReactNode;}locale is the [locale] param; validate it with hasLocale in your layout, with notFound(),
<html lang> and generateStaticParams(). The provider sets the request locale for the
no-argument helpers under it, loads namespaces (default: the instance’s default namespace) for
that locale and its fallback chain, and renders the library’s "use client" provider with that data
and the i18n and routing the client graph reads from your config module. A namespace absent
here is missing on the client until something loads it there. Without withComvi it throws.
Manual setup
Section titled “Manual setup”The pieces withComvi and <ComviProvider> stand in for, for an app whose config imports a server-only loader.
function setI18n(i18n: NextServerInstance): voidfunction createNextI18nFromInstance<D, C extends NextServerInstance<D>>(factory: () => C, options: CreateNextI18nFromInstanceOptions): { readonly i18n: C; readonly routing: Required<RoutingConfig> }function setRequestLocale(locale: string): void
type NextServerInstance<D = {}> = WrapperI18nInstance<D> & I18nLoaderApi| Helper | Meaning |
|---|---|
setI18n(i18n) |
Registers an instance you built as the one the server helpers read; repeating with the same instance is a no-op, a different one throws |
createNextI18nFromInstance(factory, routing) |
Calls factory() once, eagerly, and registers the result; the options carry routing only; no use pipe |
setRequestLocale(locale) |
Stores the locale in React’s request cache() for the no-argument helpers; needed at the top of a layout or page that renders statically and calls getI18n() without a locale |
A registered instance wins over the withComvi alias; two different objects get one development
warning naming both sources, and the check runs before createNextI18nFromInstance() calls its factory.
<I18nProvider>
Section titled “<I18nProvider>”From @comvi/next/client. Extends the React provider, minus ssrLocale and targetLocale, both set from locale.
<ComviProvider> renders it for you; render it yourself from a "use client" file that imports the
instance, because a class instance does not cross the RSC boundary.
| Prop | Type | Meaning |
|---|---|---|
locale |
string |
Required; forwarded to React’s ssrLocale when it is in routing.locales, otherwise an error is reported and the instance’s locale renders |
messages |
MessagesMap |
What loadMessages() returned; seeds the client cache |
routing |
RoutingConfig |
Read by <Link> and useLocalizedRouter(); without it they prefix unconditionally |
The layout calls setRequestLocale(locale), then loadMessages(locale, { namespaces }), and
passes the map as messages; it holds the locale and its whole fallbackLocale chain, so a key
rendered from a fallback hydrates to the same text. The files are on Server and client APIs.
createMiddleware(config)
Section titled “createMiddleware(config)”function createMiddleware(config: MiddlewareConfig): (request: NextRequest) => NextResponse
interface MiddlewareConfig extends RoutingConfig { detectLocale?: (request: NextRequest) => string | undefined; localeDetection?: false | LocaleDetectionConfig;}/_next*, /api and /api/* are skipped. Order: detectLocale (a value outside locales becomes defaultLocale), the URL path,
localeDetection.order, defaultLocale. Then it applies localePrefix and pathnames, sets the
x-comvi-locale header and, on a document load, writes the cookie when its value differs; a switch
to the default locale under as-needed is recorded too. localeDetection: false leaves the path
alone in charge and localeCookie: false persists nothing. @comvi/nuxt takes the same two
options with the same values and defaults; only the default cookie name differs.
LocaleCookieOptions
Section titled “LocaleCookieOptions”| Option | Type | Default | Meaning |
|---|---|---|---|
name |
string |
'NEXT_LOCALE' |
Cookie name |
maxAge |
number |
31536000 |
Lifetime in seconds, one year |
secure |
boolean |
true, off in development |
Secure flag; NODE_ENV=development drops it so localhost over HTTP keeps the cookie |
sameSite |
'lax' | 'strict' | 'none' |
'lax' |
SameSite attribute |
domain |
string |
none, the current domain only | Send the cookie to this domain and its subdomains |
LocaleDetectionConfig
Section titled “LocaleDetectionConfig”| Option | Type | Default | Meaning |
|---|---|---|---|
order |
Array<'query' | 'cookie' | 'header' | 'accept-language'> |
['query', 'cookie', 'accept-language'] |
Sources after the URL path; query and header do nothing until queryParam and headerName are set |
queryParam |
string |
none | Query parameter read for 'query' ('lang' reads ?lang=de); values outside locales are ignored |
headerName |
string |
none | Request header read for 'header', matched case-insensitively |
resolveAcceptLanguage |
(acceptLanguage, locales, defaultLocale) => string | undefined |
built-in matcher | Replaces the Accept-Language matcher |
import { defineRouting } from '@comvi/next/routing';
export const routing = defineRouting({ locales: ['en', 'de'], defaultLocale: 'en' });import { createMiddleware } from '@comvi/next/middleware';import { routing } from './i18n/routing';
export default createMiddleware({ ...routing, localeCookie: { name: 'locale', sameSite: 'strict' }, localeDetection: { order: ['query', 'cookie', 'accept-language'], queryParam: 'lang' },});Navigation
Section titled “Navigation”From @comvi/next/navigation; all read routing from the provider.
const Link: React.ForwardRefExoticComponent<LocalizedLinkProps & React.RefAttributes<HTMLAnchorElement>> // Next's Link props plus locale?: stringfunction usePathname(): stringfunction useLocalizedRouter(): { push: (href: string, locale?: string) => void; replace: (href: string, locale?: string) => void; back: () => void; forward: () => void; refresh: () => void; prefetch: (href: string, locale?: string) => void;}<Link> applies localePrefix to href for the current locale, or for its locale prop, which also records that locale in the cookie on click. usePathname() strips the locale prefix and never adds one.
Routing
Section titled “Routing”From @comvi/next/routing, server-safe.
function defineRouting<T extends string>(config: RoutingConfig<T>): Required<RoutingConfig<T>>function hasLocale<T extends string>(locales: readonly T[], locale: string): locale is Tfunction createGetPathname<T extends string>(config: Required<RoutingConfig<T>>): (options: { locale: string; href: string }) => string
interface RoutingConfig<T extends string = string> { locales: readonly T[]; defaultLocale: T; localePrefix?: 'always' | 'as-needed' | 'never'; // default 'as-needed' localeCookie?: false | string | LocaleCookieOptions; // default 'NEXT_LOCALE' pathnames?: Record<string, Partial<Record<T, string>>>;}Locale matching is segment-based and a trailing slash survives. createGetPathname applies localePrefix and pathnames, for sitemaps and hreflang.
Read next
Section titled “Read next”- Next.js: the App Router walkthrough:
withComvi, the config module, the layout provider, middleware. @comvi/react: every hook and component this package re-exports.- Render on the server: hydration and the server/client split.