İçeriğe git
Next.js i18n

App Router ile Next.js i18n

Next.js uygulamaları için Server Components, ISR ve edge-optimize çeviriler.

1middleware
middleware.ts
2getRequestConfig
i18n/request.ts
3getMessages()
app/[locale]/page.tsx
4useTranslations()
components/Hero.tsx
cdn.better-i18n.com/your-org/your-project/{locale}/translations.jsonmax-age=60

Setup

Set up in 4 steps

Kurulum

Projenize @better-i18n/next ve next-intl paketlerini ekleyin.

terminal
npm install @better-i18n/next next-intl

Yerel ayar tespiti için middleware ekleyin

Middleware, kullanıcının yerel ayarını belirlemek ve buna göre yönlendirme yapmak için Accept-Language başlığını ve URL önekini okur.

middleware.ts
import { createBetterI18nMiddleware } from '@better-i18n/next';

export default createBetterI18nMiddleware({
  project: 'your-org/your-project',
  defaultLocale: 'en',
  localePrefix: 'always',
});

export const config = { matcher: ['/((?!api|_next).*)'] };

Mesajları bir Server Component içinde yükleyin

Sunucu tarafında çevirileri almak ve bunları BetterI18nProvider'a iletmek için kök layout'unuzda getMessages() kullanın.

app/[locale]/layout.tsx
// app/[locale]/layout.tsx
import { BetterI18nProvider } from '@better-i18n/next/client';
import { getMessages } from '@better-i18n/next/server';

const config = { project: 'your-org/your-project', defaultLocale: 'en' };

export default async function RootLayout({ children, params }) {
  const { locale } = await params;
  const messages = await getMessages(config, locale);

  return (
    <html lang={locale}>
      <body>
        <BetterI18nProvider locale={locale} messages={messages} config={config}>
          {children}
        </BetterI18nProvider>
      </body>
    </html>
  );
}

Client Component'lerde çevirileri kullanın

Herhangi bir Client Component içinde useTranslations() çağırın. Mesajlar sunucudan zaten hydrate edilmiştir — ek bir istek gerekmez.

components/HeroSection.tsx
'use client';
import { useTranslations } from 'next-intl';

export function HeroSection() {
  const t = useTranslations('home');
  return <h1>{t('title')}</h1>;
}

Routing

Edge Çalışma Zamanı ve Yerel Ayar Algılama

Dünya çapında 50 ms'nin altında yanıt süreleri için uçta yerel ayar algılama ve mesaj yükleme işlemini çalıştırın.

Middleware Kurulumu

Tek bir middleware dosyasıyla Next.js uygulamanıza yerel ayar tespiti ve yönlendirme ekleyin.

middleware.ts
// middleware.ts — locale detection
import { createBetterI18nMiddleware } from '@better-i18n/next'

export default createBetterI18nMiddleware({
  project: 'your-org/your-project',
  defaultLocale: 'en',
  localePrefix: 'always',
})

export const config = { matcher: ['/((?!api|_next).*)'] }
middleware.ts
// middleware.ts — Edge-based locale detection
import { NextRequest, NextResponse } from 'next/server';

const SUPPORTED_LOCALES = ['en', 'de', 'fr', 'ja', 'es'] as const;
const DEFAULT_LOCALE = 'en';

function getPreferredLocale(request: NextRequest): string {
  // 1. Check URL prefix
  const pathname = request.nextUrl.pathname;
  const urlLocale = SUPPORTED_LOCALES.find(
    (l) => pathname.startsWith(`/${l}/`) || pathname === `/${l}`
  );
  if (urlLocale) return urlLocale;

  // 2. Check cookie
  const cookieLocale = request.cookies.get('NEXT_LOCALE')?.value;
  if (cookieLocale && SUPPORTED_LOCALES.includes(cookieLocale as any)) {
    return cookieLocale;
  }

  // 3. Parse Accept-Language header
  const acceptLang = request.headers.get('accept-language') ?? '';
  const preferred = acceptLang
    .split(',')
    .map((part) => part.split(';')[0].trim().substring(0, 2))
    .find((code) => SUPPORTED_LOCALES.includes(code as any));

  return preferred ?? DEFAULT_LOCALE;
}

