Skip to content
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 Server- or Client Components, the configuration from i18n.ts or NextIntlClientProvider will be applied respectively.

i18n.ts & getRequestConfig

i18n.ts can be used to provide configuration for Server Components via the getRequestConfig function and should be set up based on wether you're using i18n routing or not.

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.

Since this function is executed during the Server Components render pass, you can call functions like cookies() (opens in a new tab) and headers() (opens in a new tab) to return configuration that is request-specific.

NextIntlClientProvider

NextIntlClientProvider can be used to provide configuration for Client Components.

layout.tsx
import {NextIntlClientProvider} from 'next-intl';
import {getMessages} from 'next-intl/server';
 
export default async function RootLayout(/* ... */) {
  // 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>
  );
}

These props are inherited if you're rendering NextIntlClientProvider from a Server Component:

  1. locale
  2. now
  3. timeZone
  4. formats

In contrast, these props can be provided as necessary:

  1. messages (see Internationalization in Client Components)
  2. defaultTranslationValues
  3. onError and getMessageFallback

Additionally, nested instances of NextIntlClientProvider will inherit configuration from their respective ancestors. Note however that individual props are treated as atomic, therefore e.g. messages need to be merged manually—if necessary.

How can I provide non-serializable props like onError to NextIntlClientProvider?

React limits the types of props that can be passed to Client Components to the ones that are serializable (opens in a new tab). Since onError, getMessageFallback and defaultTranslationValues can receive functions, these configuration options can't be automatically inherited by the client side.

In order to define these values on the client side, you can add a provider that defines these props:

IntlErrorHandlingProvider.tsx
'use client';
 
import {NextIntlClientProvider} from 'next-intl';
 
export default function IntlErrorHandlingProvider({children}) {
  return (
    <NextIntlClientProvider
      onError={(error) => console.error(error)}
      getMessageFallback={({namespace, key}) => `${namespace}.${key}`}
    >
      {children}
    </NextIntlClientProvider>
  );
}

Once you have defined your client-side provider component, you can use it in a Server Component:

layout.tsx
import {NextIntlClientProvider} from 'next-intl';
import {getLocale, getMessages} from 'next-intl/server';
import IntlErrorHandlingProvider from './IntlErrorHandlingProvider';
 
export default async function RootLayout({children}) {
  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}>
          <IntlErrorHandlingProvider>{children}</IntlErrorHandlingProvider>
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

By doing this, your provider component will already be part of the client-side bundle and can therefore define and pass functions as props.

Note that the inner NextIntlClientProvider inherits the configuration from the outer one, only the onError and getMessageFallback functions are added.

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
│   └── ...
...

Colocating your messages with app code is beneficial because it allows developers to make changes quickly and 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.

That being said, next-intl is agnostic to how you store messages and allows you to freely define an async function that fetches them while your app renders:

i18n.ts
import {getRequestConfig} from 'next-intl/server';
 
export default getRequestConfig(async ({locale}) => {
  // ...
 
  return {
    messages: (await import(`../messages/${locale}.json`)).default
  };
});

After messages are configured, they can be used via useTranslations.

In case you require access to messages in a component, you can read them via useMessages() or getMessages() from your configuration:

// Regular components
import {useMessages} from 'next-intl';
const messages = useMessages();
 
// Async Server Components
import {getMessages} from 'next-intl/server';
const messages = await getMessages();
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 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);
How can I split my messages into multiple files?

Since messages can be freely defined and loaded, you can split them 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
};

Note that the VSCode integration for next-intl can help you manage messages within a single, large file. If you're splitting messages purely for organizational reasons, you might want to consider using this instead.

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 a cookie.
    timeZone: 'Europe/Vienna'
  };
});

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

The configured time zone can be read via useTimeZone or getTimeZone in components:

// Regular components
import {useTimeZone} from 'next-intl';
const messages = useTimeZone();
 
// Async Server Components
import {getTimeZone} from 'next-intl/server';
const timeZone = await getTimeZone();

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 on a wrapping NextIntlClientProvider.

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.
    // Tip: This value can be mocked to a constant value
    // for consistent results in end-to-end-tests.
    now: new Date()
  };
});

The configured now value can be read in components via useNow or getNow:

// Regular components
import {useNow} from 'next-intl';
const now = useNow();
 
// Async Server Components
import {getNow} from 'next-intl/server';
const now = await getNow();

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

Note that formats are not automatically inherited by Client Components. If you want to make this available in Client Components, you should provide the same configuration to NextIntlClientProvider.

Once you have formats set up, you can use them in your components via useFormatter:

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

Formats are automatically inherited from the server side if you wrap the relevant components in a NextIntlClientProvider that is rendered by a Server Component.

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.tsx
import {getRequestConfig} from 'next-intl/server';
 
export default getRequestConfig(async ({locale}) => {
  // ...
 
  return {
    defaultTranslationValues: {
      important: (chunks) => <b>{chunks}</b>,
      value: 123
    }
  };
});

Note that defaultTranslationValues are not automatically inherited by Client Components. If you want to make this available in Client Components, you should provide the same configuration to NextIntlClientProvider.

Error handling (onError & getMessageFallback)

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

Note that onError and getMessageFallback are not automatically inherited by Client Components. If you want to make this functionality available in Client Components too, you can however create a client-side provider that defines these props.

Locale

The current locale of your app is automatically incorporated into hooks like useTranslations & useFormatter and will affect the rendered output.

In case you need to use this value in other places of your app, e.g. to implement a locale switcher or to pass it to API calls, you can read it via useLocale or getLocale:

// Regular components
import {useLocale} from 'next-intl';
const locale = useLocale();
 
// Async Server Components
import {getLocale} from 'next-intl/server';
const locale = await getLocale();
How can I change the locale?

Depending on if you're using i18n routing, the locale can be changed as follows:

  1. With i18n routing: The locale is managed by the router and can be changed by using navigation APIs from next-intl like Link or useRouter.
  2. Without i18n routing: You can change the locale by updating the value where the locale is read from (e.g. a cookie, a user setting, etc.). If you're looking for inspiration, you can have a look at the App Router without i18n routing example that manages the locale via a cookie.
Which value is returned from useLocale?

The returned value is resolved based on these priorities:

  1. Server Components: If you're using i18n routing, the returned locale is the one that you've either provided via unstable_setRequestLocale or alternatively the one in the [locale] segment that was matched by the middleware. If you're not using i18n routing, the returned locale is the one that you've provided via getRequestConfig.
  2. Client Components: In this case, the locale is received from NextIntlClientProvider or alternatively useParams().locale. Note that NextIntlClientProvider automatically inherits the locale if the component is rendered by a Server Component. For all other cases, you can specify the value explicitly.
I'm using the Pages Router, how can I access 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>;
);