App Router setup without i18n routing
Setting up an app without the i18n routing integration can be useful in the following cases:
- You'd like to provide a locale to
next-intl
, e.g. based on user settings - Your app only supports a single language
This is the easiest way to get started with next-intl
and requires no changes to the structure of your app.
Getting started
If you haven't done so already, create a Next.js app (opens in a new tab) that uses the App Router.
Next, run npm install next-intl
and create the following file structure:
├── messages (1)
│ ├── en.json
│ └── ...
├── next.config.mjs (2)
└── src
├── i18n.ts (3)
└── app
├── layout.tsx (4)
└── page.tsx (5)
Now, set up the files as follows:
messages/en.json
Messages can be provided locally or loaded from a remote data source (e.g. a translation management system). Use whatever suits your workflow best.
The simplest option is to add JSON files in your project based on locales—e.g. en.json
.
{
"Index": {
"title": "Hello world!"
}
}
next.config.mjs
Now, set up the plugin which creates an alias to provide your i18n configuration (specified in the next step) to Server Components.
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin();
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default withNextIntl(nextConfig);
i18n.ts
next-intl
creates a request-scoped configuration object that can be used to provide messages and other options based on the user's locale for usage in Server Components.
import {NextIntlClientProvider} from 'next-intl';
import {getRequestConfig} from 'next-intl/server';
export default getRequestConfig(async () => {
// This can either be defined statically if only a single locale
// is supported, or alternatively read from the user settings
const locale = 'en';
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default
};
});
Can I move this file somewhere else?
This file is supported out-of-the-box both in the src
folder as well as in the project root with the extensions .ts
, .tsx
, .js
and .jsx
.
If you prefer to move this file somewhere else, you can provide an optional path to the plugin:
const withNextIntl = createNextIntlPlugin(
// Specify a custom path here
'./somewhere/else/i18n.ts'
);
app/layout.tsx
The locale
that was provided in i18n.ts
is available via getLocale
and can be used to configure the document language. Additionally, we can use this place to pass configuration from i18n.ts
to Client Components via NextIntlClientProvider
.
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>
);
}
Note that NextIntlClientProvider
automatically inherits configuration from i18n.ts
here.
app/page.tsx
Use translations in your page components or anywhere else!
import {useTranslations} from 'next-intl';
export default function Index() {
const t = useTranslations('Index');
return <h1>{t('title')}</h1>;
}
That's all it takes!
In case you ran into an issue, have a look at this working example: App Router (without i18n routing).
Next steps:
- Usage guide: Format messages, dates and times
Workflows: Make use of the TypeScript integration & more