Skip to content
Comvi

@comvi/nuxt

View .md

The Nuxt module (Nuxt ^3 || ^4) that wires @comvi/vue into an app: auto-imported composables, locale routing, SSR loading and a <T> component. The composables have their own reference.

  • @comvi/nuxt: the module (default export); types NuxtI18nOptions, ModuleOptions, LocaleObject, LocalePrefixMode, LocaleCookieConfig, LocaleCookieOptions, LocaleDetectionConfig, LocaleDetectionSource, ResolveAcceptLanguage, ResolvedLocaleCookie, ResolvedLocaleDetection, NuxtInstanceFactory, NuxtInstanceFactoryOptions, NuxtServerInstance, NuxtServerLoaderInstance, NuxtI18nSetup, NuxtI18nSetupContext, NuxtI18nSetupNuxtApp, NuxtI18nSetupEvent, NuxtI18nSetupRuntimeConfig, NuxtDefaultTranslationParams, RegisteredNuxtInstance, ComviNuxtInstanceRegistry, ResolvedNuxtInstance, CheckedNuxtI18nSetup, ResolvedRoutingConfig, NuxtI18nRuntimeConfig, NuxtI18nPrivateRuntimeConfig, and I18n, MessagesMap, I18nLoaderApi, LoaderFn, TranslationKeys, TranslationParams, TranslationResult from core.
  • @comvi/nuxt/i18n: defineComviI18n, createI18n, icuCompiler, loader, attachLoader, plugins, definePlugin, devtools; type NuxtInstanceFactory.
  • @comvi/nuxt/setup: defineComviSetup.
  • Auto-imported in server/: useTranslation, loadMessages, getRequestI18n, getServerRuntimeConfig; their types UseTranslationOptions, UseTranslationResult, ServerI18n, ServerTranslationFunction, LoadMessagesOptions, MessagesMap, ComviServerRuntimeConfig; for an explicit annotation import from the declaring file, @comvi/nuxt/runtime/server/utils/useTranslation, …/utils/loadMessages or …/utils/runtime-config (the directory is not an export).
  • Components: <T>, <NuxtLinkLocale>; template global $t.
Option Type Default Meaning
locales (string | LocaleObject)[] [] Supported locales; LocaleObject is { code, name?, dir?, iso? }. An empty list warns at build time
defaultLocale string 'en' Locale when none is detected
localePrefix 'always' | 'as-needed' | 'never' 'as-needed' URL prefix mode: as-needed prefixes every locale but the default, always prefixes all, never none
messages I18nOptions['messages'] none Inline catalogs, forwarded through public runtime config into every i18n instance
head boolean true Write <html lang> and <html dir> from the active locale, on the server and on every client switch; lang is the locale object’s iso or its code, dir its declared dir or the language’s direction. false leaves <html> to you
apiKey string none Forwarded as i18n.apiKey; set it through private runtimeConfig.comvi.apiKey (NUXT_COMVI_API_KEY)
defaultNs string 'default' Default namespace
ns string[] none Namespaces init() loads, core’s ns; omitted, only defaultNs loads. Forwarded into every constructed instance and into the factory’s options
fallbackLocale string | string[] defaultLocale Fallback chain
defaultParams Record<string, string | number | boolean> none JSON-serializable interpolation defaults; locale, ns, fallback, markers are rejected
icu boolean false Use icuCompiler. With a comvi.i18n.ts file it arrives as compiler, the last key of the factory’s options
i18nModule string ./comvi.i18n.* Path to the factory module, when the file is not at the convention path. Unset, the module looks for ./comvi.i18n.* under srcDir, then rootDir, and logs the path it found; a set path that does not resolve fails the build. Next’s config option names a module that exports an instance, this one names a module that exports a factory
setup string ./comvi.setup.* Path to the setup hook file
localeCookie false | string | LocaleCookieOptions 'i18n_locale' The locale cookie: a name, its attributes, or false to persist nothing
localeDetection false | LocaleDetectionConfig { order: ['query', 'cookie', 'accept-language'] } Where the locale comes from after the URL path; false leaves the path alone in charge
basicHtmlTags string[] none Tags rendered without a handler; forwarded as tagInterpolation to the app plugin and to the Nitro per-request instance alike

Every option lives under the comvi key in nuxt.config.ts. localeCookie and localeDetection are the two options @comvi/next’s createMiddleware takes, with the same values and defaults; only the default cookie name differs. A key inside either that the schema does not know fails the build, naming the key and what to write instead, as in [@comvi/nuxt] `comvi.localeDetection.cookieName` is not an option: use `comvi.localeCookie.name`.; the same check runs over runtimeConfig.public.comvi, with that prefix in the message.

