Skip to content
3.0 preview (see announcement)
Docs
Global configuration

Global configuration

Configuration properties that you use across your Next.js app can be set globally.

Client- and Server Components

Depending on if you handle internationalization in Client- or Server Components, the configuration from NextIntlClientProvider or i18n.ts will be applied respectively.

NextIntlClientProvider can be used to provide configuration for Client Components.

app/[locale]/layout.tsx
import {NextIntlClientProvider} from 'next-intl';
import {notFound} from 'next/navigation';
 
export default async function LocaleLayout({children, params: {locale}}) {
  let messages;
  try {
    messages = (await import(`../../messages/${locale}.json`)).default;
  } catch (error) {
    notFound();
  }
 
  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider locale={locale} messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

Note that NextIntlClientProvider inherits the props locale, now and timeZone when the component is rendered from a Server Component. Other configuration like messages and formats can be provided as necessary.

Messages

The most crucial aspect of internationalization is providing labels based on the user's language. The recommended workflow is to store your messages in your repository along with the code.

├── messages
│   ├── en.json
│   ├── de-AT.json
│   └── ...
...
app/[locale]/layout.tsx
import {NextIntlClientProvider} from 'next-intl';
import {notFound} from 'next/navigation';
 
export default async function LocaleLayout({children, params: {locale}}) {
  let messages;
  try {
    messages = (await import(`../../messages/${locale}.json`)).default;
  } catch (error) {
    notFound();
  }
 
  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider locale={locale} messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

Colocating your messages with app code is beneficial because it allows developers to make changes to messages quickly. Additionally, you can use the shape of your local messages for type checking.

Translators can collaborate on messages by using CI tools, such as Crowdin's GitHub integration (opens in a new tab), which allows changes to be synchronized directly into your code repository.

How can I load messages from remote sources?

While it's recommended to colocate at least the messages for the default locale, you can also load messages from remote sources, e.g. with the Crowdin OTA JS Client (opens in a new tab).

import OtaClient from '@crowdin/ota-client';
 
const defaultLocale = 'en';
const client = new OtaClient('<distribution-hash>');
const messages =
  locale === defaultLocale
    ? (await import(`../../messages/en.json`)).default
    : await client.getStringsByLocale(locale);
How can I split my messages into multiple files?

Since the messages can be freely defined and loaded, you can split your messages into multiple files and merge them later at runtime if you prefer:

const messages = {
  ...(await import(`../../messages/${locale}/login.json`)).default,
  ...(await import(`../../messages/${locale}/dashboard.json`)).default
};
How can I use messages from another locale as fallbacks?

If you have incomplete messages for a given locale and would like to use messages from another locale as a fallback, you can merge the two accordingly.

import deepmerge from 'deepmerge';
 
const userMessages = (await import(`../../messages/${locale}.json`)).default;
const defaultMessages = (await import(`../../messages/en.json`)).default;
const messages = deepmerge(defaultMessages, userMessages);

Time zone

Specifying a time zone affects the rendering of dates and times. By default, the available time zone of the runtime will be used: In Node.js this is the time zone of the server and in the browser, this is the local time zone of the user. As these can differ, this can lead to markup mismatches when your components render both on the server as well as on the client side.

To ensure consistency, you can globally define a time zone:

// The time zone can either be statically defined, read from the
// user profile if you store such a setting, or based on dynamic
// request information like the locale or headers.
const timeZone = 'Europe/Vienna';
 
<NextIntlClientProvider timeZone={timeZone}>...<NextIntlClientProvider>

The available time zone names can be looked up in the tz database (opens in a new tab).

Now value

To avoid mismatches between the server and client environment, it is recommended to configure a global value for now:

<NextIntlClientProvider
  // This value can be generated in data fetching
  // functions to make sure it's consistent
  now={now}
  ...
>
  <App />
</NextIntlClientProvider>

This value will be used as the default for the relativeTime function as well as returned during the initial render of useNow.

Tip: For consistent results in end-to-end tests, it can be helpful to mock this value to a constant value, e.g. based on an environment parameter.

⚠️

Important: When you cache the rendered markup (e.g. when using static rendering (opens in a new tab)), the formatted value will become stale. Therefore either regenerate these pages regularly or use the updateInterval option of useNow on the client side.

Formats

To achieve consistent date, time, number and list formatting, you can define a set of global formats.

<NextIntlClientProvider
  ...
  formats={{
    dateTime: {
      short: {
        day: 'numeric',
        month: 'short',
        year: 'numeric'
      }
    },
    number: {
      precise: {
        maximumFractionDigits: 5
      }
    },
    list: {
      enumeration: {
        style: 'long',
        type: 'conjunction'
      }
    }
  }}
>
  <App />
</NextIntlClientProvider>
import {useFormatter} from 'next-intl';
 
function Component() {
  const format = useFormatter();
 
  format.dateTime(new Date('2020-11-20T10:36:01.516Z'), 'short');
  format.number(47.414329182, 'precise');
  format.list(['HTML', 'CSS', 'JavaScript'], 'enumeration');
}

Global formats for numbers, dates and times can be referenced in messages too.

en.json
{
  "ordered": "You've ordered this product on {orderDate, date, short}",
  "latitude": "Latitude: {latitude, number, precise}"
}
import {useTranslations} from 'next-intl';
 
function Component() {
  const t = useTranslations();
 
  t('ordered', {orderDate: new Date('2020-11-20T10:36:01.516Z')});
  t('latitude', {latitude: 47.414329182});
}

Default translation values

To achieve consistent usage of translation values and reduce redundancy, you can define a set of global default values. This configuration can also be used to apply consistent styling of commonly used rich text elements.

<NextIntlClientProvider
  ...
  defaultTranslationValues={{
    important: (chunks) => <b>{chunks}</b>,
    value: 123
  }}
>
  <App />
</NextIntlClientProvider>

The defaults will be overridden by locally provided values.

Error handling

By default, when a message fails to resolve or when the formatting failed, an error will be printed on the console. In this case ${namespace}.${key} will be rendered instead to keep your app running.

This behavior can be customized with the onError and getMessageFallback configuration option.

import {NextIntlClientProvider, IntlErrorCode} from 'next-intl';
 
function onError(error) {
  if (error.code === IntlErrorCode.MISSING_MESSAGE) {
    // Missing translations are expected and should only log an error
    console.error(error);
  } else {
    // Other errors indicate a bug in the app and should be reported
    reportToErrorTracking(error);
  }
}
 
function getMessageFallback({namespace, key, error}) {
  const path = [namespace, key].filter((part) => part != null).join('.');
 
  if (error.code === IntlErrorCode.MISSING_MESSAGE) {
    return path + ' is not yet translated';
  } else {
    return 'Dear developer, please fix this message: ' + path;
  }
}
 
<NextIntlClientProvider ... onError={onError} getMessageFallback={getMessageFallback}>
  <App />
</NextIntlClientProvider>

Locale

NextIntlClientProvider requires a locale that it provides to all hooks from next-intl.

If you render NextIntlClientProvider from a Server Component, the locale will automatically be received.

In all other cases, e.g. when being rendered from a Client Component, a unit test or within the Pages Router, you need to pass this prop explicitly. Be sure to also set a timeZone and a value for now in this case to avoid potential markup mismatches.

I'm using the Pages Router, how can I provide the locale?

If you use internationalized routing with the Pages Router (opens in a new tab), you can receive the locale from the router in order to pass it to NextIntlClientProvider:

_app.tsx
import {useRouter} from 'next/router';
 
// ...
 
const router = useRouter();
 
return (
  <NextIntlClientProvider locale={router.locale}>
    ...
  </NextIntlClientProvider>;
);

Retrieve global configuration

As a convenience, there are a couple of hooks that allow you to read global configuration.

import {useLocale, useTimeZone, useMessages} from 'next-intl';
 
function Component() {
  const locale = useLocale();
  const timeZone = useTimeZone();
  const messages = useMessages();
}