Internationalization (i18n) support in Next.js is a key feature for building multilingual websites. Next.js 13+ App Router provides more flexible internationalization solutions.
App Router Internationalization Implementation
1. Basic Route Structure
javascript// app/[lang]/layout.js import { notFound } from 'next/navigation'; import { translations } from '../translations'; const locales = ['zh-CN', 'en', 'ja']; export function generateStaticParams() { return locales.map((locale) => ({ lang: locale })); } export default async function LocaleLayout({ children, params: { lang } }) { if (!locales.includes(lang)) { notFound(); } return ( <html lang={lang}> <body> <header> <nav> <Link href={`/${lang}`}>Home</Link> <Link href={`/${lang}/about`}>About</Link> <LanguageSwitcher currentLang={lang} /> </nav> </header> {children} </body> </html> ); } // app/[lang]/components/LanguageSwitcher.js 'use client'; import { usePathname } from 'next/navigation'; export default function LanguageSwitcher({ currentLang }) { const pathname = usePathname(); const locales = [ { code: 'zh-CN', name: '中文' }, { code: 'en', name: 'English' }, { code: 'ja', name: '日本語' } ]; return ( <select value={currentLang} onChange={(e) => { const newLang = e.target.value; const newPath = pathname.replace(`/${currentLang}`, `/${newLang}`); window.location.href = newPath; }} > {locales.map((locale) => ( <option key={locale.code} value={locale.code}> {locale.name} </option> ))} </select> ); }
2. Translation System
javascript// lib/i18n.js const translations = { 'zh-CN': { common: { home: '首页', about: '关于我们', contact: '联系我们', search: '搜索', loading: '加载中...', error: '出错了', submit: '提交', cancel: '取消' }, home: { title: '欢迎来到我们的网站', subtitle: '提供最好的服务', description: '我们致力于为您提供最优质的产品和服务' }, about: { title: '关于我们', mission: '我们的使命', vision: '我们的愿景', team: '我们的团队' } }, 'en': { common: { home: 'Home', about: 'About', contact: 'Contact', search: 'Search', loading: 'Loading...', error: 'Error', submit: 'Submit', cancel: 'Cancel' }, home: { title: 'Welcome to Our Website', subtitle: 'Providing the Best Services', description: 'We are committed to providing you with the highest quality products and services' }, about: { title: 'About Us', mission: 'Our Mission', vision: 'Our Vision', team: 'Our Team' } }, 'ja': { common: { home: 'ホーム', about: '私たちについて', contact: 'お問い合わせ', search: '検索', loading: '読み込み中...', error: 'エラー', submit: '送信', cancel: 'キャンセル' }, home: { title: '私たちのウェブサイトへようこそ', subtitle: '最高のサービスを提供', description: '最高品質の製品とサービスを提供することに尽力しています' }, about: { title: '私たちについて', mission: '私たちの使命', vision: '私たちのビジョン', team: '私たちのチーム' } } }; export function getTranslations(lang) { return translations[lang] || translations['en']; } export function t(lang, key) { const keys = key.split('.'); let value = translations[lang] || translations['en']; for (const k of keys) { value = value?.[k]; } return value || key; } // app/[lang]/page.js import { getTranslations } from '@/lib/i18n'; export default async function HomePage({ params: { lang } }) { const t = getTranslations(lang); return ( <div> <h1>{t.home.title}</h1> <p>{t.home.subtitle}</p> <p>{t.home.description}</p> </div> ); }
3. Dynamic Translation Hook
javascript// hooks/useTranslation.js 'use client'; import { usePathname } from 'next/navigation'; import { getTranslations } from '@/lib/i18n'; export function useTranslation() { const pathname = usePathname(); const lang = pathname.split('/')[1] || 'en'; const t = getTranslations(lang); return { lang, t }; } // app/[lang]/components/Navigation.js 'use client'; import { useTranslation } from '@/hooks/useTranslation'; import Link from 'next/link'; export default function Navigation() { const { t } = useTranslation(); return ( <nav> <Link href={`/${t.lang}`}>{t.common.home}</Link> <Link href={`/${t.lang}/about`}>{t.common.about}</Link> <Link href={`/${t.lang}/contact`}>{t.common.contact}</Link> </nav> ); }
4. Date and Number Formatting
javascript// lib/formatters.js export function formatDate(date, locale) { return new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'long', day: 'numeric' }).format(new Date(date)); } export function formatNumber(number, locale) { return new Intl.NumberFormat(locale).format(number); } export function formatCurrency(amount, locale, currency = 'USD') { return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount); } export function formatRelativeTime(date, locale) { const now = new Date(); const diff = now - new Date(date); const seconds = Math.floor(diff / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }); if (days > 0) return rtf.format(-days, 'day'); if (hours > 0) return rtf.format(-hours, 'hour'); if (minutes > 0) return rtf.format(-minutes, 'minute'); return rtf.format(-seconds, 'second'); } // Usage example // app/[lang]/blog/[slug]/page.js import { formatDate, formatRelativeTime } from '@/lib/formatters'; export default async function BlogPost({ params: { lang, slug } }) { const post = await getPost(slug); return ( <article> <h1>{post.title}</h1> <time dateTime={post.date}> {formatDate(post.date, lang)} </time> <p> {formatRelativeTime(post.date, lang)} </p> <div dangerouslySetInnerHTML={{ __html: post.content }} /> </article> ); }
5. SEO Optimization
javascript// app/[lang]/page.js import { Metadata } from 'next'; import { getTranslations } from '@/lib/i18n'; export async function generateMetadata({ params: { lang } }): Promise<Metadata> { const t = getTranslations(lang); return { title: t.home.title, description: t.home.description, alternates: { canonical: `/${lang}`, languages: { 'zh-CN': '/zh-CN', 'en': '/en', 'ja': '/ja' } }, openGraph: { title: t.home.title, description: t.home.description, locale: lang, alternateLocale: ['zh-CN', 'en', 'ja'].filter(l => l !== lang) } }; }
6. Translations in Server Components
javascript// app/[lang]/products/page.js import { getTranslations } from '@/lib/i18n'; export default async function ProductsPage({ params: { lang } }) { const t = getTranslations(lang); const products = await getProducts(); return ( <div> <h1>{t.products.title}</h1> <div className="grid"> {products.map(product => ( <ProductCard key={product.id} product={product} lang={lang} /> ))} </div> </div> ); } // app/[lang]/components/ProductCard.js 'use client'; import { useTranslation } from '@/hooks/useTranslation'; export default function ProductCard({ product, lang }) { const { t } = useTranslation(); return ( <div className="card"> <img src={product.image} alt={product.name[lang]} /> <h3>{product.name[lang]}</h3> <p>{product.description[lang]}</p> <button>{t.common.buy}</button> </div> ); }
7. Translation File Management
javascript// translations/zh-CN/home.json { "title": "欢迎来到我们的网站", "subtitle": "提供最好的服务", "description": "我们致力于为您提供最优质的产品和服务" } // translations/en/home.json { "title": "Welcome to Our Website", "subtitle": "Providing the Best Services", "description": "We are committed to providing you with the highest quality products and services" } // lib/loadTranslations.js import fs from 'fs'; import path from 'path'; export async function loadTranslations(lang, namespace) { const filePath = path.join(process.cwd(), 'translations', lang, `${namespace}.json`); try { const fileContent = await fs.promises.readFile(filePath, 'utf-8'); return JSON.parse(fileContent); } catch (error) { console.error(`Failed to load translations for ${lang}/${namespace}`); return {}; } } // Usage // app/[lang]/page.js import { loadTranslations } from '@/lib/loadTranslations'; export default async function HomePage({ params: { lang } }) { const homeTranslations = await loadTranslations(lang, 'home'); return ( <div> <h1>{homeTranslations.title}</h1> <p>{homeTranslations.subtitle}</p> </div> ); }
Best Practices
- Default Language: Set a default language as a fallback
- Translation Key Naming: Use consistent naming conventions (e.g.,
namespace.key) - Missing Translations: Provide graceful fallback handling
- Performance Optimization: Use static generation and caching
- SEO: Set correct hreflang tags for each language
- Content Management: Consider using a CMS to manage multilingual content
- Testing: Ensure all language versions are tested
Next.js's internationalization support makes building multilingual websites simple and efficient while maintaining excellent performance and SEO.