
When you want to reach a broader audience, open the door to international opportunities, or simply design a product with a global scope, offering a multi-language application quickly becomes a necessity.
Yet, internationalization (i18n) remains the kind of feature we tend to push back. In the React ecosystem, we immediately think of heavy configuration files, hard-to-maintain bloatware, and unnecessary weight added to the client-side JavaScript bundle.
With the arrival of Next.js and the App Router, the way we handle i18n has evolved dramatically. The goal is simple: achieve seamless, high-performance, and 100% type-safe bilingual or multilingual management without cluttering the architecture.
Rather than opting for legacy and heavier solutions like react-i18next or next-intl, next-international stands out as an excellent alternative for modern projects for several reasons:
as const). If a translation is missing or contains a typo in the code, the build fails immediately.pnpm add next-internationalFirst, we define the translation files in TypeScript. The first dictionary serves as the reference for global typing:
// locales/fr.ts
export default {
'hero.title': 'Développeur fullstack, UI/UX designer',
'hero.subtitle': 'Création d\'interfaces modernes et performantes.',
'nav.projects': 'Projets',
} as const// locales/en.ts
export default {
'hero.title': 'Fullstack developer, UI/UX designer',
'hero.subtitle': 'Building modern and performant interfaces.',
'nav.projects': 'Projects',
} as constWe generate the appropriate utilities for each environment (server and client):
// locales/server.ts
import { createI18nServer } from 'next-international/server'
export const { getI18n, getScopedI18n, getStaticParams } = createI18nServer({
fr: () => import('./fr'),
en: () => import('./en'),
})// locales/client.ts
import { createI18nClient } from 'next-international/client'
export const { useI18n, useScopedI18n, I18nProviderClient } = createI18nClient({
fr: () => import('./fr'),
en: () => import('./en'),
})To intercept requests, detect the user's preferred language, and handle redirection in Next.js, we configure proxy.ts at the root:
// proxy.ts
import { createI18nMiddleware } from 'next-international/middleware'
import { NextRequest } from 'next/server'
const I18nMiddleware = createI18nMiddleware({
locales: ['fr', 'en'],
defaultLocale: 'fr',
})
export function proxy(request: NextRequest) {
return I18nMiddleware(request)
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}On the server side, the getI18n() function is used asynchronously without needing a Provider:
// app/[locale]/page.tsx
import { getI18n } from '@/locales/server'
export default async function HomePage() {
const t = await getI18n()
return (
<main>
<h1>{t('hero.title')}</h1>
<p>{t('hero.subtitle')}</p>
</main>
)
}Like any architectural decision, next-international answers a specific need and involves trade-offs:
next-international successfully fulfills the promise of making internationalization simple and intuitive. By stripping away unnecessary boilerplate and putting type-safety at the heart of development, the library lets you add multi-language support to a Next.js application in just a few minutes.
It is precisely the kind of modern tool developers appreciate: lightweight, fast, and getting out of the way to serve the end-user experience.