export function middleware(request: NextRequest) {
  const locale = getPreferredLocale(request);
  const { pathname } = request.nextUrl;

  const hasLocale = SUPPORTED_LOCALES.some(
    (l) => pathname.startsWith(`/${l}/`) || pathname === `/${l}`
  );

  if (!hasLocale) {
    return NextResponse.redirect(
      new URL(`/${locale}${pathname}`, request.url)
    );
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

Edge Uyumlu Mesaj Yükleme

Anlık yanıtlar için hafif bir bellek içi TTL önbelleğiyle çevirileri edge'de önbelleğe alın.

lib/edge-messages.ts
// lib/edge-messages.ts — Edge-compatible message loading
const messageCache = new Map<string, { data: Record<string, string>; ts: number }>();
const TTL = 60_000; // 1 minute cache at edge

export async function getEdgeMessages(
  locale: string,
  namespace: string
): Promise<Record<string, string>> {
  const cacheKey = `${locale}:${namespace}`;
  const cached = messageCache.get(cacheKey);

  if (cached && Date.now() - cached.ts < TTL) {
    return cached.data;
  }

  const response = await fetch(
    `https://cdn.better-i18n.com/your-org/your-project/${locale}/${namespace}.json`,
    { next: { revalidate: 60 } }
  );

  const data = await response.json();
  messageCache.set(cacheKey, { data, ts: Date.now() });
  return data;
}

i18n Destekli Edge API Route'u

Minimum soğuk başlangıç süresiyle edge fonksiyonlarından çevrilmiş API yanıtları döndürün.

app/api/translate/route.ts
// app/api/translate/route.ts — Edge API route with i18n
import { getEdgeMessages } from '@/lib/edge-messages';

export const runtime = 'edge';

export async function GET(request: Request) {
  const url = new URL(request.url);
  const locale = url.searchParams.get('locale') ?? 'en';
  const key = url.searchParams.get('key') ?? '';

  const messages = await getEdgeMessages(locale, 'api-responses');
  const translated = messages[key] ?? key;

  return Response.json({ text: translated, locale });
}

Rendering

ISR ve Uluslararasılaşma

Hızlı ve her zaman güncel çok dilli sayfalar için Artımlı Statik Yenileme ile i18n'yi birleştirin.

PublishR2 writeCDN purgeRevalidatePage served
revalidate = 3600app/[locale]/layout.tsx~60 min
revalidate = 1800app/[locale]/[slug]/page.tsx~30 min
revalidatePath()app/api/revalidate/route.ts< 1 min

Hızlı Başlangıç

Sadece birkaç satır kodla Next.js uygulamanıza i18n ekleyin.

app/[locale]/page.tsx
// app/[locale]/page.tsx
import { getTranslations } from 'next-intl/server';

export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: 'home' });

  return (
    <main>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </main>
  );
}
app/[locale]/layout.tsx
// app/[locale]/layout.tsx — ISR with i18n
import { getMessages } from '@better-i18n/next/server';
import { BetterI18nProvider } from '@better-i18n/next/client';

export const revalidate = 3600; // Revalidate every hour

const config = { project: 'your-org/your-project', defaultLocale: 'en' };

export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: Promise<{ locale: string }>;
}) {
  const { locale } = await params;
  const messages = await getMessages(config, locale);

  return (
    <BetterI18nProvider locale={locale} messages={messages} config={config}>
      {children}
    </BetterI18nProvider>
  );
}

generateStaticParams ile ISR

Her yerel ayar için sayfaları build sırasında önceden oluşturun, ardından ISR ile belirli bir programa göre yenileyin.

app/[locale]/[slug]/page.tsx
// app/[locale]/[slug]/page.tsx — Generate static pages per locale
import { getMessages } from '@better-i18n/next/server';

const config = { project: 'your-org/your-project', defaultLocale: 'en' };

export async function generateStaticParams() {
  const locales = ['en', 'de', 'fr', 'ja'];
  const slugs = await fetchAllSlugs();
  return locales.flatMap((locale) =>
    slugs.map((slug) => ({ locale, slug }))
  );
}

export const revalidate = 1800; // ISR: refresh every 30 min

export default async function Page({
  params,
}: {
  params: Promise<{ locale: string; slug: string }>;
}) {
  const { locale, slug } = await params;
  const messages = await getMessages(config, locale, { namespaces: ['blog'] });
  return <article><h1>{messages.blog[slug + '.title']}</h1></article>;
}

Talebe Bağlı Yeniden Doğrulama

