Next.js Internationalization (i18n): Çok Dilli Site

Next.js Internationalization (i18n): Çok Dilli Site
Çok dilli web siteleri oluşturmak, global bir kullanıcı kitlesine ulaşmanın en etkili yoludur. Next.js App Router ile internationalization (i18n) kurmak, middleware tabanlı dil yönlendirmesi ve çeviri dosyaları ile sistematik bir şekilde yapılabilir. Bu yazıda Next.js'te sıfırdan çok dilli bir site oluşturma sürecini adım adım inceleyeceğiz.
i18n Stratejileri
Çok dilli site için iki temel URL stratejisi vardır:
- Sub-path routing:
/tr/hakkimizda,/en/about— En yaygın yöntem - Domain routing:
tr.site.com,en.site.com— Farklı domainler
Bu rehberde sub-path routing stratejisini kullanacağız.
Proje Yapısı
app/
├── [locale]/
│ ├── layout.js
│ ├── page.js
│ ├── about/
│ │ └── page.js
│ └── blog/
│ └── page.js
├── layout.js
messages/
├── tr.json
├── en.json
├── de.json
lib/
├── i18n.js
└── dictionaries.js
middleware.js
next-intl Kurulumu
next-intl, Next.js App Router için en popüler i18n kütüphanesidir:
npm install next-intl
Yapılandırma Dosyası
// i18n/config.js
export const locales = ['tr', 'en', 'de'];
export const defaultLocale = 'tr';
export const localeNames = {
tr: 'Türkçe',
en: 'English',
de: 'Deutsch',
};
Çeviri Dosyaları
// messages/tr.json
{
"common": {
"home": "Ana Sayfa",
"about": "Hakkımızda",
"blog": "Blog",
"contact": "İletişim",
"language": "Dil"
},
"home": {
"title": "Web Geliştirme Blogu",
"description": "Modern web teknolojileri hakkında yazılar ve rehberler.",
"latestPosts": "Son Yazılar",
"readMore": "Devamını Oku"
},
"about": {
"title": "Hakkımızda",
"content": "Biz web geliştirme tutkunu bir ekibiz."
},
"footer": {
"copyright": "© {year} Tüm hakları saklıdır.",
"madeWith": "{name} tarafından geliştirildi"
}
}
// messages/en.json
{
"common": {
"home": "Home",
"about": "About",
"blog": "Blog",
"contact": "Contact",
"language": "Language"
},
"home": {
"title": "Web Development Blog",
"description": "Articles and guides about modern web technologies.",
"latestPosts": "Latest Posts",
"readMore": "Read More"
},
"about": {
"title": "About Us",
"content": "We are a team passionate about web development."
},
"footer": {
"copyright": "© {year} All rights reserved.",
"madeWith": "Developed by {name}"
}
}
i18n Request Yapılandırması
// i18n/request.js
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
if (!locale || !routing.locales.includes(locale)) {
locale = routing.defaultLocale;
}
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default,
};
});
// i18n/routing.js
import { defineRouting } from 'next-intl/routing';
import { createNavigation } from 'next-intl/navigation';
export const routing = defineRouting({
locales: ['tr', 'en', 'de'],
defaultLocale: 'tr',
});
export const { Link, redirect, usePathname, useRouter } =
createNavigation(routing);
Middleware
// middleware.js
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: ['/', '/(tr|en|de)/:path*'],
};
Layout
// app/[locale]/layout.js
import { NextIntlClientProvider } from 'next-intl';
import { getMessages, setRequestLocale } from 'next-intl/server';
import { routing } from '@/i18n/routing';
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}
export async function generateMetadata({ params }) {
const { locale } = await params;
const messages = await getMessages({ locale });
return {
title: messages.home?.title || 'Web Blog',
description: messages.home?.description,
};
}
export default async function LocaleLayout({ children, params }) {
const { locale } = await params;
setRequestLocale(locale);
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
Sayfa Bileşeni
// app/[locale]/page.js
import { useTranslations } from 'next-intl';
import { setRequestLocale } from 'next-intl/server';
export default function HomePage({ params }) {
const { locale } = params;
setRequestLocale(locale);
const t = useTranslations('home');
return (
<main>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
<section>
<h2>{t('latestPosts')}</h2>
{/* Yazı listesi */}
</section>
</main>
);
}
Dil Değiştirme Bileşeni
'use client';
import { useLocale } from 'next-intl';
import { useRouter, usePathname } from '@/i18n/routing';
import { localeNames } from '@/i18n/config';
export function LanguageSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const handleChange = (newLocale) => {
router.replace(pathname, { locale: newLocale });
};
return (
<div className="language-switcher">
{Object.entries(localeNames).map(([code, name]) => (
<button
key={code}
onClick={() => handleChange(code)}
className={locale === code ? 'active' : ''}
aria-label={`Dili ${name} olarak değiştir`}
>
{name}
</button>
))}
</div>
);
}
Dinamik Değerler ve Çoğullama
// messages/tr.json
{
"blog": {
"postCount": "{count, plural, =0 {Yazı bulunamadı} one {# yazı} other {# yazı}}",
"publishedAt": "{date, date, medium} tarihinde yayınlandı",
"readTime": "{minutes} dakika okuma süresi"
}
}
// Kullanım
const t = useTranslations('blog');
t('postCount', { count: 5 }); // "5 yazı"
t('publishedAt', { date: new Date() }); // "27 Mar 2026 tarihinde yayınlandı"
t('readTime', { minutes: 8 }); // "8 dakika okuma süresi"
SEO ve hreflang
// app/[locale]/layout.js
export async function generateMetadata({ params }) {
const { locale } = await params;
return {
alternates: {
canonical: `https://site.com/${locale}`,
languages: {
tr: 'https://site.com/tr',
en: 'https://site.com/en',
de: 'https://site.com/de',
},
},
};
}
hreflang etiketlerinin doğru yapılandırılması ve html lang attribute'unun ayarlanması kritik öneme sahiptir. next-intl bu konularda büyük kolaylık sağlar.
next-intl'in getMessages fonksiyonu eksik çevirilerde varsayılan dile düşebilir. Büyük projelerde namespace'ler kullanarak çeviri dosyalarını modüler hale getirin.
Sonuç
Next.js App Router ve next-intl ile çok dilli web siteleri oluşturmak hem sistematik hem de güçlüdür. Sub-path routing stratejisi, middleware tabanlı dil yönlendirmesi, Server ve Client Component desteği, dinamik çeviriler ve SEO uyumlu hreflang yapılandırması ile profesyonel bir i18n altyapısı kurabilirsiniz. Çeviri dosyalarını modüler tutun, eksik çeviriler için fallback mekanizmaları ekleyin ve tarih/sayı formatlamalarını locale'e uygun şekilde yapın.