See Agility CMS in action. Watch a product demo
Internationalization
This guide covers implementing internationalization (i18n) with Agility CMS, including locale configuration, routing, and content management.
i18n Overview
Agility CMS supports multiple locales, allowing you to manage content in different languages and regions.
Locale Configuration
Configure locales in your environment:
AGILITY_LOCALES=en-us,fr-ca,es-mx
Default Locale
The first locale in the list is the default locale:
const locales = process.env.AGILITY_LOCALES?.split(',') || ['en-us']
const defaultLocale = locales[0]
Locale Routing
URL Patterns
Default Locale (no prefix):
/ # English (default)
/blog
/about-us
Other Locales (with prefix):
/fr # French
/fr/blog
/fr/about-us
Middleware Routing
Handle locale routing in middleware:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname
const locale = getLocaleFromPathname(pathname)
// Rewrite to locale-prefixed path
const newUrl = new URL(`/${locale}${pathname}`, request.url)
return NextResponse.rewrite(newUrl)
}
Content Fetching
Specify Locale
Always specify locale when fetching content:
const { fields } = await getContentItem<IPost>({
contentID: 204,
languageCode: "en-us" // or "fr-ca", "es-mx"
})
Locale Detection
Detect locale from request:
import { headers } from 'next/headers'
export default async function Page({ params }) {
const locale = params.locale || 'en-us'
const { fields } = await getContentItem<IPost>({
contentID: 204,
languageCode: locale
})
return <div>{fields.heading}</div>
}
Locale-Specific Content
Content Per Locale
Each locale has its own content instances:
- English (en-us): Post with ID 204
- French (fr-ca): Post with ID 205 (same content, different locale)
- Spanish (es-mx): Post with ID 206
Fallback Strategy
Implement fallback to default locale:
async function getContentWithFallback<T>(
contentID: number,
locale: string,
defaultLocale: string = 'en-us'
) {
try {
return await getContentItem<T>({
contentID,
languageCode: locale
})
} catch (error) {
if (locale !== defaultLocale) {
// Fallback to default locale
return await getContentItem<T>({
contentID,
languageCode: defaultLocale
})
}
throw error
}
}
Sitemap Per Locale
Fetch Sitemap
Fetch sitemap for specific locale:
const sitemap = await getSitemapFlat({
channelName: "website",
languageCode: "en-us"
})
Generate Static Params
Generate static params for all locales:
export async function generateStaticParams() {
const locales = process.env.AGILITY_LOCALES?.split(',') || ['en-us']
const params = []
for (const locale of locales) {
const sitemap = await getSitemapFlat({
channelName: "website",
languageCode: locale
})
for (const page of sitemap) {
params.push({
locale,
slug: page.path.split('/').filter(Boolean),
})
}
}
return params
}
Locale Utilities
Locale Helpers
Create utility functions:
// lib/i18n/utils.ts
export function isValidLocale(locale: string, locales: string[]): boolean {
return locales.includes(locale)
}
export function getLocaleFromPathname(pathname: string): string {
const segments = pathname.split('/').filter(Boolean)
const firstSegment = segments[0]
const locales = process.env.AGILITY_LOCALES?.split(',') || ['en-us']
return isValidLocale(firstSegment, locales) ? firstSegment : locales[0]
}
export function removeLocaleFromPathname(pathname: string): string {
const locale = getLocaleFromPathname(pathname)
return pathname.replace(`/${locale}`, '') || '/'
}
Best Practices
- Always Specify Locale: Never fetch content without locale
- Implement Fallbacks: Fallback to default locale if content missing
- Consistent Routing: Use consistent URL patterns
- Locale Detection: Detect locale from URL or headers
- Content Management: Manage content per locale separately
Next: Best Practices - Development best practices