Skip to content

Nuxt i18n Integration

Comvi’s Nuxt module gives you auto-imported composables, locale-aware routing, lazy-loaded translations, and full SSR support with zero client-side flash.

Terminal window
pnpm add @comvi/nuxt @comvi/plugin-fetch-loader
  1. Register the module

    Add @comvi/nuxt to your Nuxt config and provide your project settings:

    nuxt.config.ts
    export default defineNuxtConfig({
    modules: ['@comvi/nuxt'],
    comvi: {
    defaultLocale: 'en',
    locales: [
    { code: 'en', name: 'English' },
    { code: 'de', name: 'Deutsch' },
    { code: 'fr', name: 'Francais' },
    ],
    cdnUrl: 'https://cdn.comvi.io/your-distribution-id',
    },
    });
  2. Use in components

    All composables are auto-imported. No import statements needed:

    pages/index.vue
    <script setup lang="ts">
    const { t, locale } = useI18n();
    </script>
    <template>
    <h1>{{ t('home.title') }}</h1>
    <p>{{ t('home.description', { name: 'Comvi' }) }}</p>
    <p>Current locale: {{ locale }}</p>
    </template>

That’s it. The module handles plugin registration, translation loading, and SSR hydration automatically.

Configure the comvi key in nuxt.config.ts:

OptionTypeDefaultDescription
defaultLocalestringLocale used when no other locale is detected (required)
locales(string | LocaleObject)[]Available locales (required). Strings or objects (see format below)
localePrefix'always' | 'as-needed' | 'never''as-needed'Controls whether URLs include a locale prefix
cdnUrlstringCDN URL for loading translations in production
apiKeystringAPI key for authenticated requests (dev mode)
apiBaseUrlstringAPI base URL for loading translations
defaultNsstring'default'Default namespace for translations
fallbackLanguagestring | string[]Same as defaultLocaleLanguage(s) to use when a translation key is missing
detectBrowserLanguageobject | falseSee belowBrowser language detection settings

Each item in the locales array accepts these properties:

PropertyTypeRequiredDescription
codestringYesBCP 47 language code (e.g., en, de, ar)
namestringNoHuman-readable name (e.g., English, Deutsch)
dir'ltr' | 'rtl'NoText direction. Defaults to 'ltr'
isostringNoISO code for SEO (e.g., en-US)
nuxt.config.ts
comvi: {
locales: [
{ code: 'en', name: 'English' },
{ code: 'ar', name: 'Arabic', dir: 'rtl' },
{ code: 'ja', name: 'Japanese' },
],
}

Control how the module detects the user’s preferred language on first visit:

nuxt.config.ts
comvi: {
detectBrowserLanguage: {
useCookie: true,
cookieName: 'i18n_locale',
redirectOnFirstVisit: true,
},
}
PropertyTypeDefaultDescription
useCookiebooleantruePersist detected locale in a cookie
cookieNamestring'i18n_locale'Cookie name for persisted locale
cookieMaxAgenumber31536000Cookie max age in seconds (default: 1 year)
cookieSecurebooleantrueSet the Secure flag outside dev mode
redirectOnFirstVisitbooleantrueRedirect to detected locale on first visit
fallbackLocalestringdefaultLocaleLocale when detection fails or returns an unsupported locale

Set detectBrowserLanguage: false to disable automatic detection entirely.

All composables are auto-imported by the Nuxt module. You never need to write import statements for them.

The primary composable for translating strings and reading locale state.

<script setup lang="ts">
const { t, locale, isLoading } = useI18n();
</script>
<template>
<div v-if="isLoading">Loading translations...</div>
<div v-else>
<h1>{{ t('page.title') }}</h1>
<p>Current locale: {{ locale }}</p>
</div>
</template>

Returned values:

PropertyTypeDescription
t(key, params?) => stringTranslate a key with optional parameters
localeRef<string>Current language (reactive, writable)
setLocale(lang: string) => Promise<void>Switch language and wait for translations to load
isLoadingRef<boolean>Whether translations are being loaded
localesreadonly string[]Configured locale code list
defaultLocalestringConfigured default locale

Returns a function that generates locale-prefixed paths:

<script setup lang="ts">
const localePath = useLocalePath();
</script>
<template>
<NuxtLink :to="localePath('/about')">About</NuxtLink>
<!-- Renders /de/about when locale is "de" -->
</template>

