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.
npm install @nuxtjs/i18n
nuxt.config.tsexport 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',
}
})
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 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 } = 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.
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.
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
})
You can see a complete working example: