🌏 As the internet becomes increasingly global, application internationalization has become an essential consideration. This guide will walk you through implementing internationalization in Next.js to help your application reach a global audience.
I. Core Concepts: Understanding Internationalization
1. Internationalization (i18n) vs. Localization (L10n)
Think of it like running a restaurant:
- 🌐 Internationalization (i18n): Designing a universal menu template that easily supports different languages
- 🎯 Localization (L10n): Adapting content for specific regions, such as:
- Arabic regions: Right-to-left menu layout
- Indian regions: Vegetarian indicators
- Japanese regions: Local currency format
2. Locale: Language Environment Identifiers
Locale naming convention: language[_territory][.codeset][@modifier]
Examples:
- 🇺🇸
en_US
: American English - 🇬🇧
en_GB
: British English - 🇨🇳
zh_CN
: Simplified Chinese (Mainland China)
II. Practical Implementation in Next.js
1. Route Design
Starting with a concrete example:
/dashboard -> Default language
/en/dashboard -> English version
/fr/dashboard -> French version
2. Implementation Steps
Step 1: Language Detection
// middleware.js
import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'
const LOCALES = ['en-US', 'zh-CN', 'fr-FR']
const DEFAULT_LOCALE = 'en-US'
function getLocale(request) {
const headers = {
'accept-language': request.headers.get('accept-language') || ''
}
const languages = new Negotiator({ headers }).languages()
return match(languages, LOCALES, DEFAULT_LOCALE)
}
Step 2: Route Middleware
// middleware.js
export function middleware(request) {
const { pathname } = request.nextUrl
// Check if path already includes locale prefix
const hasLocale = LOCALES.some(
locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
)
if (hasLocale) return
// Get user's locale
const locale = getLocale(request)
// Redirect to localized route
request.nextUrl.pathname = `/${locale}${pathname}`
return Response.redirect(request.nextUrl)
}
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}
Step 3: Translation Management
Create language dictionaries:
// dictionaries/en-US.json
{
"common": {
"welcome": "Welcome",
"login": "Log In",
"signup": "Sign Up"
},
"home": {
"title": "Welcome to our platform",
"description": "Start your journey with us"
}
}
// dictionaries/zh-CN.json
{
"common": {
"welcome": "欢迎",
"login": "登录",
"signup": "注册"
},
"home": {
"title": "欢迎使用我们的平台",
"description": "开启您的精彩旅程"
}
}
Step 4: Translation Loader
// lib/dictionary.js
import 'server-only'
const dictionaries = {
'en-US': () => import('../dictionaries/en-US.json').then(m => m.default),
'zh-CN': () => import('../dictionaries/zh-CN.json').then(m => m.default),
'fr-FR': () => import('../dictionaries/fr-FR.json').then(m => m.default),
}
export async function getDictionary(locale) {
return dictionaries[locale]()
}
Step 5: Page Implementation
// app/[lang]/page.js
import { getDictionary } from '@/lib/dictionary'
export default async function HomePage({ params: { lang } }) {
const dict = await getDictionary(lang)
return (
<main>
<h1>{dict.home.title}</h1>
<p>{dict.home.description}</p>
<div className="buttons">
<button>{dict.common.login}</button>
<button>{dict.common.signup}</button>
</div>
</main>
)
}
3. Static Generation Optimization
// app/[lang]/layout.js
export async function generateStaticParams() {
return [
{ lang: 'en-US' },
{ lang: 'zh-CN' },
{ lang: 'fr-FR' }
]
}
export default function RootLayout({ children, params }) {
return (
<html lang={params.lang}>
<body>{children}</body>
</html>
)
}
III. Best Practices and Considerations
1. Performance Optimization
- ⚡️ Leverage Next.js static generation
- 🚀 Load translations on demand
- 💾 Implement translation caching
2. SEO Optimization
// app/[lang]/layout.js
export default function Layout({ children, params: { lang } }) {
return (
<html lang={lang}>
<head>
<link
rel="alternate"
hreflang="en-US"
href="https://example.com/en-US"
/>
<link
rel="alternate"
hreflang="zh-CN"
href="https://example.com/zh-CN"
/>
</head>
<body>{children}</body>
</html>
)
}
3. Common Pitfalls
- Client Component Handling:
'use client'
import { useParams } from 'next/navigation'
export default function ClientComponent() {
const { lang } = useParams()
// Access current language in client components
}
- Dynamic Content Handling:
function formatPrice(price, locale) {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: locale === 'zh-CN' ? 'CNY' : 'USD',
}).format(price)
}
- Date/Time Handling:
function formatDate(date, locale) {
return new Intl.DateTimeFormat(locale).format(date)
}
IV. Advanced Techniques
1. Language Switcher Implementation
'use client'
import { useRouter } from 'next/navigation'
export default function LanguageSwitcher({ currentLang }) {
const router = useRouter()
return (
<select
value={currentLang}
onChange={(e) => {
const newLang = e.target.value
const path = window.location.pathname
const newPath = path.replace(currentLang, newLang)
router.push(newPath)
}}
>
<option value="en-US">English</option>
<option value="zh-CN">中文</option>
<option value="fr-FR">Français</option>
</select>
)
}
2. Default Language Optimization
// middleware.js
export function middleware(request) {
const { pathname } = request.nextUrl
const locale = getLocale(request)
if (locale === DEFAULT_LOCALE) {
const newPathname = pathname.replace(`/${DEFAULT_LOCALE}`, '')
request.nextUrl.pathname = newPathname
return Response.redirect(request.nextUrl)
}
request.nextUrl.pathname = `/${locale}${pathname}`
return Response.redirect(request.nextUrl)
}
Conclusion
While implementing internationalization in Next.js requires careful configuration, with proper architectural design we can achieve:
- 🚀 Automatic language detection and route redirection
- 📦 Efficient translation resource management
- ⚡️ Optimal performance
- 🔍 Enhanced SEO performance
🔍 Recommended Tools:
- next-i18next: Full-featured i18n solution
- next-translate: Lightweight translation tool
- Crowdin: Professional translation management platform