id | sidebar_label | title | description |
---|---|---|---|
app-router |
App router |
Next.js with App Router |
Get started with Tolgee's integration for Next.js. Learn how to add localization to your Next.js applications that uses the App Router. |
import ExampleBanner from '../../../shared/ExampleBanner';
The Next.js App Router does not provide native support for i18n
as the Page Router does. However, you can use the next-intl
library, for routing and locale detection.
Follow the below instructions to learn to implement localization to your Next.js App Router app with Tolgee.
- An existing Next.js project.
- An existing project on Tolgee platform with at least 2 languages. This guide uses English (en) and Czech (cs).
- Added localization keys and translations for both the languages in the Tolgee Platform. This guide uses the key name
hello_world
inen
ancs
languages. - Exported localization data in json format and stored in
src/i18n/<lang>.json
- Created API key of your Tolgee Platform project.
To implement localization to your app, you need to install the next-intl
and @tolgee/react
package. Execute the following command to install the package.
npm install next-intl @tolgee/react
:::info
Use @tolgee/react
version 5.17.0
and higher
:::
The folder structure of your project should resemble the following:
├── next.config.js
├── .env.development.local
└── src
├── middleware.ts
├── navigation.ts
├── i18n
│ ├── en.json
│ ├── cs.json
│ └── request.ts
├── tolgee
│ ├── shared.ts
│ ├── client.tsx
│ └── server.tsx
└── app
└── [locale]
├── layout.tsx
└── page.tsx
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.
NEXT_PUBLIC_TOLGEE_API_KEY=<your api key>
NEXT_PUBLIC_TOLGEE_API_URL=https://app.tolgee.io
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.
In your tolgee/shared.ts
file, add the following code.
import { DevTools, Tolgee, FormatSimple } from '@tolgee/web';
export const ALL_LOCALES = ['en', 'cs'];
export const DEFAULT_LOCALE = 'en';
const apiKey = process.env.NEXT_PUBLIC_TOLGEE_API_KEY;
const apiUrl = process.env.NEXT_PUBLIC_TOLGEE_API_URL;
export async function getStaticData(languages: string[]) {
const result: Record<string, any> = {};
for (const lang of languages) {
result[lang] = (await import(`../i18n/${lang}.json`)).default;
}
return result;
}
export function TolgeeBase() {
return (
Tolgee()
.use(FormatSimple())
.use(DevTools())
// Preset shared settings
.updateDefaults({
apiKey,
apiUrl,
})
);
}
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.
'use client';
import { TolgeeBase } from './shared';
import { TolgeeProvider, useTolgeeSSR } from '@tolgee/react';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
type Props = {
locales: any;
locale: string;
children: React.ReactNode;
};
const tolgee = TolgeeBase().init();
export const TolgeeNextProvider = ({ locale, locales, children }: Props) => {
// synchronize SSR and client first render
const tolgeeSSR = useTolgeeSSR(tolgee, locale, locales);
const router = useRouter();
useEffect(() => {
const { unsubscribe } = tolgeeSSR.on('permanentChange', () => {
// refresh page when there is a translation update
router.refresh();
});
return () => unsubscribe();
}, [tolgeeSSR, router]);
return (
<TolgeeProvider tolgee={tolgeeSSR} options={{ useSuspense: false }}>
{children}
</TolgeeProvider>
);
};
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.
import { getLocale } from 'next-intl/server';
import { TolgeeBase, ALL_LOCALES, getStaticData } from './shared';
import { createServerInstance } from '@tolgee/react/server';
export const { getTolgee, getTranslate, T } = createServerInstance({
getLocale,
createTolgee: async (locale) =>
TolgeeBase().init({
// load all languages on the server
staticData: await getStaticData(ALL_LOCALES),
observerOptions: {
fullKeyEncode: true,
},
language: locale,
// using custom fetch to avoid aggressive caching
fetch: async (input, init) => {
const data = await fetch(input, { ...init, next: { revalidate: 0 } });
return data;
},
}),
});
The next step is to wrap the children with the TolgeeNextProvider
. Update the layout.tsx
file with the following code:
import { notFound } from 'next/navigation';
import { ReactNode } from 'react';
import { TolgeeNextProvider } from '@/tolgee/client';
import { ALL_LOCALES, getStaticData } from '@/tolgee/shared';
type Props = {
children: ReactNode;
params: { locale: string };
};
export default async function LocaleLayout({
children,
params: { locale },
}: Props) {
if (!ALL_LOCALES.includes(locale)) {
notFound();
}
// make sure you provide all the necessary locales
// for the inital SSR render (e.g. fallback languages)
const locales = await getStaticData([locale]);
return (
<html lang={locale}>
<body>
<TolgeeNextProvider locale={locale} locales={locales}>
{children}
</TolgeeNextProvider>
</body>
</html>
);
}
The above code loads relevant locale available on the server and pass it to the client component through props.
This example provides translation globally. If you want to provide each page with a different namespace, you can move the provider to the relevant page files.
To use next-intl
for routing and language detection, you need to set middleware
and navigation
. Create a src/middleware.ts
file if it does not exist. Add the following code in this file.
import createMiddleware from 'next-intl/middleware';
import { ALL_LOCALES, DEFAULT_LOCALE } from '@/tolgee/shared';
export default createMiddleware({
locales: ALL_LOCALES,
defaultLocale: DEFAULT_LOCALE,
localePrefix: 'as-needed',
});
export const config = {
// Skip the paths that should not be internationalized
matcher: ['/((?!api|_next|.*\\..*).*)'],
};
Next, create a src/navigation.ts
file. This provides easy access to the navigation APIs in your components.
import { createSharedPathnamesNavigation } from 'next-intl/navigation';
import { ALL_LOCALES } from './tolgee/shared';
// read more about next-intl library
// https://next-intl-docs.vercel.app
export const { Link, redirect, usePathname, useRouter } =
createSharedPathnamesNavigation({ locales: ALL_LOCALES });
To gain a comprehensive understanding of how
next-intl
operates, read their documentation. This example utilizes the necessary setup for proper routing. Not all the listed configurations are required.
You also need to update the next.config.js
file as follows.
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin();
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default withNextIntl(nextConfig);
The i18n/request.ts
is required by next-intl
package, we don't actually need it, so we are only doing necessary actions to stop next-intl
from complaining.
import { getRequestConfig } from 'next-intl/server';
import { getStaticData } from '../tolgee/shared';
export default getRequestConfig(async ({ locale }) => {
return {
// do this to make next-intl not emmitt any warnings
messages: { locale },
};
});
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.
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.
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.
:::
For switching languages you should use the next-intl
router helpers.
'use client';
import React, { ChangeEvent, useTransition } from 'react';
import { usePathname, useRouter } from '@/navigation';
import { useTolgee } from '@tolgee/react';
export const LangSelector: React.FC = () => {
const tolgee = useTolgee(['language']);
const locale = tolgee.getLanguage();
const router = useRouter();
const pathname = usePathname();
const [isPending, startTransition] = useTransition();
function onSelectChange(event: ChangeEvent<HTMLSelectElement>) {
const newLocale = event.target.value;
startTransition(() => {
router.replace(pathname, { locale: newLocale });
});
}
return (
<select onChange={onSelectChange} value={locale}>
<option value="en">🇬🇧 English</option>
<option value="cs">🇨🇿 Česky</option>
</select>
);
};
:::info
Make sure you use navigation-related components from @/navigation
instead natively from Next.js. This ensures that the correct locale is passed to the route.
:::
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.
To learn more about checkout this example: github.com/tolgee/next-example.