Back to home

Next.js Internationalization A Complete Guide to Building Global Applications

188 min read

🌏 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

  1. Client Component Handling:
'use client'

import { useParams } from 'next/navigation'

export default function ClientComponent() {
  const { lang } = useParams()
  // Access current language in client components
}
  1. Dynamic Content Handling:
function formatPrice(price, locale) {
  return new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: locale === 'zh-CN' ? 'CNY' : 'USD',
  }).format(price)
}
  1. 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:

  1. 🚀 Automatic language detection and route redirection
  2. 📦 Efficient translation resource management
  3. ⚡️ Optimal performance
  4. 🔍 Enhanced SEO performance

🔍 Recommended Tools:

  • next-i18next: Full-featured i18n solution
  • next-translate: Lightweight translation tool
  • Crowdin: Professional translation management platform