Skip to content
DocsWorkflows & integrationsTypeScript augmentation

TypeScript augmentation

next-intl integrates seamlessly with TypeScript right out of the box, requiring no additional setup.

However, you can optionally provide supplemental definitions to augment the types that next-intl works with, enabling improved autocompletion and type safety across your app.

global.ts
import {routing} from '@/i18n/routing';
import {formats} from '@/i18n/request';
import messages from './messages/en.json';
 
declare module 'next-intl' {
  interface AppConfig {
    Locale: (typeof routing.locales)[number];
    Messages: typeof messages;
    Formats: typeof formats;
  }
}

Type augmentation is available for:

Locale

Augmenting the Locale type will affect all APIs from next-intl that either return or receive a locale:

import {useLocale} from 'next-intl';
 
// ✅ 'en' | 'de'
const locale = useLocale();
import {Link} from '@/i18n/routing';
 
// ✅ Passes the validation
<Link href="/" locale="en" />;

Additionally, next-intl provides a Locale type that can be used when passing the locale as an argument.

To enable this validation, you can adapt AppConfig as follows:

global.ts
import {routing} from '@/i18n/routing';
 
declare module 'next-intl' {
  interface AppConfig {
    // ...
    Locale: (typeof routing.locales)[number];
  }
}

Messages

Messages can be strictly typed to ensure you’re using valid keys.

messages.json
{
  "About": {
    "title": "Hello"
  }
}
function About() {
  // ✅ Valid namespace
  const t = useTranslations('About');
 
  // ✖️ Unknown message key
  t('description');
 
  // ✅ Valid message key
  t('title');
}

To enable this validation, you can adapt AppConfig as follows:

global.ts
import messages from './messages/en.json';
 
declare module 'next-intl' {
  interface AppConfig {
    // ...
    Messages: typeof messages;
  }
}

You can freely define the interface, but if you have your messages available locally, it can be helpful to automatically create the type based on the messages from your default locale.

Does this affect the performance of type checking?

While the size of your messages file can have an effect on the time it takes to run the TypeScript compiler on your project, the overhead of augmenting Messages should be reasonably fast.

Here’s a benchmark from a sample project with 340 messages:

  • No type augmentation for messages: ~2.20s
  • Type-safe keys: ~2.82s
  • Type-safe arguments: ~2.85s

This was observed on a MacBook Pro 2019 (Intel).


If you experience performance issues on larger projects, you can consider:

  1. Using type augmentation of messages only on your continuous integration pipeline as a safety net
  2. Splitting your project into multiple packages in a monorepo, allowing you to work with separate messages per package
Does this affect the performance of my editor?

Generally, type augmentation for Messages should be reasonably fast.

In case you notice your editor performance related to saving files to be impacted, it might be caused by running ESLint on save when using type-aware rules from @typescript-eslint.

To ensure your editor performance is optimal, you can consider running expensive, type-aware rules only on your continuous integration pipeline:

eslint.config.js
// ...
 
  // Run expensive, type-aware linting only on CI
  '@typescript-eslint/no-misused-promises': process.env.CI
    ? 'error'
    : 'off'

Type-safe arguments

Apart from strictly typing message keys, you can also ensure type safety for message arguments:

messages/en.json
{
  "UserProfile": {
    "title": "Hello {firstName}"
  }
}
function UserProfile({user}) {
  const t = useTranslations('UserProfile');
 
  // ✖️ Missing argument
  t('title');
 
  // ✅ Argument is provided
  t('title', {firstName: user.firstName});
}

TypeScript currently has a limitation where it infers values of imported JSON modules as loose types like string instead of the actual value. To bridge this gap for the time being, next-intl can generate an accompanying .d.json.ts file for the messages that you’re assigning to your AppConfig.

Usage:

  1. Add support for JSON type declarations in your tsconfig.json:
tsconfig.json
{
  "compilerOptions": {
    // ...
    "allowArbitraryExtensions": true
  }
}
  1. Configure the createMessagesDeclaration setting in your Next.js config:
next.config.mjs
import {createNextIntlPlugin} from 'next-intl/plugin';
 
const withNextIntl = createNextIntlPlugin({
  experimental: {
    // Provide the path to the messages that you're using in `AppConfig`
    createMessagesDeclaration: './messages/en.json'
  }
  // ...
});
 
// ...

With this setup in place, you’ll see a new declaration file generated in your messages directory once you run next dev or next build:

  messages/en.json
+ messages/en.d.json.ts

This declaration file will provide the exact types for the JSON messages that you’re importing and assigning to AppConfig, enabling type safety for message arguments.

To keep your code base tidy, you can ignore this file in Git:

.gitignore
messages/*.d.json.ts

Please consider upvoting TypeScript#32063 to potentially remove this workaround in the future.

Formats

If you’re using global formats, you can strictly type the format names that are referenced in calls to format.dateTime, format.number and format.list.

function Component() {
  const format = useFormatter();
 
  // ✖️ Unknown format string
  format.dateTime(new Date(), 'unknown');
 
  // ✅ Valid format
  format.dateTime(new Date(), 'short');
 
  // ✅ Valid format
  format.number(2, 'precise');
 
  // ✅ Valid format
  format.list(['HTML', 'CSS', 'JavaScript'], 'enumeration');
}

To enable this validation, export the formats that you’re using e.g. from your request configuration:

i18n/request.ts
import {Formats} from 'next-intl';
 
export const formats = {
  dateTime: {
    short: {
      day: 'numeric',
      month: 'short',
      year: 'numeric'
    }
  },
  number: {
    precise: {
      maximumFractionDigits: 5
    }
  },
  list: {
    enumeration: {
      style: 'long',
      type: 'conjunction'
    }
  }
} satisfies Formats;
 
// ...

Now, you can include the formats in your AppConfig:

global.ts
import {formats} from '@/i18n/request';
 
declare module 'next-intl' {
  interface AppConfig {
    // ...
    Formats: typeof formats;
  }
}

Troubleshooting

If you’re encountering problems, double check that:

  1. The interface uses the correct name AppConfig.
  2. Your type declaration file is included in tsconfig.json.
  3. Your editor has loaded the latest types. When in doubt, restart your editor.

Docs

 · 

Examples

 · 

Blog

 ·