Creating a multilanguage Next.js application is essential for reaching global audiences. This comprehensive guide shows you how to implement Next.js i18n (internationalization) from scratch, making your web app accessible to users worldwide.
In this tutorial, you’ll learn to build a complete multilanguage i18n system using Next.js App Router and the latest internationalization tools. We’ll create a blog website that supports multiple languages with automatic locale detection and seamless language switching.
What is Next.js i18n?
Next.js i18n refers to internationalization support in Next.js applications. It allows you to:
- Display content in multiple languages
- Automatically detect user’s preferred language
- Handle different locales and regions
- Format dates, numbers, and currencies correctly
- Provide locale-specific routing
Multilanguage Next.js applications improve user experience by presenting content in users’ native languages, leading to better engagement and conversion rates.
Why Use Next.js for Multilanguage Applications?
Built-in i18n Features
Next.js provides excellent multilanguage i18n support with:
- Automatic locale detection
- Built-in routing for different languages
- Static and dynamic content translation
- SEO-friendly URL structures
- Server-side rendering for all locales
Benefits of Next.js i18n
- Better SEO: Search engines can crawl locale-specific pages
- Improved Performance: Static generation for translated content
- User Experience: Automatic language detection and switching
- Developer Experience: Simple configuration and setup
Prerequisites
Before building your multilanguage Next.js application, make sure you have:
- Node.js 18+ installed
- Basic knowledge of React and Next.js
- Understanding of JavaScript objects and arrays
- Text editor or IDE of your choice
Part 1: Setting Up Next.js with i18n
Step 1: Create New Next.js Project
Start by creating a fresh Next.js i18n project:
npx create-next-app@latest nextjs-i18n-tutorial
cd nextjs-i18n-tutorial
Choose these options:
- TypeScript: Yes (recommended)
- ESLint: Yes
- Tailwind CSS: Yes
src/
directory: Yes- App Router: Yes
- Import alias: Yes
Step 2: Install i18n Dependencies
Install required packages for multilanguage Next.js functionality:
npm install next-intl
The next-intl
library is the most popular and well-maintained solution for Next.js i18n with the App Router.
Step 3: Configure Internationalization
Create next.config.js
in your project root:
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverComponentsExternalPackages: ['next-intl']
}
}
module.exports = nextConfig
Step 4: Set Up Locale Configuration
Create src/i18n.ts
:
import {notFound} from 'next/navigation';
import {getRequestConfig} from 'next-intl/server';
// List of supported locales for your multilanguage Next.js app
export const locales = ['en', 'es', 'fr', 'de'] as const;
export type Locale = (typeof locales)[number];
export default getRequestConfig(async ({locale}) => {
// Validate that the incoming `locale` parameter is valid
if (!locales.includes(locale as Locale)) notFound();
return {
messages: (await import(`../messages/${locale}.json`)).default
};
});
Step 5: Create Translation Files
Create the messages
directory and translation files:
mkdir messages
touch messages/en.json messages/es.json messages/fr.json messages/de.json
Add content to messages/en.json
:
{
"common": {
"home": "Home",
"about": "About",
"contact": "Contact",
"language": "Language",
"welcome": "Welcome to our website"
},
"home": {
"title": "Welcome to Next.js i18n Tutorial",
"subtitle": "Learn how to build multilanguage applications with Next.js",
"description": "This tutorial covers everything you need to know about internationalization in Next.js applications. Build apps that speak your users' language.",
"getStarted": "Get Started",
"learnMore": "Learn More"
},
"about": {
"title": "About Us",
"content": "We are passionate developers creating amazing multilanguage experiences with Next.js i18n. Our goal is to make internationalization simple and accessible for everyone."
},
"blog": {
"title": "Blog Posts",
"readMore": "Read More",
"publishedOn": "Published on",
"backToBlog": "Back to Blog"
}
}
Add content to messages/es.json
:
{
"common": {
"home": "Inicio",
"about": "Acerca de",
"contact": "Contacto",
"language": "Idioma",
"welcome": "Bienvenido a nuestro sitio web"
},
"home": {
"title": "Bienvenido al Tutorial de Next.js i18n",
"subtitle": "Aprende a construir aplicaciones multiidioma con Next.js",
"description": "Este tutorial cubre todo lo que necesitas saber sobre internacionalización en aplicaciones Next.js. Construye apps que hablen el idioma de tus usuarios.",
"getStarted": "Comenzar",
"learnMore": "Aprender Más"
},
"about": {
"title": "Acerca de Nosotros",
"content": "Somos desarrolladores apasionados creando experiencias multiidioma increíbles con Next.js i18n. Nuestro objetivo es hacer la internacionalización simple y accesible para todos."
},
"blog": {
"title": "Artículos del Blog",
"readMore": "Leer Más",
"publishedOn": "Publicado el",
"backToBlog": "Volver al Blog"
}
}
Add content to messages/fr.json
:
{
"common": {
"home": "Accueil",
"about": "À propos",
"contact": "Contact",
"language": "Langue",
"welcome": "Bienvenue sur notre site web"
},
"home": {
"title": "Bienvenue au Tutoriel Next.js i18n",
"subtitle": "Apprenez à créer des applications multilingues avec Next.js",
"description": "Ce tutoriel couvre tout ce que vous devez savoir sur l'internationalisation dans les applications Next.js. Créez des apps qui parlent la langue de vos utilisateurs.",
"getStarted": "Commencer",
"learnMore": "En Savoir Plus"
},
"about": {
"title": "À Propos de Nous",
"content": "Nous sommes des développeurs passionnés créant des expériences multilingues incroyables avec Next.js i18n. Notre objectif est de rendre l'internationalisation simple et accessible à tous."
},
"blog": {
"title": "Articles de Blog",
"readMore": "Lire Plus",
"publishedOn": "Publié le",
"backToBlog": "Retour au Blog"
}
}
Add content to messages/de.json
:
{
"common": {
"home": "Startseite",
"about": "Über uns",
"contact": "Kontakt",
"language": "Sprache",
"welcome": "Willkommen auf unserer Website"
},
"home": {
"title": "Willkommen zum Next.js i18n Tutorial",
"subtitle": "Lernen Sie, mehrsprachige Anwendungen mit Next.js zu erstellen",
"description": "Dieses Tutorial deckt alles ab, was Sie über Internationalisierung in Next.js-Anwendungen wissen müssen. Erstellen Sie Apps, die die Sprache Ihrer Benutzer sprechen.",
"getStarted": "Loslegen",
"learnMore": "Mehr Erfahren"
},
"about": {
"title": "Über Uns",
"content": "Wir sind leidenschaftliche Entwickler, die erstaunliche mehrsprachige Erfahrungen mit Next.js i18n schaffen. Unser Ziel ist es, Internationalisierung einfach und für alle zugänglich zu machen."
},
"blog": {
"title": "Blog-Artikel",
"readMore": "Weiterlesen",
"publishedOn": "Veröffentlicht am",
"backToBlog": "Zurück zum Blog"
}
}
Part 2: Setting Up Routing and Middleware
Step 6: Create Middleware for Locale Detection
Create src/middleware.ts
:
import createMiddleware from 'next-intl/middleware';
import {locales} from './i18n';
export default createMiddleware({
// A list of all locales that are supported
locales,
// Used when no locale matches
defaultLocale: 'en',
// Always use locale prefix
localePrefix: 'always'
});
export const config = {
// Match only internationalized pathnames
matcher: ['/', '/(de|en|es|fr)/:path*']
};
Step 7: Update App Layout Structure
First, move your existing src/app
directory to src/app-old
(temporary backup), then create the new structure:
mkdir -p src/app/[locale]
Create src/app/[locale]/layout.tsx
:
import {NextIntlClientProvider} from 'next-intl';
import {getMessages} from 'next-intl/server';
import {notFound} from 'next/navigation';
import {locales} from '@/i18n';
import './globals.css';
import Header from '@/components/Header';
export default async function LocaleLayout({
children,
params: {locale}
}: {
children: React.ReactNode;
params: {locale: string};
}) {
// Validate that the incoming `locale` parameter is valid
if (!locales.includes(locale as any)) notFound();
// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
<Header />
<main className="min-h-screen bg-gray-50">
{children}
</main>
</NextIntlClientProvider>
</body>
</html>
);
}
export function generateStaticParams() {
return locales.map((locale) => ({locale}));
}
Create src/app/[locale]/globals.css
(copy from your backup):
@tailwind base;
@tailwind components;
@tailwind utilities;
Step 8: Create the Header Component
Create src/components/Header.tsx
:
'use client';
import {useTranslations, useLocale} from 'next-intl';
import {useRouter, usePathname} from 'next/navigation';
import Link from 'next/link';
export default function Header() {
const t = useTranslations('common');
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const handleLanguageChange = (newLocale: string) => {
// Remove the current locale from the pathname
const pathWithoutLocale = pathname.replace(`/${locale}`, '');
// Navigate to the same path with the new locale
router.push(`/${newLocale}${pathWithoutLocale}`);
};
return (
<header className="bg-white shadow-sm border-b">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
{/* Logo */}
<div className="flex-shrink-0">
<Link href={`/${locale}`} className="text-xl font-bold text-blue-600">
Next.js i18n
</Link>
</div>
{/* Navigation */}
<nav className="hidden md:flex space-x-8">
<Link
href={`/${locale}`}
className="text-gray-900 hover:text-blue-600 px-3 py-2 text-sm font-medium"
>
{t('home')}
</Link>
<Link
href={`/${locale}/about`}
className="text-gray-900 hover:text-blue-600 px-3 py-2 text-sm font-medium"
>
{t('about')}
</Link>
<Link
href={`/${locale}/blog`}
className="text-gray-900 hover:text-blue-600 px-3 py-2 text-sm font-medium"
>
Blog
</Link>
</nav>
{/* Language Selector */}
<div className="flex items-center space-x-4">
<select
value={locale}
onChange={(e) => handleLanguageChange(e.target.value)}
className="border border-gray-300 rounded-md px-3 py-1 text-sm bg-white"
>
<option value="en">🇺🇸 English</option>
<option value="es">🇪🇸 Español</option>
<option value="fr">🇫🇷 Français</option>
<option value="de">🇩🇪 Deutsch</option>
</select>
</div>
</div>
</div>
</header>
);
}
Part 3: Creating Pages with Translations
Step 9: Create Home Page
Create src/app/[locale]/page.tsx
:
import {useTranslations} from 'next-intl';
import Link from 'next/link';
import {useLocale} from 'next-intl';
export default function HomePage() {
const t = useTranslations('home');
const locale = useLocale();
return (
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
{/* Hero Section */}
<div className="text-center mb-16">
<h1 className="text-4xl font-bold text-gray-900 mb-4">
{t('title')}
</h1>
<p className="text-xl text-gray-600 mb-8 max-w-3xl mx-auto">
{t('subtitle')}
</p>
<p className="text-lg text-gray-500 mb-10 max-w-4xl mx-auto">
{t('description')}
</p>
<div className="flex justify-center space-x-4">
<Link
href={`/${locale}/about`}
className="bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors"
>
{t('getStarted')}
</Link>
<Link
href={`/${locale}/blog`}
className="border border-blue-600 text-blue-600 px-6 py-3 rounded-lg font-medium hover:bg-blue-50 transition-colors"
>
{t('learnMore')}
</Link>
</div>
</div>
{/* Features Section */}
<div className="grid md:grid-cols-3 gap-8">
<div className="bg-white p-6 rounded-lg shadow-sm border">
<div className="text-3xl mb-4">🌍</div>
<h3 className="text-lg font-semibold mb-2">Global Reach</h3>
<p className="text-gray-600">
Reach users worldwide with proper internationalization and localization support.
</p>
</div>
<div className="bg-white p-6 rounded-lg shadow-sm border">
<div className="text-3xl mb-4">⚡</div>
<h3 className="text-lg font-semibold mb-2">Fast Performance</h3>
<p className="text-gray-600">
Built-in optimization ensures your multilanguage app loads quickly everywhere.
</p>
</div>
<div className="bg-white p-6 rounded-lg shadow-sm border">
<div className="text-3xl mb-4">🔧</div>
<h3 className="text-lg font-semibold mb-2">Easy Setup</h3>
<p className="text-gray-600">
Simple configuration gets your Next.js i18n app running in minutes.
</p>
</div>
</div>
</div>
);
}
export async function generateMetadata({params: {locale}}: {params: {locale: string}}) {
return {
title: 'Next.js i18n Tutorial - Multilanguage App Example',
description: 'Learn how to build multilanguage Next.js applications with internationalization support'
};
}
Step 10: Create About Page
Create src/app/[locale]/about/page.tsx
:
import {useTranslations, useLocale} from 'next-intl';
export default function AboutPage() {
const t = useTranslations('about');
const locale = useLocale();
return (
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="bg-white rounded-lg shadow-sm border p-8">
<h1 className="text-3xl font-bold text-gray-900 mb-6">
{t('title')}
</h1>
<div className="prose prose-lg max-w-none">
<p className="text-gray-600 text-lg leading-relaxed">
{t('content')}
</p>
<div className="mt-8 grid md:grid-cols-2 gap-6">
<div className="bg-gray-50 p-6 rounded-lg">
<h3 className="font-semibold text-gray-900 mb-2">
Current Language: {locale.toUpperCase()}
</h3>
<p className="text-gray-600 text-sm">
This page content is automatically translated based on your selected language.
</p>
</div>
<div className="bg-blue-50 p-6 rounded-lg">
<h3 className="font-semibold text-gray-900 mb-2">
Supported Languages
</h3>
<ul className="text-gray-600 text-sm space-y-1">
<li>🇺🇸 English (en)</li>
<li>🇪🇸 Spanish (es)</li>
<li>🇫🇷 French (fr)</li>
<li>🇩🇪 German (de)</li>
</ul>
</div>
</div>
</div>
</div>
</div>
);
}
export async function generateMetadata({params: {locale}}: {params: {locale: string}}) {
return {
title: 'About Us - Next.js i18n Tutorial',
description: 'Learn about our multilanguage Next.js tutorial and internationalization approach'
};
}
Step 11: Create Blog Section
Create src/app/[locale]/blog/page.tsx
:
import {useTranslations, useLocale} from 'next-intl';
import Link from 'next/link';
// Sample blog posts data
const blogPosts = [
{
id: 1,
slug: 'getting-started-nextjs-i18n',
title: {
en: 'Getting Started with Next.js Internationalization',
es: 'Comenzando con la Internacionalización de Next.js',
fr: 'Commencer avec l\'Internationalisation Next.js',
de: 'Erste Schritte mit Next.js Internationalisierung'
},
excerpt: {
en: 'Learn the basics of setting up i18n in your Next.js applications for global reach.',
es: 'Aprende los conceptos básicos para configurar i18n en tus aplicaciones Next.js para alcance global.',
fr: 'Apprenez les bases de la configuration i18n dans vos applications Next.js pour une portée mondiale.',
de: 'Lernen Sie die Grundlagen der i18n-Einrichtung in Ihren Next.js-Anwendungen für globale Reichweite.'
},
date: '2024-01-15'
},
{
id: 2,
slug: 'advanced-translation-techniques',
title: {
en: 'Advanced Translation Techniques in Next.js',
es: 'Técnicas Avanzadas de Traducción en Next.js',
fr: 'Techniques de Traduction Avancées dans Next.js',
de: 'Erweiterte Übersetzungstechniken in Next.js'
},
excerpt: {
en: 'Explore advanced patterns for handling complex translations and dynamic content.',
es: 'Explora patrones avanzados para manejar traducciones complejas y contenido dinámico.',
fr: 'Explorez des modèles avancés pour gérer des traductions complexes et du contenu dynamique.',
de: 'Erkunden Sie erweiterte Muster für komplexe Übersetzungen und dynamische Inhalte.'
},
date: '2024-01-10'
}
];
export default function BlogPage() {
const t = useTranslations('blog');
const locale = useLocale();
return (
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-4">
{t('title')}
</h1>
<p className="text-gray-600">
Discover articles about Next.js internationalization and multilanguage development.
</p>
</div>
<div className="space-y-6">
{blogPosts.map((post) => (
<article key={post.id} className="bg-white rounded-lg shadow-sm border p-6">
<div className="flex justify-between items-start mb-4">
<div className="flex-1">
<h2 className="text-xl font-semibold text-gray-900 mb-2">
<Link
href={`/${locale}/blog/${post.slug}`}
className="hover:text-blue-600 transition-colors"
>
{post.title[locale as keyof typeof post.title]}
</Link>
</h2>
<p className="text-gray-600 mb-4">
{post.excerpt[locale as keyof typeof post.excerpt]}
</p>
</div>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-gray-500">
{t('publishedOn')} {new Date(post.date).toLocaleDateString(locale)}
</span>
<Link
href={`/${locale}/blog/${post.slug}`}
className="text-blue-600 hover:text-blue-700 font-medium text-sm"
>
{t('readMore')} →
</Link>
</div>
</article>
))}
</div>
</div>
);
}
export async function generateMetadata({params: {locale}}: {params: {locale: string}}) {
return {
title: 'Blog - Next.js i18n Tutorial',
description: 'Read articles about Next.js internationalization and multilanguage web development'
};
}
Step 12: Create Individual Blog Post Page
Create src/app/[locale]/blog/[slug]/page.tsx
:
import {useTranslations, useLocale} from 'next-intl';
import Link from 'next/link';
import {notFound} from 'next/navigation';
// Sample blog post content
const blogPosts = {
'getting-started-nextjs-i18n': {
title: {
en: 'Getting Started with Next.js Internationalization',
es: 'Comenzando con la Internacionalización de Next.js',
fr: 'Commencer avec l\'Internationalisation Next.js',
de: 'Erste Schritte mit Next.js Internationalisierung'
},
content: {
en: `
<h2>Introduction to Next.js i18n</h2>
<p>Internationalization (i18n) is crucial for creating web applications that can reach global audiences. Next.js provides excellent built-in support for i18n, making it easier than ever to create multilanguage applications.</p>
<h2>Why Choose Next.js for Multilanguage Apps?</h2>
<p>Next.js offers several advantages for building multilanguage applications:</p>
<ul>
<li>Built-in routing for different locales</li>
<li>Automatic language detection</li>
<li>SEO-friendly URL structures</li>
<li>Server-side rendering support</li>
</ul>
<h2>Getting Started</h2>
<p>Setting up i18n in Next.js involves configuring your next.config.js file, creating translation files, and implementing the necessary components.</p>
`,
es: `
<h2>Introducción a Next.js i18n</h2>
<p>La internacionalización (i18n) es crucial para crear aplicaciones web que puedan llegar a audiencias globales. Next.js proporciona un excelente soporte integrado para i18n, haciendo más fácil que nunca crear aplicaciones multiidioma.</p>
<h2>¿Por qué Elegir Next.js para Apps Multiidioma?</h2>
<p>Next.js ofrece varias ventajas para construir aplicaciones multiidioma:</p>
<ul>
<li>Enrutamiento integrado para diferentes locales</li>
<li>Detección automática de idioma</li>
<li>Estructuras de URL amigables para SEO</li>
<li>Soporte de renderizado del lado del servidor</li>
</ul>
<h2>Comenzando</h2>
<p>Configurar i18n en Next.js involucra configurar tu archivo next.config.js, crear archivos de traducción e implementar los componentes necesarios.</p>
`,
fr: `
<h2>Introduction à Next.js i18n</h2>
<p>L'internationalisation (i18n) est cruciale pour créer des applications web qui peuvent atteindre des audiences mondiales. Next.js fournit un excellent support intégré pour i18n, rendant plus facile que jamais la création d'applications multilingues.</p>
<h2>Pourquoi Choisir Next.js pour les Apps Multilingues?</h2>
<p>Next.js offre plusieurs avantages pour construire des applications multilingues:</p>
<ul>
<li>Routage intégré pour différents locales</li>
<li>Détection automatique de langue</li>
<li>Structures d'URL favorables au SEO</li>
<li>Support de rendu côté serveur</li>
</ul>
<h2>Commencer</h2>
<p>Configurer i18n dans Next.js implique de configurer votre fichier next.config.js, créer des fichiers de traduction et implémenter les composants nécessaires.</p>
`,
de: `
<h2>Einführung in Next.js i18n</h2>
<p>Internationalisierung (i18n) ist entscheidend für die Erstellung von Webanwendungen, die globale Zielgruppen erreichen können. Next.js bietet hervorragende integrierte Unterstützung für i18n und macht es einfacher denn je, mehrsprachige Anwendungen zu erstellen.</p>
<h2>Warum Next.js für mehrsprachige Apps wählen?</h2>
<p>Next.js bietet mehrere Vorteile für den Aufbau mehrsprachiger Anwendungen:</p>
<ul>
<li>Integriertes Routing für verschiedene Locales</li>
<li>Automatische Spracherkennung</li>
<li>SEO-freundliche URL-Strukturen</li>
<li>Server-seitiges Rendering-Support</li>
</ul>
<h2>Erste Schritte</h2>
<p>Die Einrichtung von i18n in Next.js umfasst die Konfiguration Ihrer next.config.js-Datei, das Erstellen von Übersetzungsdateien und die Implementierung der notwendigen Komponenten.</p>
`
},
date: '2024-01-15'
},
'advanced-translation-techniques': {
title: {
en: 'Advanced Translation Techniques in Next.js',
es: 'Técnicas Avanzadas de Traducción en Next.js',
fr: 'Techniques de Traduction Avancées dans Next.js',
de: 'Erweiterte Übersetzungstechniken in Next.js'
},
content: {
en: `
<h2>Advanced Translation Patterns</h2>
<p>Once you've mastered the basics of Next.js i18n, it's time to explore advanced techniques for handling complex translation scenarios.</p>
<h2>Dynamic Content Translation</h2>
<p>Learn how to translate dynamic content that comes from APIs or databases while maintaining performance and SEO benefits.</p>
<h2>Pluralization and Formatting</h2>
<p>Handle complex language rules including pluralization, number formatting, and date localization for a truly native experience.</p>
`,
es: `
<h2>Patrones de Traducción Avanzados</h2>
<p>Una vez que hayas dominado los conceptos básicos de Next.js i18n, es hora de explorar técnicas avanzadas para manejar escenarios de traducción complejos.</p>
<h2>Traducción de Contenido Dinámico</h2>
<p>Aprende cómo traducir contenido dinámico que proviene de APIs o bases de datos mientras mantienes los beneficios de rendimiento y SEO.</p>
<h2>Pluralización y Formato</h2>
<p>Maneja reglas complejas del idioma incluyendo pluralización, formato de números y localización de fechas para una experiencia verdaderamente nativa.</p>
`,
fr: `
<h2>Modèles de Traduction Avancés</h2>
<p>Une fois que vous avez maîtrisé les bases de Next.js i18n, il est temps d'explorer des techniques avancées pour gérer des scénarios de traduction complexes.</p>
<h2>Traduction de Contenu Dynamique</h2>
<p>Apprenez comment traduire du contenu dynamique provenant d'APIs ou de bases de données tout en maintenant les avantages de performance et SEO.</p>
<h2>Pluralisation et Formatage</h2>
<p>Gérez des règles linguistiques complexes incluant la pluralisation, le formatage des nombres et la localisation des dates pour une expérience vraiment native.</p>
`,
de: `
<h2>Erweiterte Übersetzungsmuster</h2>
<p>Nachdem Sie die Grundlagen von Next.js i18n gemeistert haben, ist es Zeit, erweiterte Techniken für komplexe Übersetzungsszenarien zu erkunden.</p>
<h2>Dynamische Inhaltsübersetzung</h2>
<p>Lernen Sie, wie Sie dynamische Inhalte übersetzen, die von APIs oder Datenbanken stammen, während Sie Performance- und SEO-Vorteile beibehalten.</p>
<h2>Pluralisierung und Formatierung</h2>
<p>Behandeln Sie komplexe Sprachregeln einschließlich Pluralisierung, Zahlenformatierung und Datumslokalisierung für eine wirklich native Erfahrung.</p>
`
},
date: '2024-01-10'
}
};
export default function BlogPostPage({params}: {params: {slug: string}}) {
const t = useTranslations('blog');
const locale = useLocale();
const post = blogPosts[params.slug as keyof typeof blogPosts];
if (!post) {
notFound();
}
return (
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="mb-6">
<Link
href={`/${locale}/blog`}
className="text-blue-600 hover:text-blue-700 font-medium text-sm"
>
← {t('backToBlog')}
</Link>
</div>
<article className="bg-white rounded-lg shadow-sm border p-8">
<header className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-4">
{post.title[locale as keyof typeof post.title]}
</h1>
<p className="text-gray-600">
{t('publishedOn')} {new Date(post.date).toLocaleDateString(locale)}
</p>
</header>
<div
className="prose prose-lg max-w-none"
dangerouslySetInnerHTML={{
__html: post.content[locale as keyof typeof post.content]
}}
/>
</article>
</div>
);
}
export async function generateStaticParams() {
return Object.keys(blogPosts).map((slug) => ({
slug: slug,
}));
}
export async function generateMetadata({params}: {params: {slug: string, locale: string}}) {
const post = blogPosts[params.slug as keyof typeof blogPosts];
if (!post) {
return {
title: 'Post Not Found'
};
}
return {
title: post.title[params.locale as keyof typeof post.title],
description: 'Learn about Next.js internationalization and multilanguage development techniques'
};
}
Part 4: Advanced Next.js i18n Features
Step 13: Add Date and Number Formatting
Create src/utils/formatters.ts
:
import {useLocale} from 'next-intl';
export function useFormatters() {
const locale = useLocale();
const formatDate = (date: Date) => {
return new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date);
};
const formatNumber = (number: number) => {
return new Intl.NumberFormat(locale).format(number);
};
const formatCurrency = (amount: number, currency = 'USD') => {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
}).format(amount);
};
const formatRelativeTime = (date: Date) => {
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
const diffInDays = Math.floor((date.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
if (Math.abs(diffInDays) < 7) {
return rtf.format(diffInDays, 'day');
} else if (Math.abs(diffInDays) < 30) {
return rtf.format(Math.floor(diffInDays / 7), 'week');
} else {
return rtf.format(Math.floor(diffInDays / 30), 'month');
}
};
return {
formatDate,
formatNumber,
formatCurrency,
formatRelativeTime
};
}
Step 14: Handle Pluralization
Update your translation files to include pluralization rules. Add to messages/en.json
:
{
"plurals": {
"items": "{count, plural, =0 {no items} =1 {one item} other {# items}}"
}
}
Step 15: Create Language Switcher Component
Create src/components/LanguageSwitcher.tsx
:
'use client';
import {useLocale} from 'next-intl';
import {useRouter, usePathname} from 'next/navigation';
import {useState} from 'react';
const languages = [
{ code: 'en', name: 'English', flag: '🇺🇸' },
{ code: 'es', name: 'Español', flag: '🇪🇸' },
{ code: 'fr', name: 'Français', flag: '🇫🇷' },
{ code: 'de', name: 'Deutsch', flag: '🇩🇪' }
];
export default function LanguageSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const [isOpen, setIsOpen] = useState(false);
const currentLanguage = languages.find(lang => lang.code === locale);
const handleLanguageChange = (newLocale: string) => {
const pathWithoutLocale = pathname.replace(`/${locale}`, '');
router.push(`/${newLocale}${pathWithoutLocale}`);
setIsOpen(false);
};
return (
<div className="relative">
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center space-x-2 px-3 py-2 border border-gray-300 rounded-md bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<span>{currentLanguage?.flag}</span>
<span className="text-sm font-medium">{currentLanguage?.name}</span>
<svg
className={`w-4 h-4 transition-transform ${isOpen ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{isOpen && (
<div className="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg z-10">
{languages.map((language) => (
<button
key={language.code}
onClick={() => handleLanguageChange(language.code)}
className="w-full text-left px-4 py-2 text-sm hover:bg-gray-100 flex items-center space-x-2"
>
<span>{language.flag}</span>
<span>{language.name}</span>
{language.code === locale && (
<span className="ml-auto text-blue-600">✓</span>
)}
</button>
))}
</div>
)}
</div>
);
}
Part 5: SEO and Performance Optimization
Step 16: Add SEO-Friendly URLs
Create src/app/[locale]/sitemap.ts
:
import {locales} from '@/i18n';
export default function sitemap() {
const baseUrl = 'https://your-domain.com';
const routes = ['', '/about', '/blog'];
const urls = locales.flatMap((locale) =>
routes.map((route) => ({
url: `${baseUrl}/${locale}${route}`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: route === '' ? 1 : 0.8,
}))
);
return urls;
}
Step 17: Add Robots.txt
Create src/app/robots.ts
:
export default function robots() {
return {
rules: {
userAgent: '*',
allow: '/',
},
sitemap: 'https://your-domain.com/sitemap.xml',
};
}
Step 18: Optimize Loading Performance
Create src/components/LoadingSpinner.tsx
:
export default function LoadingSpinner() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
);
}
Testing Your Multilanguage Next.js Application
Step 19: Test Your Application
Start your development server:
npm run dev
Visit these URLs to test your multilanguage Next.js app:
http://localhost:3000/en
- English versionhttp://localhost:3000/es
- Spanish versionhttp://localhost:3000/fr
- French versionhttp://localhost:3000/de
- German version
Step 20: Test Automatic Language Detection
The middleware will automatically redirect users based on their browser’s language preference when they visit the root URL (http://localhost:3000
).
Common Issues and Solutions
Issue 1: Translations Not Loading
Problem: Translations don’t appear on the page Solution: Check that your JSON files are properly formatted and the keys match exactly
Issue 2: Routing Not Working
Problem: Language-specific routes return 404 errors Solution: Ensure your middleware is configured correctly and the matcher
pattern includes all your locales
Issue 3: SEO URLs Not Generated
Problem: Search engines can’t find localized pages Solution: Implement proper generateStaticParams
and metadata generation for each locale
Advanced Next.js i18n Techniques
Dynamic Translation Loading
For large applications, load translations dynamically to improve performance:
// src/lib/translations.ts
export async function loadTranslations(locale: string, namespace: string) {
try {
const translations = await import(`../../messages/${locale}/${namespace}.json`);
return translations.default;
} catch (error) {
console.warn(`Translation file not found: ${locale}/${namespace}.json`);
return {};
}
}
Translation Management
For professional projects, consider using translation management services:
- Locize: Cloud-based translation management
- Crowdin: Collaborative translation platform
- Phrase: Translation management for developers
RTL (Right-to-Left) Language Support
Add support for RTL languages like Arabic or Hebrew:
// src/utils/rtl.ts
const RTL_LOCALES = ['ar', 'he', 'fa'];
export function isRTL(locale: string): boolean {
return RTL_LOCALES.includes(locale);
}
export function getDirection(locale: string): 'ltr' | 'rtl' {
return isRTL(locale) ? 'rtl' : 'ltr';
}
Deployment Best Practices
Static Generation for Better Performance
Configure static generation for all locales:
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
trailingSlash: true,
experimental: {
serverComponentsExternalPackages: ['next-intl']
}
}
module.exports = nextConfig
CDN Configuration
For global applications, configure your CDN to serve the correct locale based on user location:
// Cloudflare Workers example
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const country = request.cf.country
const acceptLanguage = request.headers.get('Accept-Language')
// Logic to determine best locale
const locale = determineLocale(country, acceptLanguage)
return fetch(`https://your-app.com/${locale}${new URL(request.url).pathname}`)
}
Performance Monitoring
Measuring Translation Loading Performance
Add performance monitoring to track translation loading times:
// src/lib/performance.ts
export function measureTranslationPerformance(locale: string, namespace: string) {
const startTime = performance.now();
return {
end: () => {
const endTime = performance.now();
console.log(`Translation ${locale}/${namespace} loaded in ${endTime - startTime}ms`);
}
};
}
Security Considerations
Input Sanitization for Translations
Always sanitize user-generated content in translations:
import DOMPurify from 'dompurify';
export function sanitizeTranslation(content: string): string {
return DOMPurify.sanitize(content);
}
Locale Validation
Validate locales to prevent injection attacks:
export function validateLocale(locale: string): boolean {
const validLocales = ['en', 'es', 'fr', 'de'];
return validLocales.includes(locale) && /^[a-z]{2}(-[A-Z]{2})?$/.test(locale);
}
Conclusion
Building a multilanguage Next.js application with Next.js i18n opens your web app to global audiences. This comprehensive tutorial covered everything from basic setup to advanced optimization techniques.
Key benefits of implementing multilanguage i18n in Next.js include:
- Global Reach: Serve users in their preferred languages
- Better SEO: Search engines can index locale-specific content
- Improved User Experience: Native language support increases engagement
- Professional Scaling: Enterprise-ready internationalization solution
The Next.js i18n approach we’ve implemented provides a solid foundation that can handle complex translation requirements while maintaining excellent performance and developer experience.