Examples & Integrations

I18n module

Learn how to create multi-language websites using Nuxt Content with the @nuxtjs/i18n module.

Nuxt Content integrates with @nuxtjs/i18n to create multi-language websites. When both modules are configured together, you can organize content by language and automatically serve the correct content based on the user's locale.

Setup

Install the required module

terminal
npm install @nuxtjs/i18n

Configure your nuxt.config.ts

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxt/content', '@nuxtjs/i18n'],
  i18n: {
    locales: [
      { code: 'en', name: 'English', language: 'en-US', dir: 'ltr' },
      { code: 'fr', name: 'French', language: 'fr-FR' },
      { code: 'fa', name: 'Farsi', language: 'fa-IR', dir: 'rtl' },
    ],
    strategy: 'prefix_except_default',
    defaultLocale: 'en',
  }
})

Define collections for each language

Create separate collections for each language in your content.config.ts:

content.config.ts
const commonSchema = ...;

export default defineContentConfig({
  collections: {
    // English content collection
    content_en: defineCollection({
      type: 'page',
      source: {
        include: 'en/**',
        prefix: '',
      },
      schema: commonSchema,
    }),
    // French content collection
    content_fr: defineCollection({
      type: 'page',
      source: {
        include: 'fr/**',
        prefix: '',
      },
      schema: commonSchema,
    }),
    // Farsi content collection
    content_fa: defineCollection({
      type: 'page',
      source: {
        include: 'fa/**',
        prefix: '',
      },
      schema: commonSchema,
    }),
  },
})

Create dynamic pages

Create a catch-all page that fetches content based on the current locale:

pages/[...slug].vue
<script setup lang="ts">
import { withLeadingSlash } from 'ufo'
import type { Collections } from '@nuxt/content'

const route = useRoute()
const { locale, localeProperties } = useI18n()
const slug = computed(() => withLeadingSlash(String(route.params.slug)))

const { data: page } = await useAsyncData('page-' + slug.value, async () => {
  // Build collection name based on current locale
  const collection = ('content_' + locale.value) as keyof Collections
  const content = await queryCollection(collection).path(slug.value).first()

  // Optional: fallback to default locale if content is missing
  if (!content && locale.value !== 'en') {
    return await queryCollection('content_en').path(slug.value).first()
  }

  return content
}, {
  watch: [locale], // Refetch when locale changes
})
</script>

<template>
  <ContentRenderer v-if="page" :value="page" />
  <div v-else>
    <h1>Page not found</h1>
    <p>This page doesn't exist in {{ locale }} language.</p>
  </div>
</template>

That's it! 🚀 Your multi-language content site is ready.

Content Structure

Organize your content files in language-specific folders to match your collections:

content/
  en/
    index.md
    about.md
    blog/
      post-1.md
  fr/
    index.md
    about.md
    blog/
      post-1.md
  fa/
    index.md
    about.md

Each language folder should contain the same structure to ensure content parity across locales.

Fallback Strategy

You can implement a fallback strategy to show content from the default locale when content is missing in the current locale:

pages/[...slug].vue
const { data: page } = await useAsyncData('page-' + slug.value, async () => {
  const collection = ('content_' + locale.value) as keyof Collections
  let content = await queryCollection(collection).path(slug.value).first()

  // Fallback to default locale if content is missing
  if (!content && locale.value !== 'en') {
    content = await queryCollection('content_en').path(slug.value).first()
  }

  return content
})
Make sure to handle missing content gracefully and provide clear feedback to users when content is not available in their preferred language.

Complete Examples

You can see a complete working example: