Internationalizing Your Next.js Application
When building a global application, it's essential to provide content tailored to users based on their language preferences. Let's explore how to implement internationalization (i18n) in a Next.js app, allowing users to view content in their preferred language and switch between supported locales.
Reading User Language Preferences
Browsers send user language preferences through the Accept-Language
header in HTTP requests. This header contains comma-separated language codes, often including a priority (q-factor). For example:
{
"Accept-Language": "en-US,fa;q=0.9,en;q=0.8"
}
The server can parse this header to determine the best-matching language based on supported locales.
Dynamic Routes for Localization
To enable localization, include language codes in your app's URL structure, such as /en/home
or /fr/home
. Here's how to set up dynamic routing:
- Create a
[lang]
folder in theapp
directory. - Move the
page.tsx
file into this folder. - Update the dynamic route to handle language codes.
Example route setup:
// app/[lang]/page.tsx
export default async function HomePage({ params }: { params: Promise<{ lang: string }> }) {
const lang = (await params).lang;
const content = lang === 'fr' ? "Page d'accueil" : "Home Page";
return <h1>{content}</h1>;
}
Loading Translations
Instead of hardcoding strings, create JSON files for each language. For example:
locales/en.json
:
{
"homepage": {
"title": "Home Page",
"description": "This is the English version."
}
}
locales/fr.json
:
{
"homepage": {
"title": "Page d'accueil",
"description": "Ceci est la version française."
}
}
Load these files dynamically based on the user's selected language.
import en from '../locales/en.json';
import fr from '../locales/fr.json';
const translations = { en, fr };
export function getTranslation(lang: string, key: string) {
return translations[lang]?.[key] || translations['en'][key];
}
Middleware for Language Detection
Use Next.js middleware to redirect users to their preferred language based on the Accept-Language
header:
// middleware.ts
import { NextResponse } from 'next/server';
export function middleware(req: Request) {
const acceptLanguage = req.headers.get('accept-language') || '';
const supportedLanguages = ['en', 'fr'];
const userLanguages = acceptLanguage
.split(',')
.map((lang) => lang.split(';')[0].split('-')[0]);
const bestMatch = supportedLanguages.find((lang) =>
userLanguages.includes(lang)
) || 'en';
const url = new URL(req.url);
if (!supportedLanguages.some((lang) => url.pathname.startsWith(`/${lang}`))) {
return NextResponse.redirect(`${url.origin}/${bestMatch}${url.pathname}`);
}
return NextResponse.next();
}
Refactoring Translation Logic
For scalability, create a translation service:
// services/translationService.ts
type SupportedLanguages = 'en' | 'fr';
import en from '../locales/en.json';
import fr from '../locales/fr.json';
const locales = { en, fr };
export const translationService = (lang: SupportedLanguages) => {
return {
t: (key: keyof typeof en) => locales[lang]?.[key] || locales['en'][key],
};
};
Usage:
import { translationService } from '../services/translationService';
const { t } = translationService('fr');
console.log(t('homepage').title); // "Page d'accueil"
Enhancing Accessibility
Set the <html>
language attribute dynamically based on the user's selected language to improve accessibility:
// app/layout.tsx
export default function Layout({ children, params }: { children: React.ReactNode; params: { lang: string } }) {
const lang = params.lang || 'en';
return (
<html lang={lang}>
<body>{children}</body>
</html>
);
}
Tips for Larger Applications
- Structured Translations: Use libraries like
i18next
for complex translation needs, including parameterized strings. - Fallback Mechanisms: Always provide a default language to ensure users see content, even if their preference isn't supported.
- Nested Routes: Structure folders to handle language codes for subpages, e.g.,
/en/news
or/fr/news
.
By following these steps, you can implement efficient and scalable i18n in your Next.js application, delivering a seamless experience to users worldwide.