Skip to content
Docs
Without i18n routing

App Router setup without i18n routing

Setting up an app without the i18n routing integration can be useful in the following cases:

  1. You'd like to provide a locale to next-intl, e.g. based on user settings
  2. 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 and run:

npm install next-intl

Now, we're going to create the following file structure:

├── messages (1)
│   ├── en.json
│   └── ...
├── next.config.mjs (2)
└── src
    ├── i18n.ts (3)
    └── app
        ├── layout.tsx (4)
        └── page.tsx (5)

Let's set up the files:

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.

messages/en.json
{
  "HomePage": {
    "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.

next.config.mjs
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.

src/i18n.ts
import {getRequestConfig} from 'next-intl/server';
 
export default getRequestConfig(async () => {
  // Provide a static locale, fetch a user setting,
  // read from `cookies()`, `headers()`, etc.
  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 optionally provide a path to the plugin:

next.config.mjs
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.

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>
  );
}

Note that NextIntlClientProvider automatically inherits configuration from i18n.ts here.

app/page.tsx

Use translations in your page components or anywhere else!

app/page.tsx
import {useTranslations} from 'next-intl';
 
export default function HomePage() {
  const t = useTranslations('HomePage');
  return <h1>{t('title')}</h1>;
}

That's all it takes!

In case you ran into an issue, have a look at a working example:

💡

Next steps:

  • Usage guide: Format messages, dates and times
  • Workflows: Make use of the TypeScript integration & more