Internationalization (i18n) in Next.js: Building Multi-Language Applications

Building a multi-language web application in Next.js involves more than translating strings—it requires routing infrastructure, locale detection, formatting abstractions, and search engine optimization for every language version. Next.js provides first-class support for internationalization through the App Router's built-in i18n features, but choosing the right strategy for routing, translation management, and locale persistence makes the difference between a maintainable system and a brittle one.
Routing Strategies for Locales
Next.js supports two routing approaches for internationalization: sub-path routing (/en/products, /fr/produits) and domain routing (en.example.com, fr.example.com). Sub-path routing is simpler and preferred for most applications:
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
i18n: false, // App Router handles i18n without this config
}
// middleware.ts — handle locale routing
import { NextResponse } from 'next/server'
import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'
const locales = ['en', 'fr', 'de', 'es', 'ja']
const defaultLocale = 'en'
function getLocale(request: Request): string {
const negotiator = new Negotiator({ headers: { 'accept-language': request.headers.get('accept-language') || '' } })
const languages = negotiator.languages()
return match(languages, locales, defaultLocale)
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
const pathnameHasLocale = locales.some(
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
)
if (pathnameHasLocale) return
const locale = getLocale(request)
request.nextUrl.pathname = `/${locale}${pathname}`
return NextResponse.redirect(request.nextUrl)
}
export const config = {
matcher: ['/((?!api|_next|_vercel|static|favicon.ico).*)'],
}
This middleware detects the user's preferred language from the Accept-Language header and redirects to the correct locale path. Users can override their locale by navigating to a different prefix, and the cookie-based locale selection persists across sessions.
Translation Management with Files
A robust i18n system separates translation data from application code. Use JSON files organized by locale and namespace:
// messages/en/common.json
{
"nav": {
"products": "Products",
"pricing": "Pricing",
"contact": "Contact"
},
"home": {
"hero": "Build better software",
"cta": "Get started"
}
}
// messages/fr/common.json
{
"nav": {
"products": "Produits",
"pricing": "Tarifs",
"contact": "Contact"
},
"home": {
"hero": "Créez de meilleurs logiciels",
"cta": "Commencer"
}
}
Integrate with next-intl for runtime translation resolution:
// app/[locale]/page.tsx
import { useTranslations } from 'next-intl'
export default function HomePage() {
const t = useTranslations('home')
return (
<section>
<h1>{t('hero')}</h1>
<button>{t('cta')}</button>
</section>
)
}
Using namespaced JSON files instead of a single monolithic translations object keeps bundles small—each page only loads the namespaces it needs.
Date, Number, and Currency Formatting
Numbers, dates, and currencies require locale-aware formatting that goes beyond simple string replacement. The Intl APIs handle this correctly across all locales:
// Formatting utility with fallback
export function formatPrice(amount: number, locale: string, currency: string) {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency,
}).format(amount)
}
export function formatDate(date: Date, locale: string) {
return new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric',
}).format(date)
}
export function formatRelativeTime(ms: number, locale: string) {
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' })
const seconds = Math.floor((Date.now() - ms) / 1000)
if (seconds < 60) return rtf.format(-seconds, 'second')
const minutes = Math.floor(seconds / 60)
if (minutes < 60) return rtf.format(-minutes, 'minute')
// ...extend for hours, days
}
Intl.RelativeTimeFormat handles "2 hours ago" or "yesterday" in any locale without third-party libraries. Combined with Intl.ListFormat for lists and Intl.PluralRules for pluralization, the native Intl API covers most formatting needs.
SEO for Multi-Language Sites
Search engines rely on hreflang tags to serve the correct language version of a page. Next.js's generateMetadata can inject these tags dynamically:
import { getTranslations } from 'next-intl/server'
export async function generateMetadata({ params: { locale } }) {
const t = await getTranslations({ locale, namespace: 'products' })
return {
title: t('meta.title'),
description: t('meta.description'),
alternates: {
canonical: `/${locale}/products`,
languages: {
'en': '/en/products',
'fr': '/fr/produits',
'de': '/de/produkte',
'x-default': '/en/products',
},
},
}
}
The x-default language tag tells search engines which page to show when no language match is found—typically the English version. Each language variant should have its own URL, unique metadata, and properly translated content for maximum SEO impact.
Internationalization is an investment in user experience and market reach. Next.js's App Router, combined with next-intl and native Intl APIs, provides a production-grade foundation for multi-language applications that scale.
Planning to launch in multiple markets? <a href="/services/web-development">Our web development team</a> can architect your i18n strategy and handle the implementation. Get in touch to discuss your requirements.
Related Insights

CI/CD Pipeline for Next.js: GitHub Actions to Vercel and Docker Deployments
A step-by-step guide to building CI/CD pipelines for Next.js applications using GitHub Actions including automated testing, preview deployments, Docker builds, and production rollouts.

Edge Computing with Next.js: Deploying to the Network Edge for Speed
Learn how to deploy Next.js applications to the edge using Vercel Edge Functions, Cloudflare Workers, and edge-rendered pages for sub-50ms response times.

Gatsby vs Next.js: Choosing Your React Framework in 2026
A comparison of Gatsby vs Next.js for building React websites covering performance, data fetching, plugin ecosystem, hosting options, and use case suitability.