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. Create a setup file

    Create comvi.setup.ts at the project root to register plugins. The module auto-discovers this file:

    comvi.setup.ts
    import { defineComviSetup } from '@comvi/nuxt/setup';
    import { FetchLoader } from '@comvi/plugin-fetch-loader';
    export default defineComviSetup(({ i18n, runtimeConfig }) => {
    i18n.use(FetchLoader({
    cdnUrl: runtimeConfig?.public?.comvi?.cdnUrl,
    }));
    });
  3. 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). See security note below.
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). Used in hreflang tags
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
isLoadingReadonly<Ref<boolean>>Whether translations are being loaded
dirComputedRef<"ltr" | "rtl">Text direction for the current locale. Useful for RTL layout
localesreadonly string[]Configured locale code list
defaultLocalestringConfigured default locale

See the Nuxt API reference for the full return type including all methods.

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>

Returns a function that resolves a full route object for a given path and locale. Use this when you need the complete RouteLocationResolved object rather than just a path string:

<script setup lang="ts">
const localeRoute = useLocaleRoute();
function goToAbout() {
const route = localeRoute('/about', 'de');
if (route) {
navigateTo(route.fullPath);
}
}
</script>

Automatically sets the <html lang> and dir attributes, adds a canonical URL, alternate hreflang links for every configured locale (using ISO codes when set), and Open Graph locale meta tags. The cleanest way to handle all i18n SEO in one call:

pages/index.vue
<script setup lang="ts">
useLocaleHead({
baseUrl: 'https://yoursite.com',
});
</script>

All outputs are enabled by default and can be selectively disabled:

<script setup lang="ts">
useLocaleHead({
baseUrl: 'https://yoursite.com',
addOgLocale: true, // og:locale + og:locale:alternate
addAlternateLinks: true, // hreflang links
addCanonical: true, // canonical <link>
addDir: true, // html dir attribute
addLang: true, // html lang attribute
});
</script>

Returns resolved routing configuration and helpers. Particularly useful for sitemap generation:

server/routes/sitemap.xml.ts
export default defineEventHandler(() => {
const { locales, getPathname } = useRouteConfig();
const pages = ['/', '/about', '/contact'];
const urls = pages.flatMap((page) =>
locales.map((locale) => ({
loc: `https://example.com${getPathname({ locale, href: page })}`,
}))
);
});

See the Nuxt API reference for the full return type.

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/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 easiest way to handle all i18n SEO is useLocaleHead(). It automatically manages:

  • <html lang> and dir attributes
  • Canonical URL
  • hreflang alternate links for every locale (using iso codes when configured on locale objects)
  • og:locale and og:locale:alternate Open Graph tags
pages/index.vue
<script setup lang="ts">
useLocaleHead({ baseUrl: 'https://yoursite.com' });
</script>

If you need to control individual tags, build them manually using useSwitchLocalePath and useHead:

pages/index.vue
<script setup lang="ts">
const { locale, locales } = useI18n();
const switchLocalePath = useSwitchLocalePath();
useHead({
htmlAttrs: { lang: locale.value },
link: locales.value.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.