Çeviriler güncellendiğinde ISR yeniden doğrulamasını tetikleyin — Better I18N'in yayınlama webhook'una bağlanın.

app/api/revalidate/route.ts
// app/api/i18n/revalidate/route.ts — On-demand ISR for translation updates
import { createRevalidateHandler } from '@better-i18n/next/revalidate';

// Called by the Better i18n publish webhook — verifies the HMAC signature,
// then revalidates the paths/tags below.
export const POST = createRevalidateHandler({
  secret: process.env.BETTER_I18N_WEBHOOK_SECRET!,
  revalidatePaths: ['/'],
  revalidateTags: ['i18n-messages'],
});

Advanced

Gelişmiş Desenler

İç içe geçmiş düzenler, paralel rotalar ve tür güvenli çevirilere sahip sunucu eylemleri.

app/[locale]/dashboard/layout.tsx
// app/[locale]/dashboard/layout.tsx — Nested layout with namespace
import { getMessages } from '@better-i18n/next/server';
import { BetterI18nProvider } from '@better-i18n/next/client';
import { DashboardNav } from '@/components/DashboardNav';

const config = { project: 'your-org/your-project', defaultLocale: 'en' };

export default async function DashboardLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: Promise<{ locale: string }>;
}) {
  const { locale } = await params;
  // Load the dashboard-specific namespace alongside common messages
  const messages = await getMessages(config, locale, {
    namespaces: ['common', 'dashboard'],
  });

  return (
    <BetterI18nProvider locale={locale} messages={messages} config={config}>
      <DashboardNav />
      <main>{children}</main>
    </BetterI18nProvider>
  );
}

i18n ile Paralel Route'lar

Modüler ve yerel ayara duyarlı düzenler için çevirileri paralel route slotlarında bağımsız olarak yükleyin.

app/[locale]/@analytics/page.tsx
// app/[locale]/@analytics/page.tsx — Parallel route with i18n
import { getTranslations } from 'next-intl/server';

export default async function AnalyticsSlot({
  params,
}: {
  params: Promise<{ locale: string }>;
}) {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: 'analytics' });

  return (
    <section aria-label={t('title')}>
      <h2>{t('title')}</h2>
      <p>{t('description')}</p>
    </section>
  );
}

// app/[locale]/layout.tsx — Consuming parallel routes
export default function Layout({
  children,
  analytics,
  notifications,
}: {
  children: React.ReactNode;
  analytics: React.ReactNode;
  notifications: React.ReactNode;
}) {
  return (
    <div>
      <main>{children}</main>
      <aside>{analytics}</aside>
      <aside>{notifications}</aside>
    </div>
  );
}

Çeviri Destekli Server Action'lar

Server action'lardan çevrilmiş doğrulama hataları ve başarı mesajları döndürün.

app/[locale]/contact/actions.ts
// app/[locale]/contact/actions.ts — Server action with i18n
'use server';
import { getTranslations } from 'next-intl/server';
import { headers } from 'next/headers';

export async function submitContactForm(formData: FormData) {
  const headersList = await headers();
  // Set by createBetterI18nMiddleware — see the routing section above
  const locale = headersList.get('x-locale') ?? 'en';
  const t = await getTranslations({ locale, namespace: 'contact' });

  const email = formData.get('email') as string;
  const message = formData.get('message') as string;

  if (!email || !message) {
    return { error: t('validation.required') };
  }

  try {
    await sendEmail({ email, message, locale });
    return { success: t('form.success') };
  } catch {
    return { error: t('form.error') };
  }
}

// app/[locale]/contact/page.tsx — Using the server action
'use client';
import { useTranslations } from 'next-intl';
import { submitContactForm } from './actions';

export default function ContactPage() {
  const t = useTranslations('contact');

  return (
    <form action={submitContactForm}>
      <label>{t('form.email')}</label>
      <input name="email" type="email" required />
      <label>{t('form.message')}</label>
      <textarea name="message" required />
      <button type="submit">{t('form.submit')}</button>
    </form>
  );
}

Yaygın i18n Sorunlarını Giderme

Hidrasyon uyumsuzluklarını, eksik yerel ayar yedeklerini ve tarih/sayı biçimlendirme farklılıklarını düzeltin.

// Fix: Hydration mismatch with date/number formatting
// Problem: Server renders "1,000" but client renders "1.000"
// Solution: BetterI18nProvider already passes an explicit timeZone down to
// NextIntlClientProvider, so server and client share the same formatting locale.

