Going global means more than translation—it's dates, numbers, pluralization, and RTL support. Next.js provides primitives, but a complete i18n implementation requires strategy.
Next.js i18n Routing
With App Router, use middleware to detect locale and redirect:
// middleware.ts
import { match } from '@formatjs/intl-localematcher';
import Negotiator from 'negotiator';
const locales = ['en', 'de', 'ar'];
const defaultLocale = 'en';
export function middleware(request) {
const pathname = request.nextUrl.pathname;
const pathnameHasLocale = locales.some(
(locale) => pathname.startsWith(\`/\${locale}/\`)
);
if (!pathnameHasLocale) {
const locale = getLocale(request);
return NextResponse.redirect(
new URL(\`/\${locale}\${pathname}\`, request.url)
);
}
}Translation Management
i18next is popular and full-featured. Paraglide offers type-safe translations. Next-intl integrates well with App Router.
import { useTranslations } from 'next-intl';
export function Greeting() {
const t = useTranslations('home');
return <h1>{t('welcome')}</h1>;
}Date, Time, and Number Formatting
Use the Intl API for locale-aware formatting:
const formatter = new Intl.DateTimeFormat(locale, {
dateStyle: 'long',
timeStyle: 'short'
});
const currencyFormatter = new Intl.NumberFormat(locale, {
style: 'currency',
currency: 'EUR'
});RTL Support
Set the dir attribute based on locale. Use logical CSS properties (start/end vs left/right). Test with actual RTL content.
<html lang={locale} dir={isRTL(locale) ? 'rtl' : 'ltr'}>Performance: Lazy Loading
Don't load all translations upfront. Load only the current locale, split by route/page, and prefetch adjacent locales.
i18n done right is invisible to users—they just see content in their language with proper formatting. The complexity is in implementation, not user experience.
