Skip to main content
Version: 5.x.x

Next.js with App Router

tip
You can also check the Next app router example application to learn more.

Follow the below instructions to learn to implement localization to your Next.js App Router app with Tolgee.

In this example we'll store language in cookies, which is a simple to implement, but is not ideal for SEO. If you want a more robust solution with language in url, see App router with next-intl

Prerequisites

  1. An existing Next.js project.
  2. An existing project on Tolgee platform with at least 2 languages. This guide uses English (en) and Czech (cs).
  3. Add localization keys and translations for both the languages. This guide uses the key name hello_world.
  4. API key of your Tolgee project.

Install the required packages

To implement localization to your app, you need to install the @tolgee/react package:

npm install @tolgee/react
info

Use @tolgee/react version 5.30.0 and higher

Folder structure

The folder structure of your project should resemble the following:

├── .env.development.local    # ignored by git
├── next.config.js
├── messages
│ ├── en.json
│ └── cs.json
└── src
├── tolgee
│ ├── shared.ts
│ ├── language.ts
│ ├── client.tsx
│ └── server.tsx
└── app
├── layout.tsx
└── page.tsx

Set up your environment

Create the .env.development.local file if it does not exist. You will store the Tolgee credentials securely in this file.

Paste the following in the newly created file. Replace <your api key> with your Tolgee API key.

.env.development.local
NEXT_PUBLIC_TOLGEE_API_KEY=<your api key>
NEXT_PUBLIC_TOLGEE_API_URL=https://app.tolgee.io

Save exported data

Create an messages folder in the root of your project directory, if it does not already exists. Move the exported localization json files to the messages folder.

Set up Tolgee

You need to set up Tolgee for both the client and the server. You can create shared configuration that can be used for both client and server.

Shared configuration

In your tolgee/shared.ts file, add the following code.

src/tolgee/shared.ts
import { DevTools, Tolgee, FormatSimple } from '@tolgee/web';

const apiKey = process.env.NEXT_PUBLIC_TOLGEE_API_KEY;
const apiUrl = process.env.NEXT_PUBLIC_TOLGEE_API_URL;

export const ALL_LANGUAGES = ['en', 'cs'];

export const DEFAULT_LANGUAGE = 'en';

export async function getStaticData(
languages: string[],
namespaces: string[] = ['']
) {
const result: Record<string, any> = {};
for (const lang of languages) {
for (const namespace of namespaces) {
if (namespace) {
result[`${lang}:${namespace}`] = (
await import(`../../messages/${namespace}/${lang}.json`)
).default;
} else {
result[lang] = (await import(`../../messages/${lang}.json`)).default;
}
}
}
return result;
}

export function TolgeeBase() {
return Tolgee().use(FormatSimple()).use(DevTools()).updateDefaults({
apiKey,
apiUrl,
});
}

Client configutation

The client configuration is very similar to the Pages Router set up. It serves the purpose of translating client components and also enables the in-context functionality for server-rendered components.

src/tolgee/client.tsx
'use client';

import { TolgeeBase } from './shared';
import { TolgeeProvider, TolgeeStaticData } from '@tolgee/react';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';

type Props = {
language: string;
staticData: TolgeeStaticData;
children: React.ReactNode;
};

const tolgee = TolgeeBase().init();

export const TolgeeNextProvider = ({
language,
staticData,
children,
}: Props) => {
const router = useRouter();

useEffect(() => {
const { unsubscribe } = tolgee.on('permanentChange', () => {
router.refresh();
});
return () => unsubscribe();
}, [tolgee, router]);

return (
<TolgeeProvider
tolgee={tolgee}
options={{ useSuspense: false }}
fallback="Loading"
ssr={{ language, staticData }}
>
{children}
</TolgeeProvider>
);
};

Storing language in cookies

To store user language create file language.ts, important part here is the "use server" directive, so we can call setLanguage method from frontend as server action.

src/tolgee/language.ts
'use server';

import { detectLanguageFromHeaders } from '@tolgee/react/server';
import { cookies, headers } from 'next/headers';
import { ALL_LANGUAGES, DEFAULT_LANGUAGE } from './shared';

const LANGUAGE_COOKIE = 'NEXT_LOCALE';

export async function setLanguage(locale: string) {
const cookieStore = cookies();
cookieStore.set(LANGUAGE_COOKIE, locale, {
maxAge: 1000 * 60 * 60 * 24 * 365, // one year in milisecods
});
}

