Treceți la conținut
Next.js i18n

Next.js i18n cu App Router

Componente de server, ISR și traduceri optimizate pentru aplicații Next.js.

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

Instalare

Adaugă @better-i18n/next și next-intl în proiectul tău.

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

Adaugă middleware pentru detectarea locale-ului

Middleware-ul citește header-ul Accept-Language și prefixul URL-ului pentru a detecta locale-ul utilizatorului și a redirecționa în consecință.

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).*)'] };

Încarcă mesajele într-un Server Component

Folosește getMessages() în layout-ul rădăcină pentru a prelua traducerile pe server și a le transmite către BetterI18nProvider.

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>
  );
}

Folosește traducerile în Client Components

Apelează useTranslations() în orice Client Component. Mesajele sunt deja hidratate de pe server — fără un fetch suplimentar.

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

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

Routing

Edge Runtime și detectarea localizării

Rulați detectarea localizării și încărcarea mesajelor la margine pentru timpi de răspuns sub 50 ms în întreaga lume.

Configurarea middleware-ului

Adaugă detectarea locale-ului și rutarea în aplicația ta Next.js cu un singur fișier middleware.

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).*)'],
};

Încărcare de mesaje compatibilă cu Edge

Pune traducerile în cache la edge folosind un cache TTL ușor, în memorie, pentru răspunsuri instantanee.

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;
}

Rută API Edge cu i18n

Returnează răspunsuri API traduse din funcții edge cu un cold start minim.

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 și internaționalizare

Combinați regenerarea statică incrementală cu i18n pentru pagini multilingve rapide și mereu actualizate.

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

Start rapid

Adaugă i18n în aplicația ta Next.js cu doar câteva linii de cod.

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>
  );
}

ISR cu generateStaticParams

Pre-randează paginile pentru fiecare locale la momentul build-ului, apoi reîmprospătează-le periodic cu ISR.

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>;
}

Revalidare la cerere

Declanșează revalidarea ISR când traducerile sunt actualizate — conectează-te la webhook-ul de publicare Better I18N.

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

Modele avansate

Layouturi imbricate, rute paralele și acțiuni de server cu traduceri sigure din punct de vedere tipografic.

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>
  );
}

Rute paralele cu i18n

Încarcă traducerile independent în sloturile rutelor paralele pentru layout-uri modulare, adaptate la locale.

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>
  );
}

Server Actions cu traducere

Returnează erori de validare traduse și mesaje de succes din server actions.

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>
  );
}

Depanarea problemelor comune legate de i18n

Remediați neconcordanțele de hidratare, lipsurile de soluții alternative locale și diferențele de formatare a datei/numărului.

// 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>;
}

Lanț de fallback pentru locale

Definește lanțuri de fallback astfel încât variantele regionale precum pt-BR să revină la pt, apoi la en.

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
  );
}

Formatare consecventă a datelor

Evită discrepanțele de dată server/client setând explicit timeZone la UTC.

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

Caracteristici

Suport pentru App Router și Pages Router
Middleware pentru detectarea automată a localităților
Suport pentru componentele React Server
Generarea statică cu generateStaticParams
Regenerare statică incrementală
Traduceri tip-safe
Livrare CDN de margine
SEO-optimizat cu hreflang
Rutare pe bază locală
Integrations

Funcționează cu bibliotecile i18n populare pentru Next.js

Better I18N nu înlocuiește biblioteca i18n favorită — este stratul de gestionare a traducerilor care le face și mai puternice.

Better I18N + next-intl

Cea mai populară bibliotecă i18n pentru Next.js cu suport App Router, mesaje type-safe și sintaxă ICU.Better I18N sincronizează traducerile direct în formatul JSON next-intl. Gestionați din dashboard, implementați instant prin CDN.

Better I18N + next-i18next

Bibliotecă i18n testată în luptă pentru Next.js bazată pe i18next. Ideală pentru Pages Router și migrarea la App Router.Export în JSON cu namespace compatibil i18next. Better I18N gestionează fluxul de traducere, next-i18next gestionează runtime-ul.

Better I18N + Lingui

Bibliotecă i18n ușoară bazată pe macro-uri cu DX excelent și extracție automată a mesajelor.Extrageți mesaje cu Lingui CLI, gestionați traducerile în Better I18N, sincronizați automat prin integrarea GitHub.

Explorați alte ghiduri pentru framework-uri

Începeți să construiți cu Next.js i18n

Nivel gratuit disponibil. Nu este necesar un card de credit.