Skip to content
next-intl 3.0 is out! (announcement)
Docs
Usage guide
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 i18n.ts or NextIntlClientProvider will be applied respectively.

i18n.ts

i18n.ts can be used to provide configuration for Server Components.

i18n.ts
import {notFound} from 'next/navigation';
import {getRequestConfig} from 'next-intl/server';
 
// Can be imported from a shared config
const locales = ['en', 'de'];
 
export default getRequestConfig(async ({locale}) => {
  // Validate that the incoming `locale` parameter is valid
  if (!locales.includes(locale as any)) notFound();
 
  return {
    messages: (await import(`../messages/${locale}.json`)).default
  };
});

The configuration object is created once for each request by internally using React's cache (opens in a new tab). The first component to use internationalization will call the function defined with getRequestConfig.

NextIntlClientProvider

NextIntlClientProvider can be used to provide configuration for Client Components.

app/[locale]/layout.tsx
import {NextIntlClientProvider, useMessages} from 'next-intl';
 
export default function LocaleLayout({children, params: {locale}}) {
  const messages = useMessages();
 
  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

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.

💡

Before passing all messages to the client side, learn more about the options you have to use internationalization in Client Components.

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
│   └── ...
...
i18n.ts
import {getRequestConfig} from 'next-intl/server';
 
export default getRequestConfig(async ({locale}) => {
  // ...
 
  return {
    messages: (await import(`../messages/${locale}.json`)).default
  };
});

Colocating your messages with app code is beneficial because it allows developers to make changes 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 time zone of the server runtime will be used, but can be customized as necessary.

i18n.ts
import {getRequestConfig} from 'next-intl/server';
 
export default getRequestConfig(async ({locale}) => {
  // ...
 
  return {
    // 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.
    timeZone: 'Europe/Vienna'
  };
});

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

💡

The time zone in Client Components is automatically inherited from the server side if you wrap the relevant components in a NextIntlClientProvider that is rendered by a Server Component. For all other cases, you can specify the value explicitly.

Now value

When formatting relative dates and times, next-intl will format times in relation to a reference point in time that is referred to as "now". By default, this is the time a component renders.

If you prefer to override the default, you can provide an explicit value for now:

i18n.ts
import {getRequestConfig} from 'next-intl/server';
 
export default getRequestConfig(async ({locale}) => {
  // ...
 
  return {
    // This is the default, a single date instance will be used
    // by all Server Components to ensure consistency
    now: new Date()
  };
});

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.

💡

Similarly to the timeZone, the now value in Client Components is automatically inherited from the server side if you wrap the relevant components in a NextIntlClientProvider that is rendered by a Server Component.

Formats

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

i18n.ts
import {getRequestConfig} from 'next-intl/server';
 
export default getRequestConfig(async ({locale}) => {
  // ...
 
  return {
    formats: {
      dateTime: {
        short: {
          day: 'numeric',
          month: 'short',
          year: 'numeric'
        }
      },
      number: {
        precise: {
          maximumFractionDigits: 5
        }
      },
      list: {
        enumeration: {
          style: 'long',
          type: 'conjunction'
        }
      }
    }
  };
});

Usage in components:

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.

i18n.ts
import {getRequestConfig} from 'next-intl/server';
 
export default getRequestConfig(async ({locale}) => {
  // ...
 
  return {
    defaultTranslationValues: {
      important: (chunks) => <b>{chunks}</b>,
      value: 123
    }
  };
});

The defaults will be overridden if local formats are provided at a specific call site.

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.

i18n.ts
import {getRequestConfig} from 'next-intl/server';
import {IntlErrorCode} from 'next-intl';
 
export default getRequestConfig(async ({locale}) => {
  // ...
 
  return {
    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);
      }
    },
    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;
      }
    }
  };
});

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, useNow} from 'next-intl';
 
function Component() {
  const locale = useLocale();
  const timeZone = useTimeZone();
  const messages = useMessages();
  const now = useNow();
}