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
npm install @nuxtjs/i18n
nuxt.config.ts
Configure your
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
:
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:
<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:
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
})
Complete Examples
You can see a complete working example: