React Internationalization (i18n) with react-intl and Intlayer
This guide shows how to integrate Intlayer with react-intl to manage translations in a React application. You’ll declare your translatable content with Intlayer and then consume those messages with react-intl, a popular library from the FormatJS ecosystem.
Overview
- Intlayer allows you to store translations in component-level content declaration files (JSON, JS, TS, etc.) within your project.
- react-intl provides React components and hooks (like <FormattedMessage> and useIntl()) to display localized strings.
By configuring Intlayer to export translations in a react-intl–compatible format, you can automatically generate and update the message files that <IntlProvider> (from react-intl) requires.
Why Use Intlayer with react-intl?
Per-Component Content Declarations
Intlayer content declaration files can live alongside your React components, preventing “orphaned” translations if components are moved or removed. For example:bash.└── src └── components └── MyComponent ├── index.content.ts # Intlayer content declaration └── index.tsx # React component
Centralized Translations
Each content declaration file collects all translations needed by a component. This is particularly helpful in TypeScript projects: missing translations can be caught at compile time.Automatic Build and Regeneration
Whenever you add or update translations, Intlayer regenerates message JSON files. You can then pass these into react-intl’s <IntlProvider>.
Installation
In a typical React project, install the following:
# with npmnpm install intlayer react-intl# with yarnyarn add intlayer react-intl# with pnpmpnpm add intlayer react-intl
Why These Packages?
- intlayer: Core CLI and library that scans for content declarations, merges them, and builds dictionary outputs.
- react-intl: The main library from FormatJS that provides <IntlProvider>, <FormattedMessage>, useIntl() and other internationalization primitives.
If you don’t already have React itself installed, you’ll need it, too (react and react-dom).
Configuring Intlayer to Export react-intl Messages
In your project’s root, create intlayer.config.ts (or .js, .mjs, .cjs) like so:
import { Locales, type IntlayerConfig } from "intlayer";const config: IntlayerConfig = { internationalization: { // Add as many locales as you wish locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH], defaultLocale: Locales.ENGLISH, }, content: { // Tells Intlayer to generate message files for react-intl dictionaryOutput: ["react-intl"], // The directory where Intlayer will write your message JSON files reactIntlMessagesDir: "./react-intl/messages", },};export default config;
Note: For other file extensions (.mjs, .cjs, .js), see the Intlayer docs for usage details.
Creating Your Intlayer Content Declarations
Intlayer scans your codebase (by default, under ./src) for files matching *.content.{ts,tsx,js,jsx,mjs,cjs,json}.
Here’s a TypeScript example:
import { t, type DeclarationContent } from "intlayer";const content = { // "key" becomes the top-level message key in your react-intl JSON file key: "my-component", content: { // Each call to t() declares a translatable field helloWorld: t({ en: "Hello World", fr: "Bonjour le monde", es: "Hola Mundo", }), description: t({ en: "This is a description", fr: "Ceci est une description", es: "Esta es una descripción", }), },} satisfies DeclarationContent;export default content;
If you prefer JSON or different JS flavors (.cjs, .mjs), the structure is largely the same see Intlayer docs on content declaration.
Building the react-intl Messages
To generate the actual message JSON files for react-intl, run:
# with npmnpx intlayer build# with yarnyarn intlayer build# with pnpmpnpm intlayer build
This scans all *.content.* files, compiles them, and writes the results to the directory specified in your intlayer.config.ts in this example, ./react-intl/messages.
A typical output might look like:
.└── react-intl └── messages ├── en.json ├── fr.json └── es.json
Each file is a JSON object whose top-level keys correspond to each content.key from your declarations. The sub-keys (like helloWorld) reflect the translations declared within that content item.
For example, the en.json might look like:
{ "helloWorld": "Hello World", "description": "This is a description"}
Initializing react-intl in Your React App
1. Load the Generated Messages
Where you configure your app’s root component (e.g., src/main.tsx or src/index.tsx), you’ll need to:
- Import the generated message files (either statically or dynamically).
- Provide them to <IntlProvider> from react-intl.
A simple approach is to import them statically:
import React from "react";import ReactDOM from "react-dom/client";import { IntlProvider } from "react-intl";import App from "./App";// Import the JSON files from the build output.// Alternatively, you can import dynamically based on the user's chosen locale.import en from "../react-intl/messages/en.json";import fr from "../react-intl/messages/fr.json";import es from "../react-intl/messages/es.json";// Dynamically import all JSON files using Vite's import.meta.globconst messages = import.meta.glob("../react-intl/messages/**/*.json", { eager: true,});// Collate messages into a structured recordconst messagesRecord: Record<string, Record<string, any>> = {};Object.entries(messages).forEach(([path, module]) => { // Extract locale and namespace from the file path const [, locale, namespace] = path.match(/messages\/(\w+)\/(.+?)\.json$/) ?? []; if (locale && namespace) { messagesRecord[locale] = messagesRecord[locale] ?? {}; messagesRecord[locale][namespace] = module.default; // Assign JSON content }});// Merge namespaces for each localeconst mergeMessages = (locale: string) => Object.values(messagesRecord[locale] ?? {}).reduce( (acc, namespaceMessages) => ({ ...acc, ...namespaceMessages }), {} );// If you have a mechanism to detect the user's language, set it here.// For simplicity, let's pick English.const locale = "en";ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( <React.StrictMode> <IntlProvider locale={locale} messages={mergeMessages(locale)}> <App /> </IntlProvider> </React.StrictMode>);
Tip: For real projects, you might:
- Dynamically load the JSON messages at runtime.
- Use environment-based, browser-based, or user account–based locale detection.
2. Use <FormattedMessage> or useIntl()
Once your messages are loaded into <IntlProvider>, any child component can use react-intl to access localized strings. There are two main approaches:
- <FormattedMessage> component
- useIntl() hook
Using Translations in React Components
Approach A: <FormattedMessage>
For quick inline usage:
import React from "react";import { FormattedMessage } from "react-intl";export default function MyComponent() { return ( <div> <h1> {/* “my-component.helloWorld” references the key from en.json, fr.json, etc. */} <FormattedMessage id="my-component.helloWorld" /> </h1> <p> <FormattedMessage id="my-component.description" /> </p> </div> );}
The id prop in <FormattedMessage> must match the top-level key (my-component) plus any sub-keys (helloWorld).
Approach B: useIntl()
For more dynamic usage:
import React from "react";import { useIntl } from "react-intl";export default function MyComponent() { const intl = useIntl(); return ( <div> <h1>{intl.formatMessage({ id: "my-component.helloWorld" })}</h1> <p>{intl.formatMessage({ id: "my-component.description" })}</p> </div> );}
Either approach is valid choose whichever style suits your app.
Updating or Adding New Translations
- Add or modify content in any *.content.* file.
- Rerun intlayer build to regenerate the JSON files under ./react-intl/messages.
- React (and react-intl) will pick up the updates next time you rebuild or reload your application.
TypeScript Integration (Optional)
If you’re using TypeScript, Intlayer can generate type definitions for your translations.
- Make sure tsconfig.json includes your types folder (or whichever output folder Intlayer generates) in the "include" array.
{ "compilerOptions": { // ... }, "include": ["src", "types"],}
The generated types can help detect missing translations or invalid keys in your React components at compile time.
Git Configuration
It’s common to exclude Intlayer’s internal build artifacts from version control. In your .gitignore, add:
# Ignore intlayer build artifacts.intlayerreact-intl
Depending on your workflow, you may also want to ignore or commit the final dictionaries in ./react-intl/messages. If your CI/CD pipeline regenerates them, you can safely ignore them; otherwise, commit them if you need them for production deployments.
If you have an idea for improving this blog, please feel free to contribute by submitting a pull request on GitHub.
GitHub link to the blog