Returns a function that generates the current page’s URL in a different locale:

<script setup lang="ts">
const switchLocalePath = useSwitchLocalePath();
</script>
<template>
<nav>
<NuxtLink :to="switchLocalePath('en')">English</NuxtLink>
<NuxtLink :to="switchLocalePath('de')">Deutsch</NuxtLink>
<NuxtLink :to="switchLocalePath('fr')">Francais</NuxtLink>
</nav>
</template>

In templates, you can also use the globally available $t() function without calling useI18n():

components/Footer.vue
<template>
<footer>
<p>{{ $t('footer.copyright', { year: 2025 }) }}</p>
</footer>
</template>

For translations containing HTML or Vue components, use <T> instead of t(). It renders safely without v-html:

<template>
<!-- Translation: "Read our <link>terms of service</link>" -->
<T i18nKey="legal.tos">
<template #link="{ children }">
<NuxtLink to="/terms">{{ children }}</NuxtLink>
</template>
</T>
</template>

The localePrefix option controls how locales appear in URLs:

ModeDefault Locale URLOther Locale URLDescription
as-needed/about/de/aboutDefault locale has no prefix
always/en/about/de/aboutAll locales get a prefix
never/about/aboutNo URL prefixes (locale set via cookie/header)
nuxt.config.ts
comvi: {
localePrefix: 'as-needed',
}

A locale-aware replacement for <NuxtLink> that automatically prefixes the href with the current locale:

<template>
<NuxtLinkLocale to="/about">About Us</NuxtLinkLocale>
<!-- Renders <a href="/de/about"> when locale is "de" -->
<NuxtLinkLocale to="/contact" locale="fr">Contact (FR)</NuxtLinkLocale>
<!-- Always renders <a href="/fr/contact"> -->
</template>

Build a complete language switcher with useSwitchLocalePath and the locale list:

components/LanguageSwitcher.vue
<script setup lang="ts">
const { locale, locales } = useI18n();
const switchLocalePath = useSwitchLocalePath();
</script>
<template>
<select
:value="locale"
@change="navigateTo(switchLocalePath(($event.target as HTMLSelectElement).value))"
>
<option
v-for="loc in locales"
:key="loc"
:value="loc"
>
{{ loc }}
</option>
</select>
</template>

Translations are loaded through the loaders you register on the i18n instance. Install @comvi/plugin-fetch-loader and register it in comvi.setup.ts to fetch from the Comvi CDN/API:

comvi.setup.ts
import { defineComviSetup } from '@comvi/nuxt/runtime/setup';
import { FetchLoader } from '@comvi/plugin-fetch-loader';
export default defineComviSetup(({ i18n, runtimeConfig }) => {
i18n.use(FetchLoader({
cdnUrl: runtimeConfig?.public?.comvi?.cdnUrl,
}));
});

The module creates the Nuxt/Vue i18n instance and runs your setup hook before initialization. Server utilities use the same setup hook for request-scoped i18n instances.

Translations can be fully rendered on the server when a loader is registered. The module handles the following automatically:

  • Detects the locale from the URL, cookie, or Accept-Language header
  • Runs your comvi.setup hook for server-side i18n instances
  • Hydrates the client with the loaded translations (no duplicate fetch)
  • Sets the lang and dir attributes on <html>

Register a loader in comvi.setup.ts so SSR helpers can load missing translations.

The module sets the <html lang> attribute automatically based on the active locale. For RTL locales, it also sets dir="rtl".

Add hreflang meta tags so search engines discover all language versions of each page:

pages/index.vue
<script setup lang="ts">
const { locale, locales } = useI18n();
const switchLocalePath = useSwitchLocalePath();
useHead({
htmlAttrs: { lang: locale.value },
link: locales.map((loc) => ({
rel: 'alternate',
hreflang: loc,
href: `https://yoursite.com${switchLocalePath(loc)}`,
})),
});
</script>

Enable type-safe translations with auto-generated types:

nuxt.config.ts
comvi: {
// Type generation picks up your default locale's keys
defaultLocale: 'en',
}
pages/index.vue
<script setup lang="ts">
const { t } = useI18n();
t('home.title'); // Autocompletes
t('nonexistent.key'); // Type error
</script>

See Type-Safe Translations for the full setup, including the CLI command to generate types.