Internationalization with I18n in NextJs projects
In this article, I will try to be brief and quickly and clearly teach how to add internationalization to a NextJs project, following the example used on my own website.
For this tutorial, we will use the app router without using i18n routing (that is, the translation will not be tied to the route, example: "example.com/en").
Additionally, we will use a cookie so that the website can be delivered in the correct language to the user. This cookie will be set on the user's first load of the website and can be reset later by a selector component.
Project Setup
If you haven't already, start your Next.Js project with the App Router configuration.
Next, install the next-intl
package:
npm install next-intl
Then, we will create the following file structure:
├── i18n
├── dictionaries
├── en.json (1)
└── ...
├── locales.ts (2)
├── request.ts (3)
└── setLocale.ts (4)
├── next.config.mjs (5)
└── app
├── layout.tsx (6)
└── page.tsx (7)
i18n/dictionaries/en.json
The i18n folder will store everything related to localization and internationalization to facilitate project structuring.
The dictionaries folder will store our translations. These files can be loaded statically or dynamically. For simplicity, we will be adding them as files.
i18n/dictionaries/en.json
{
"HomePage": {
"title": "Hello world!"
}
}
i18n/locales.ts
To store our constants about localization. (This can also be stored dynamically or even become an environment variable)
i18n/locales.ts
export const SUPPORTED_LOCALES = ['en', 'pt']; // Available translations
export const DEFAULT_LOCALE = 'en'; // Default language used as fallback
export const LOCALE_COOKIE_NAME = 'NEXT_LOCALE'; // Cookie key that we will use to get the translation from the server
i18n/request.ts
Next-intl will use the request to define how to proceed with server components that use internationalization. The request file handles the logic to determine the language. Our approach here will be to use cookies to observe the language to be used.
i18n/request.ts
import { getRequestConfig } from 'next-intl/server';
import { cookies } from 'next/headers';
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from './locales';
export default getRequestConfig(async () => {
const cookieLocale = cookies().get('NEXT_LOCALE')?.value;
// Determine the locale
const locale = cookieLocale || DEFAULT_LOCALE;
// Check if it is supported or return the default
const finalLocale = SUPPORTED_LOCALES.includes(locale)
? locale
: DEFAULT_LOCALE;
return {
locale: finalLocale,
messages: (await import(`./dictionaries/${finalLocale}.json`)).default,
};
});
i18n/setLocale.ts
This file will serve as a utility so that we can set cookies on the server side.
i18n/setLocale.ts
'use server';
import { cookies } from 'next/headers';
import { LOCALE_COOKIE_NAME } from './locales';
export async function setUserLocale(locale: string) {
cookies().set(LOCALE_COOKIE_NAME, locale);
}
next.config.mjs
Now, configure the plugin that creates an alias to provide a request-specific i18n configuration to server components (which we created as i18n/request.ts
).
next.config.mjs
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin();
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default withNextIntl(nextConfig);
app/layout.tsx
The language code provided in i18n/request.ts
is available via getLocale
and can be used to set the document's language. Additionally, we can use this location to pass the i18n/request.ts
configuration to client components via NextIntlClientProvider
.
app/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getLocale, getMessages } from 'next-intl/server';
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const locale = await getLocale();
// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
app/page.tsx
To use your translations, simply use the useTranslations
hook in your components and pages!
app/page.tsx
import { useTranslations } from 'next-intl';
export default function HomePage() {
const t = useTranslations('HomePage');
return <h1>{t('title')}</h1>;
}
Changing the language on the first load
Although your project will already work with internationalization from the setup, to be able to change the language, we need to create a client-side component that can read the browser's language and change the cookie. To do this, just create a component as follows:
import { useEffect } from 'react';
import {
SUPPORTED_LOCALES,
DEFAULT_LOCALE,
LOCALE_COOKIE_NAME,
} from '../../i18n/locales';
import { setUserLocale } from '../../i18n/setLocale';
export default function LocaleSetter() {
useEffect(() => {
// Check if the locale cookie already exists
if (document.cookie.includes(LOCALE_COOKIE_NAME)) return;
// Remove the country from the locale (e.g., pt-BR and pt-PT becomes pt)
const navigatorLang = navigator.language.split('-')[0];
let locale = SUPPORTED_LOCALES.includes(navigatorLang)
? navigatorLang
: DEFAULT_LOCALE;
// Change cookie
setUserLocale(locale);
}, []);
return null;
}
Thus, we can include LocaleSetter
in our app/layout.tsx
and our application will already have translations set by the browser's language!
From here, you can also create a selector component and use the setUserLocale
function to have the language redefined!
See you next time ;D
And don't forget to check out other posts on my blog