export async function getLanguage() {
const cookieStore = cookies();
const locale = cookieStore.get(LANGUAGE_COOKIE)?.value;
if (locale && ALL_LANGUAGES.includes(locale)) {
return locale;
}

// try to detect language from headers or use default
const detected = detectLanguageFromHeaders(headers(), ALL_LANGUAGES);
return detected || DEFAULT_LANGUAGE;
}

Server configuration

Your app will utilize the React server cache for sharing Tolgee instance across components in a single render. This allows the app to use the Tolgee instance anywhere in the server components.

One important difference from the client setup is the utilization of fullKeyEncode. fullKeyEncode ensures that translations rendered on the server are correctly picked up and interactive for in-context mode.

src/tolgee/server.tsx
import { TolgeeBase, ALL_LANGUAGES, getStaticData } from './shared';
import { createServerInstance } from '@tolgee/react/server';
import { getLanguage } from './language';

export const { getTolgee, getTranslate, T } = createServerInstance({
getLocale: getLanguage,
createTolgee: async (locale) =>
TolgeeBase().init({
// including all locales
// on server we are not concerned about bundle size
staticData: await getStaticData(ALL_LANGUAGES),
observerOptions: {
fullKeyEncode: true,
},
language: locale,
fetch: async (input, init) =>
fetch(input, { ...init, next: { revalidate: 0 } }),
}),
});

Use the TolgeeNextProvider

The next step is to wrap the children with the TolgeeNextProvider. Update the layout.tsx file with the following code:

src/app/layout.tsx
import { ReactNode } from 'react';
import { TolgeeNextProvider } from '@/tolgee/client';
import { getStaticData } from '@/tolgee/shared';
import { getLanguage } from '@/tolgee/language';
import './style.css';

type Props = {
children: ReactNode;
params: { locale: string };
};

export default async function LocaleLayout({ children }: Props) {
const locale = await getLanguage();
// it's important you provide all data which are needed for initial render
// so current language and also fallback languages + necessary namespaces
const staticData = await getStaticData([locale]);

return (
<html lang={locale}>
<body>
<TolgeeNextProvider language={locale} staticData={staticData}>
{children}
</TolgeeNextProvider>
</body>
</html>
);
}

The above code loads relevant locale available on the server and pass it to the client component through props.

info

Make sure you are not using export const dynamic = 'force-static' on your pages, as that makes it impossible to use cookies (more info).

Localizing Server components

Now that everything is setup, you can use the getTranslate function to render the translated text. Since this is a server component, you use getTranslate instead of the useTranslate hook.

src/page.tsx
import { getTranslate } from '@/tolgee/server';

export default async function IndexPage() {
// because this is server component, use `getTranslate`
// not useTranslate from '@tolgee/react'
const t = await getTranslate();
return (
<main>
<h1>{t('hello_world')}</h1>
</main>
);
}

If everything is set up correctly, you could use in-context translation. Press and holde the alt (or option) key, and click hello_world. This will open the in-context pop-up window.

Localizing Client components

For client components, you can use the useTranslate hook or the t-component.

'use client';

import { useTranslate } from '@tolgee/react';

export const ExampleClientComponent = () => {
const { t } = useTranslate();

return (
<section>
<span>{t('example-key-in-client-component')}</span>
</section>
);
};
info

Make sure to use @/tolgee/server in server components and @tolgee/react in client components.

Switching Languages

For switching languages use setLanguage server action from language.ts, which will just update our cookie.

'use client';

import React from 'react';
import { useTolgee } from '@tolgee/react';
import { setLanguage } from '@/tolgee/language';

export const LangSelector: React.FC = () => {
const tolgee = useTolgee(['language']);
const language = tolgee.getLanguage();

function onSelectChange(e) {
setLanguage(e.target.value);
}

return (
<select onChange={onSelectChange} value={language}>
<option value="en">🇬🇧 English</option>
<option value="cs">🇨🇿 Česky</option>
</select>
);
};

Limitations of Server Components

Although in-context translation works with server components, there are some limitations compared to client components. The Tolgee cache on the server is separate. This prevents Tolgee from automatically changing the translation when creating a screenshot (unlike with client components, which swap the content if you've modified it in a dialog).

Furthermore, if you're using the Tolgee browser plugin, it won't affect the server's transition to dev mode. As a result, only the client switches, leaving server components non-editable in this mode.

tip
You can also check the Next app router example application to learn more.