// app/[locale]/layout.tsx
import { getFormatter } from 'next-intl/server';

export default async function Layout({ children, params }: {
  children: React.ReactNode;
  params: Promise<{ locale: string }>;
}) {
  const { locale } = await params;
  // Pre-format on server with the explicit locale
  const format = await getFormatter({ locale });

  return (
    <html lang={locale} suppressHydrationWarning>
      <body>{children}</body>
    </html>
  );
}

// components/Price.tsx — Client component
'use client';
import { useFormatter } from 'next-intl';

export function Price({ amount }: { amount: number }) {
  const format = useFormatter();
  // useFormatter automatically uses the locale/timeZone from BetterI18nProvider
  // ensuring server and client render the same output
  return <span>{format.number(amount, { style: 'currency', currency: 'USD' })}</span>;
}

Yerel Ayar Yedekleme Zinciri

pt-BR gibi bölgesel varyantların önce pt'ye, sonra en'e geri düşmesi için yedekleme zincirleri tanımlayın.

lib/i18n-config.ts
// lib/i18n-config.ts — Locale fallback chain
const FALLBACK_CHAIN: Record<string, string[]> = {
  'pt-BR': ['pt', 'en'],
  'zh-TW': ['zh-CN', 'en'],
  'en-GB': ['en'],
  'de-AT': ['de', 'en'],
};

export function resolveMessages(
  locale: string,
  allMessages: Record<string, Record<string, string>>
): Record<string, string> {
  const chain = FALLBACK_CHAIN[locale] ?? ['en'];
  const primary = allMessages[locale] ?? {};

  // Merge fallback messages (primary overrides fallbacks)
  return chain.reduceRight(
    (merged, fallbackLocale) => ({
      ...merged,
      ...(allMessages[fallbackLocale] ?? {}),
    }),
    primary
  );
}

Tutarlı Tarih Biçimlendirme

timeZone'u açıkça UTC olarak ayarlayarak sunucu/istemci tarih uyuşmazlıklarından kaçının.

components/LocalizedDate.tsx
// components/LocalizedDate.tsx — Consistent date formatting
'use client';
import { useFormatter, useLocale } from 'next-intl';

export function LocalizedDate({ date }: { date: Date | string }) {
  const format = useFormatter();
  const locale = useLocale();
  const dateObj = typeof date === 'string' ? new Date(date) : date;

  return (
    <time dateTime={dateObj.toISOString()}>
      {format.dateTime(dateObj, {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        // Explicitly set timeZone to avoid server/client mismatch
        timeZone: 'UTC',
      })}
    </time>
  );
}

Capabilities

Özellikler

App Router & Pages Router desteği
Otomatik dil algılama için Middleware
React Server Components desteği
generateStaticParams ile statik oluşturma
Artımlı Statik Yenileme (ISR)
Tip güvenli çeviriler
Edge CDN dağıtımı
hreflang ile SEO optimizasyonu
Dil bazlı yönlendirme
Integrations

Popüler Next.js i18n kütüphaneleriyle çalışır

Better I18N, favori i18n kütüphanenizin yerini almaz — onları daha güçlü kılan çeviri yönetim katmanıdır.

Better I18N + next-intl

App Router desteği, tip güvenli mesajlar ve ICU söz dizimi ile en popüler Next.js i18n kütüphanesi.Better I18N, çevirileri doğrudan next-intl JSON formatına senkronize eder. Panelimizde yönetin, CDN üzerinden anında dağıtın.

Better I18N + next-i18next

i18next tabanlı, kanıtlanmış Next.js i18n kütüphanesi. Pages Router ve App Router'a geçiş için idealdir.i18next uyumlu namespace'li JSON'a aktarın. Better I18N çeviri iş akışını, next-i18next runtime'ı yönetir.

Better I18N + Lingui

Mükemmel DX ve otomatik mesaj çıkarma özellikli hafif, makro tabanlı i18n kütüphanesi.Lingui CLI ile mesajları çıkarın, Better I18N'de çevirileri yönetin, GitHub entegrasyonuyla otomatik senkronize edin.

Diğer Framework Rehberlerini Keşfedin

Next.js i18n ile İnşa Etmeye Başlayın

Ücretsiz plan mevcut. Kredi kartı gerekmez.