Option Type Default Meaning
name string 'i18n_locale' Cookie name
maxAge number 31536000 Lifetime in seconds, one year
secure boolean true, off in development Secure flag; nuxt dev 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
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; header and accept-language are read on the server only; an unknown entry is refused at build time, and the middleware ignores one that reaches it
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; copied into the build as source, so it must be self-contained, with no import or variable from nuxt.config

The route middleware skips /_nuxt*, /api and /api/* and resolves the locale in this order: the path prefix, then localeDetection.order, then defaultLocale. Under as-needed a prefixless non-root path is the default locale’s URL, decided after any query source that leads the order and before the rest; / is too only on a navigation from the same page under a locale prefix (/de to /), otherwise localeDetection.order decides it and a non-default locale redirects to its prefixed root.

nuxt.config.ts
export default defineNuxtConfig({
modules: ['@comvi/nuxt'],
comvi: {
defaultLocale: 'en',
locales: ['en', 'de'],
localeCookie: { name: 'locale', sameSite: 'strict' },
localeDetection: { order: ['query', 'cookie', 'accept-language'], queryParam: 'lang' },
},
});
Mode Default locale URL Other locale URL
as-needed /about /de/about
always /en/about /de/about
never /about /about

Routes are cloned at build time as /{locale}{path} named {name}___{locale}; under always the unprefixed originals are removed and a page with a [locale] param is dropped; under never pages are left as they are.

nuxt.config.ts
export default defineNuxtConfig({
modules: ['@comvi/nuxt'],
comvi: {
defaultLocale: 'en',
locales: [
{ code: 'en', name: 'English' },
{ code: 'de', name: 'Deutsch' },
],
messages: { en: { nav: { home: 'Home' } }, de: { nav: { home: 'Startseite' } } },
},
});

With no comvi.i18n.ts file the module builds a bare instance, plus compiler: icuCompiler when icu is true. Every other capability comes from that file.

type NuxtInstanceFactory<C = WrapperI18nInstance, D = {}> = (options: NuxtInstanceFactoryOptions<D>) => C
type NuxtInstanceFactoryOptions<D> = {
locale: string;
fallbackLocale?: string | string[];
defaultNs?: string;
ns?: string[]; // comvi.ns verbatim
devMode?: boolean;
apiKey?: string;
messages?: I18nOptions['messages'];
compiler?: MessageCompiler; // icuCompiler when comvi.icu is true, last key
tagInterpolation?: { basicHtmlTags?: string[] }; // both instances
ssrLocale?: string; // client plugin call only
} & (keyof D extends never ? {} : { defaultParams: D })

Default-export a factory that returns a new core instance from createI18n on every call, never a VueI18n. The module calls it once for the client and once per server request; in development the module’s generated code throws when the factory returns an instance it already returned. The Nitro per-request instance receives locale, fallbackLocale, defaultNs, ns, devMode, apiKey, messages, tagInterpolation and defaultParams; only ssrLocale is client-only.

function defineComviI18n<C extends WrapperI18nInstance<D>, D = {}>(
factory: (options: NuxtInstanceFactoryOptions<D>) => C,
): (options: NuxtInstanceFactoryOptions<D>) => C
comvi.i18n.ts
import { createI18n, defineComviI18n, loader } from '@comvi/nuxt/i18n';
export default defineComviI18n((options) =>
createI18n(options).use(
loader({
en: () => import('./locales/en.json'),
de: () => import('./locales/de.json'),
}),
),
);

From @comvi/nuxt/i18n. It types options and returns the factory unchanged, so the composed instance’s type still reaches the setup hook. A factory written as (options) => … with satisfies NuxtInstanceFactory, or a plain function typed with NuxtInstanceFactoryOptions from @comvi/nuxt, is the same thing spelled longer.

function useTranslation(event: H3Event, options?: UseTranslationOptions): Promise<UseTranslationResult>
function loadMessages(event: H3Event, locale: string, options?: { namespaces?: string[] }): Promise<MessagesMap>
function getRequestI18n(event: H3Event, locale: string): Promise<NuxtServerInstance>
function getServerRuntimeConfig(event?: H3Event): ComviServerRuntimeConfig
interface UseTranslationOptions {
ns?: string; // scopes t(); an empty string counts as not given
locale?: string; // overrides detection
}
interface UseTranslationResult {
t: ServerTranslationFunction;
locale: string;
hasTranslation: (key: string, opts?: { locale?: string; ns?: string }) => boolean;
}
type ServerI18n = UseTranslationResult // the name @comvi/next uses for the same shape
type NuxtServerInstance<D = {}> = WrapperI18nInstance<D>
type NuxtServerLoaderInstance<D = {}> = WrapperI18nInstance<D> & I18nLoaderApi
type MessagesMap = Record<string, Record<string, TranslationValue>> // @comvi/core's, re-exported by @comvi/nuxt
Utility Meaning
useTranslation(event, options?) Request-scoped t, locale and hasTranslation. Locale precedence: options.locale, a locale prefix on the request path, then localeDetection.order (the query parameter, the cookie, a configured header, Accept-Language), then defaultLocale. A prefixless path is not read as the default locale here, because an API route carries no prefix to omit
loadMessages(event, locale, options?) Loads namespaces (default [defaultNs]) and returns a map keyed "locale:namespace"; warns once per instance when it has no loader and nothing is cached
getRequestI18n(event, locale) The request-scoped instance
getServerRuntimeConfig(event?) Public and private Comvi runtime config together

loadMessages() and useTranslation() load the locale you pass and every locale in the instance’s fallbackLocale chain, for each namespace asked for, the same contract as the @comvi/next helpers. A pair that fails to load is reported through loadError and onError, attempted once per request, and left out of the map; the call still resolves.

One instance is built per request and per locale and destroyed after the response, so calling useTranslation() or loadMessages() afterwards throws.

server/api/hello.ts
export default defineEventHandler(async (event) => {
const { t, locale } = await useTranslation(event, { ns: 'api' });
return { message: t('hello'), locale };
});
type NuxtI18nSetup<C extends WrapperI18nInstance = ResolvedNuxtInstance> = (context: NuxtI18nSetupContext<C>) => void | Promise<void>
interface NuxtI18nSetupContext<C> {
i18n: VueI18n<{}, C> | C; // VueI18n in the app plugin, the core instance in server utilities
core: C; // the core instance on both runtimes
runtime: 'client' | 'server';
nuxtApp?: NuxtI18nSetupNuxtApp;
event?: NuxtI18nSetupEvent;
runtimeConfig?: NuxtI18nSetupRuntimeConfig;
}
function defineComviSetup(setup: NuxtI18nSetup): NuxtI18nSetup

comvi.setup default-exports a hook that runs before init() on the app plugin and on each request instance. ResolvedNuxtInstance is read from the factory’s return type at build time, so a hook with no type argument compiles only against the capabilities the factory composed.

comvi.setup.ts
import { defineComviSetup } from '@comvi/nuxt/setup';
export default defineComviSetup(({ core, runtimeConfig }) => {
core.setFallbackLocale(runtimeConfig?.public?.comvi?.fallbackLocale ?? 'en');
});

<T> is @comvi/vue’s <T>, auto-imported. <NuxtLinkLocale> takes every <NuxtLink> prop plus:

Prop Type Default Meaning
to string | Record<string, unknown> required Prefixed through useLocalePath(); external, mailto:, query-only and hash-only targets pass through
locale string current locale Locale for this link

$t(key, params?), $tRaw and $i18n are available in every template.

interface NuxtI18nRuntimeConfig { // useRuntimeConfig().public.comvi
comvi: {
locales: string[];
localeObjects: Record<string, LocaleObject>;
defaultLocale: string;
localePrefix: LocalePrefixMode;
head?: boolean;
messages?: I18nOptions['messages'];
defaultNs: string;
ns?: string[];
fallbackLocale: string | string[];
defaultParams?: NuxtDefaultTranslationParams;
basicHtmlTags?: string[];
localeCookie: ResolvedLocaleCookie | false; // every default applied
localeDetection: ResolvedLocaleDetection | false; // without resolveAcceptLanguage, which travels as source
};
}
interface NuxtI18nPrivateRuntimeConfig { comvi: { apiKey?: string } } // server-only
interface ResolvedRoutingConfig { // useAppConfig().comvi.routing
locales: readonly string[];
localeObjects: Record<string, LocaleObject>;
defaultLocale: string;
localePrefix: LocalePrefixMode;
cookieName: string | false; // false when localeCookie is false
}
  • @comvi/nuxt composables: useI18n, the capability pair, the routing composables, useLocaleHead and useRouteConfig.
  • Nuxt: the guide: the module, comvi.i18n.ts and the worked setups.
  • @comvi/vue: the VueI18n class the module wires in.