Next.js i18n s aplikací App Router
Komponenty serveru, ISR a překlady optimalizované pro aplikace Next.js.
middlewaremiddleware.tsgetRequestConfigi18n/request.tsgetMessages()app/[locale]/page.tsxuseTranslations()components/Hero.tsxSetup
Set up in 4 steps
Instalace
Přidejte @better-i18n/next a next-intl do svého projektu.
npm install @better-i18n/next next-intlPřidejte middleware pro detekci jazykové verze
Middleware čte hlavičku Accept-Language a prefix URL, aby zjistil jazykovou verzi uživatele a odpovídajícím způsobem přesměroval.
import { createBetterI18nMiddleware } from '@better-i18n/next';
export default createBetterI18nMiddleware({
project: 'your-org/your-project',
defaultLocale: 'en',
localePrefix: 'always',
});
export const config = { matcher: ['/((?!api|_next).*)'] };Načtěte zprávy v Server Component
Použijte getMessages() ve svém root layoutu k načtení překladů na straně serveru a jejich předání do BetterI18nProvider.
// 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>
);
}Používejte překlady v Client Components
Zavolejte useTranslations() v jakékoli Client Component. Zprávy jsou už hydratovány ze serveru — žádný další fetch není potřeba.
'use client';
import { useTranslations } from 'next-intl';
export function HeroSection() {
const t = useTranslations('home');
return <h1>{t('title')}</h1>;
}Routing
Edge Runtime a detekce národního prostředí
Spusťte detekci locale a načítání zpráv na okraji s odezvou pod 50 ms po celém světě.
Nastavení middleware
Přidejte detekci jazykové verze a směrování do své Next.js aplikace pomocí jediného souboru middleware.
// 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 — 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).*)'],
};Načítání zpráv kompatibilní s Edge
Ukládejte překlady do mezipaměti na edge pomocí lehké in-memory TTL cache pro okamžité odpovědi.
// 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;
}Edge API Route s i18n
Vracejte přeložené API odpovědi z edge funkcí s minimálním cold startem.
// 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 a internacionalizace
Kombinujte inkrementální statickou regeneraci s i18n pro rychlé a vždy aktuální vícejazyčné stránky.
revalidate = 3600app/[locale]/layout.tsx~60 minrevalidate = 1800app/[locale]/[slug]/page.tsx~30 minrevalidatePath()app/api/revalidate/route.ts< 1 minRychlý start
Přidejte i18n do své Next.js aplikace jen s několika řádky kódu.
// 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 — 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 s generateStaticParams
Předrenderujte stránky pro každou jazykovou verzi při buildu a poté je pravidelně obnovujte pomocí ISR.
// 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>;
}Revalidace na vyžádání
Spusťte revalidaci ISR při aktualizaci překladů — napojte se na publikační webhook Better I18N.
// 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
Pokročilé vzory
Vnořené rozvržení, paralelní trasy a akce serveru s typově bezpečnými překlady.
// 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>
);
}Paralelní trasy s i18n
Načítejte překlady nezávisle ve slotech paralelních tras pro modulární layouty citlivé na jazykovou verzi.
// 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 s překladem
Vracejte přeložené chyby validace a zprávy o úspěchu ze server actions.
// 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>
);
}Řešení běžných problémů s i18n
Opravte nesrovnalosti v hydrataci, chybějící záložní lokální nastavení a rozdíly ve formátování datumu/čísel.
// 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>;
}Řetězec záložních jazykových verzí
Definujte řetězce záložních jazyků, aby se regionální varianty jako pt-BR vracely k pt a poté k en.
// 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
);
}Konzistentní formátování data
Vyhněte se nesouladu data mezi serverem a klientem explicitním nastavením timeZone na UTC.
// 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
Funkce
Funguje s populárními i18n knihovnami pro Next.js
Better I18N nenahrazuje vaši oblíbenou i18n knihovnu — je to vrstva správy překladů, která je činí ještě výkonnějšími.
Better I18N + next-intl
Nejpopulárnější i18n knihovna pro Next.js s podporou App Router, typově bezpečnými zprávami a ICU syntaxí.Better I18N synchronizuje překlady přímo do JSON formátu next-intl. Spravujte v dashboardu, nasaďte okamžitě přes CDN.
Better I18N + next-i18next
Osvědčená i18n knihovna pro Next.js založená na i18next. Ideální pro Pages Router a migraci na App Router.Export do i18next kompatibilního JSON s namespace. Better I18N řídí překladový workflow, next-i18next řídí runtime.
Better I18N + Lingui
Lehká, na makrech založená i18n knihovna s vynikajícím DX a automatickou extrakcí zpráv.Extrahujte zprávy pomocí Lingui CLI, spravujte překlady v Better I18N, synchronizujte automaticky přes GitHub integraci.
Related Articles
en vs en-US: How to Name Locale Files Without Breaking Your i18n Pipeline
You are setting up i18n. You create the folder. Then you stop, because you have to name the first file and there are two obvious answers: locales/en.json...
Read More →Introducing the Better i18n CLI: Manage Translations from Your Terminal
Until now, managing translation keys on Better i18n meant using the dashboard UI or connecting an MCP server to your AI assistant. Both work great — but...
Read More →How to Add i18n to Shopify Hydrogen (Complete Guide)
Shopify Hydrogen is the modern way to build custom storefronts — but adding internationalization (i18n) can be challenging. You need locale-aware routing,...
Read More →Související
Prohlédněte si další průvodce po frameworku
Začněte tvořit s Next.js i18n
K dispozici je bezplatná úroveň. Není vyžadována kreditní karta.