# @comvi/nuxt

API reference for @comvi/nuxt: module options, the comvi.i18n.ts factory and @comvi/nuxt/i18n, the server utilities, the setup hook, the components and the runtime config.

Canonical: https://comvi.io/docs/i18n/api/nuxt

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

## Exports

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

## Module options

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

### `LocaleCookieOptions`

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

### `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; `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`

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

```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' } } },
},
});
```

## Composing the i18n instance

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.

### The `comvi.i18n.ts` factory

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

### `defineComviI18n(factory)`

```ts
function defineComviI18n<C extends WrapperI18nInstance<D>, D = {}>(
factory: (options: NuxtInstanceFactoryOptions<D>) => C,
): (options: NuxtInstanceFactoryOptions<D>) => C
```

`comvi.i18n.ts`

```ts

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.

## Server utilities

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

### `useTranslation(event, options?)`

`server/api/hello.ts`

```ts
export default defineEventHandler(async (event) => {
const { t, locale } = await useTranslation(event, { ns: 'api' });
return { message: t('hello'), locale };
});
```

## Setup hook

```ts
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`

```ts

export default defineComviSetup(({ core, runtimeConfig }) => {
core.setFallbackLocale(runtimeConfig?.public?.comvi?.fallbackLocale ?? 'en');
});
```

## Components

`<T>` is [`@comvi/vue`'s `<T>`](/docs/i18n/api/vue#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.

## Runtime config

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

## Read next

- [`@comvi/nuxt` composables](/docs/i18n/api/nuxt-composables): `useI18n`, the capability pair, the routing composables, `useLocaleHead` and `useRouteConfig`.
- [Nuxt](/docs/i18n/nuxt): the guide: the module, `comvi.i18n.ts` and the worked setups.
- [`@comvi/vue`](/docs/i18n/api/vue): the `VueI18n` class the module wires in.
