In this article, I will illustrate how to implement next-intl localization in a Next.js 15 application.
Localization is a crucial feature for any application aiming to cater to a global audience, ensuring that users can interact with content in their preferred language.
Overview
- Packages
- How to install and configure next-intl?
Step 1: Create a fresh Next.js project.
Step 2: Install the next-intl package.
Step 3: Adding the locale route segment.
Step 4: Adding the translation message files.
Step 5: Setting up configuration files.
Step 6: Create a localization middleware.
Step 7: How to retrieve translated contents. - Demo
- Conclusion
Packages
Lib/technology | Version | Description |
---|---|---|
Typescript | 5.4.5 | Our programming language |
Next.js | 15.0.0 | The fullstack react framework |
React | 18.3.1 | For building user interface |
next-intl | 3.15.3 | i18n lib for Next.js app router |
rtl-detect | 1.1.2 | Detects right to left languages |
Tailwind CSS | 3.4.3 | Utility-first CSS framework for styling |
@mui/material | 5.15.20 | Material design UI component lib |
How to install and configure next-intl?
Step 1: Create a fresh Next.js project.
npx create-next-app i18n-next-app cd i18n-next-app yarn add next@latest
Step 2: Install the next-intl package:
yarn add next-intl
Step 3: Adding the locale route segment
*** First, we need to follow the documents about locale codes and dynamic route segments.
Next, the subdirectory called [locale] is created under src/app.

***Note, the module import should be updated if needed.
Step 4: Adding the translation message files
First, create a new directory called locales under src
In this tutorial, we are supporting en-us (English USA) and vi (Vietnamese).

Continue, add the content for translation files:
src/locales/en-us/about.ts
export default {
title: 'Title',
description: 'Description',
} as const;
src/locales/vi/about.ts
export default {
title: 'Tiêu đề',
description: 'Miêu tả',
} as const;
src/locales/vi/index.ts
src/locales/vi/index.ts
import about from './about';
export default { about };
Step 5: Setting up configuration files
next.config.mjs
import createNextIntlPlugin from "next-intl/plugin";
const withNextIntl = createNextIntlPlugin();
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default withNextIntl(nextConfig);
Create a new file called
i18n.config.ts
import { createSharedPathnamesNavigation } from 'next-intl/navigation';
export const locales = ['en-us', 'vi'] as const;
export type Locale = (typeof locales)[number];
export const localeNames: Record<Locale, string> = {
'en-us': 'Tiếng anh',
vi: 'Vietnamese',
};
export const { Link, usePathname, useRouter } = createSharedPathnamesNavigation({ locales });
The next-intl uses the i18n.ts file to load translations.
src/global.d.ts
type Messages = (typeof import('./locales/en-us'))['default'];
declare interface IntlMessages extends Messages {}
src/i18n.ts
import { getRequestConfig } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { type Locale, locales } from ' ./i18n.config';
export default getRequestConfig(async ({ locale }) => {
if (!locales.includes(locale as Locale)) {
return notFound();
}
const messages = await import(`./locales/${locale}/index.ts`);
return {
messages: messages.default,
};
});
Step 6: Create a localization middleware
src/middlewares/with-localization.ts
import createMiddleware from 'next-intl/middleware';
import { NextMiddleware } from 'next/server';
import { locales } from '../i18n.config';
import { MiddlewareFactory } from './types';
export const withLocalization: MiddlewareFactory = (next: NextMiddleware) =>
createMiddleware({
defaultLocale: 'vi',
locales,
localeDetection: false,
});
src/middleware.ts
import { stackMiddlewares } from './middlewares/stack-middlewares';
import { withLocalization } from './middlewares/with-localization';
export default stackMiddlewares([
withLocalization,
]);
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
Step 7: How to retrieve translated contents
src/hooks/useTextDirection.ts
import { useLocale } from 'next-intl';
import { isRtlLang } from 'rtl-detect';
export type TextDirection = 'ltr' | 'rtl';
export default function useTextDirection(): TextDirection {
const locale = useLocale();
return isRtlLang(locale) ? 'rtl' : 'ltr';
}
src/[locale]/layout.tsx
import { Metadata, Viewport } from 'next';
import { NextIntlClientProvider, useMessages } from 'next-intl';
import { unstable_setRequestLocale } from 'next-intl/server';
import { locales } from './src/i18n.config';
import useTextDirection from '@/hooks/useTextDirection';
import './globals.css';
export async function generateMetadata(): Promise<Metadata> {
return {
title: {
default: site.header,
template: `${site.header} | %s`,
},
};
}
export function generateStaticParams() {
return locales.map((locale) => ({ locale }));
}
export default function RootLayout({
children,
params: { locale },
}: Readonly<{
children: React.ReactNode;
params: { locale: string };
}>) {
unstable_setRequestLocale(locale);
const dir = useTextDirection();
const messages = useMessages();
return (
<html lang={locale} dir={dir} className={combineClasses([roboto.className])}>
<head></head>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
src/app/[locale]/about.page.tsx
import { getTranslations } from 'next-intl/server';
export async function generateMetadata({
params: { locale },
}: {
params: { locale: string };
}): Promise<Metadata> {
const t = await getTranslations({
locale,
});
return {
title: t('about.title'),
};
}
export default async function About({ params: { locale } }: { params: { locale: string } }) {
const t = useTranslations('about');
return (
<Layout>
{t('description')}
</Layout>
);
}
Demo
Vietnamese: https://www.dinhthanhcong.info/vi/about

English: https://dinhthanhcong.info/en-us/about

Conclusion
By following these guidelines, you can build a Next.js 15 application that meets the technical requirements of localization and delivers a superior user experience for a diverse audience.
Good luck to you, I hope this post is of value to you!!!!
Reference Documents: