comvi
@comvi/nuxt

i18n with type-checked keys, ICU plurals and a <T> component for markup inside translations.

Comvi

@comvi/nuxt

Nuxt module for Comvi i18n: auto-imports, SSR, and locale-prefixed routes.

npmLicense: MITOpen in StackBlitz


@comvi/nuxt is the Comvi i18n module for Nuxt 3 and 4: it auto-imports the composables and <T>, clones your pages into locale-prefixed routes, builds one i18n instance per request on the server and hydrates the client from what the server loaded. Use it instead of @comvi/vue on Nuxt; it brings @comvi/core and the Vue bindings with it.

Install

npm install @comvi/nuxt
# Peer: nuxt ^3.0.0 || ^4.0.0

Setup

Add the module and declare the locales, in nuxt.config.ts:

import { defineNuxtConfig } from "nuxt/config";

export default defineNuxtConfig({
  modules: ["@comvi/nuxt"],
  comvi: {
    defaultLocale: "en",
    locales: ["en", "uk"],
    messages: {
      en: { greeting: "Hello, {name}!" },
      uk: { greeting: "Привіт, {name}!" },
    },
  },
});

Nuxt renders the pages through an outlet, in app/app.vue:

<template>
  <NuxtPage />
</template>

comvi.messages is the inline catalog, nested or flat, and must stay JSON-serializable. The module writes <html lang dir> from the active locale on the server and on every switch.

Use in components

In a page, app/pages/index.vue:

<script setup lang="ts">
const { t, locale } = useI18n();
</script>

<template>
  <h1>{{ t("greeting", { name: "Alice" }) }}</h1>
  <NuxtLinkLocale to="/" :locale="locale === 'en' ? 'uk' : 'en'">Switch</NuxtLinkLocale>
</template>

useI18n() is auto-imported. It returns the @comvi/vue composable plus locales and defaultLocale from runtime config. setLocale() loads the new catalog and advances the locale state the route middleware watches. $t() works in any template without calling useI18n(). formatNumber, formatDate, formatCurrency and formatRelativeTime are auto-imported too, and a plain module imports them from @comvi/nuxt/i18n. They take the core instance first: formatRelativeTime(useNuxtApp().$i18n.core, -2, 'hour').

Load from Comvi

npm install @comvi/plugin-fetch-loader

Compose the loader in comvi.i18n.ts:

import { createI18n, defineComviI18n } from "@comvi/nuxt/i18n";
import { cdnLoader } from "@comvi/plugin-fetch-loader";

export default defineComviI18n((options) =>
  createI18n(options).use(cdnLoader({ cdnUrl: "https://cdn.comvi.io/<project-token>" })),
);

Add this file next to nuxt.config.ts. The module picks up ./comvi.i18n.* by convention and logs the path; comvi.i18nModule points at a file elsewhere. The default export receives the resolved comvi options and must return a new instance from createI18n on every call; in development the module throws when it gets the same instance twice. The CDN URL is on Connect to Comvi. If your keys use ICU ({count, plural, …}), set comvi.icu: true or pass compiler: icuCompiler in the factory, as on Plurals and ICU; the platform and the compiler are independent.

How it works

On every request the module builds a fresh i18n instance in the default locale, runs comvi.setup.ts for it when the file exists, initializes it, and then the route middleware detects the locale from the URL path, the cookie and the Accept-Language header and switches to it before the page renders. The loaded catalog is serialized into the Nuxt payload, so the client hydrates with no second fetch and no flash. Without a comvi.i18n.ts file the module builds the base instance, with no loader, and with the ICU compiler only when comvi.icu is true; every installer that file needs (createI18n, loader, icuCompiler, plugins, devtools) comes from @comvi/nuxt/i18n. In a Nitro route the server auto-imports are useTranslation(event), loadMessages(event, locale), getRequestI18n(event, locale) and getServerRuntimeConfig(event); the app-side composables do not exist there. <T> is auto-imported: each tag a translator wrote becomes a named slot, or an entry in the components prop. Never render a translation with v-html.

Routing

localePrefixDefault localeOther locale
as-needed/about/de/about
always/en/about/de/about
never/about/about, locale from the cookie or Accept-Language instead

The route middleware detects the locale, persists it in the i18n_locale cookie and loads that locale's catalog before the page renders. Pair a switcher with useSwitchLocalePath() when the URL should change too. localeCookie takes a name, { name, maxAge, secure, sameSite, domain }, or false to persist nothing; localeDetection takes { order, queryParam, headerName, resolveAcceptLanguage }, or false to leave the URL path alone in charge. comvi.ns lists the namespaces to load besides defaultNs. useLocaleHead() adds canonical, hreflang and og:locale tags, and writes lang and dir too unless addLang or addDir is false; set comvi.head: false when you write those attributes yourself.

Contributing

Bug reports, docs fixes and pull requests are welcome. CONTRIBUTING.md explains the setup and what CI checks.

License

MIT © Comvi