Using MF2 with React
This guide explains how to localize React applications with MessageFormat 2
(MF2), using the mf2react package.
The library builds ontop of i18next and react-i18next, popular internationalization frameworks for JavaScript and React.
The package contains a post-processor plugin for i18next that compiles MF2
messages and converts lightweight curly-tag markup into safe HTML tags, which
react-i18next can render as JSX. For example the message
{#bold}Hello{/bold}, {$name}! becomes <strong>Hello</strong>, {$name}! when
rendered.
MF2 features such as pluralization, select, and conditional logic are fully supported. For example, the following MF2 message:
.match {$count: number}
one {{You have {$count} message}}
* {{You have {$count} messages}}
Can be used in a React component like this:
import { Trans } from "react-i18next";
export default function MessagesComponent({ count }: { count: number }) {
return <Trans i18nKey="messages" values={{ count }} />;
}
Introduction Jump to heading
This guide assumes you have a basic understanding of React and i18next / react-i18next.
Installation and setup Jump to heading
In an existing React project, install the mf2react package, along with the
i18next, and react-i18next dependencies:
npm install mf2react i18next react-i18next
You can also use a different package manager, such as yarn, pnpm, or deno
to install the packages.
Defining your catalogs (translations) Jump to heading
Create JSON files for each locale you want to support. For example, create a
locales/en/translation.json file for English translations:
{
"welcome": "Welcome to our application!",
"goodbye": "Goodbye!",
"greeting": "Hello, {$name}!",
"apples": ".input {$value :number}\n.match $value\none {{{#bold}1{/bold} apple}}\n* {{{#bold}{$value}{/bold} apples}}"
}
And a locales/no/translation.json file for Norwegian translations:
{
"welcome": "Velkommen til vår applikasjon!",
"goodbye": "Ha det!",
"greeting": "Hei, {$name}!",
"apples": ".input {$value :number}\n.match $value\none {{{#bold}1{/bold} eple}}\n* {{{#bold}{$value}{/bold} epler}}"
}
Setting up i18next Jump to heading
Create a i18n.ts file in your project to configure i18next.
// i18n.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import { MF2PostProcessor, MF2ReactPreset } from "mf2react";
import en from "./locales/en/translation.json";
import no from "./locales/no/translation.json";
i18n
.use(MF2PostProcessor) // Enable the post-processor
.use(MF2ReactPreset) // Enable curly-tag -> JSX conversion
.use(initReactI18next)
.init({
lng: "en",
postProcess: ["mf2"], // Apply MF2 to all translations
resources: {
// Reference the translation files
en: { translation: en },
no: { translation: no },
},
});
export default i18n;
Instead of defining the selected locale (
lng) andresourcesdirectly in theinitfunction, you may also choose to load them dynamically, e.g. via i18next-http-backend, i18next-resources-to-backend. Additionally you may want to auto-detect the user's locale using i18next-browser-languagedetector.
Wrapping your application with I18nextProvider Jump to heading
To use translations in your React components, you need to wrap your application
with the I18nextProvider from react-i18next. This is typically done in your
main application file or layout component.
"use client";
import { I18nextProvider } from "react-i18next";
import { i18n } from "./i18n";
export default function AppLayout({ children }: { children: React.ReactNode }) {
return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
}
Good to know: because
I18nextProvideruses React context, it can only be used in a client component.
Choosing where to place the provider Jump to heading
Where you place the provider affects what becomes client-rendered. You may
either wrap your whole application in the provider, or each component that uses
translations. This is because I18nextProvider must be used in a client
component. Choose the placement based on how much of your UI should be
client-rendered.
Using translations in components Jump to heading
Now you can use the <Trans> component from react-i18next to render
translations in your React components. For example:
import { Trans } from "react-i18next";
export default function WelcomeComponent() {
return (
<div>
<h1>
<Trans i18nKey="welcome" />
</h1>
<p>
<Trans i18nKey="goodbye" />
</p>
</div>
);
}
You can read more about the Trans component in the react-i18next documentation
Passing variables to translations Jump to heading
You can also pass variables to your translations using the values prop of the
<Trans> component. For example, if you have a translation that includes a
variable:
{
"greeting": "Hello, {$name}!"
}
You can use it in your component like this:
import { Trans } from "react-i18next";
export default function GreetingComponent({ name }: { name: string }) {
return <Trans i18nKey="greeting" values={{ name }} />;
}
This also works for MF2 messages with pluralization and formatting:
import { Trans } from "react-i18next";
export default function ApplesComponent({ count }: { count: number }) {
return <Trans i18nKey="apples" values={{ value: count }} />;
}
Output when
countis 1: 1 appleOutput when
countis 5: 5 apples
Markup with curly-tags Jump to heading
You can use curly-tags in your translations to add formatting. For example, in your translation file:
{
"bold": "This is {#bold}bold text{/bold}."
}
You can render it in your component like this:
import { Trans } from "react-i18next";
export default function BoldComponent() {
return <Trans i18nKey="bold" />;
}
Output: This is bold text.
This works because the mf2react post-processor converts the curly-tags into
safe HTML tags, which react-i18next can render as JSX. The following tags are
supported:
{#bold}…{/bold}
{#strong}…{/strong}
{#i}…{/i}
{#em}…{/em}
{#u}…{/u}
{#s}…{/s}
{#small}…{/small}
{#code}…{/code}
Notes and limitations Jump to heading
- i18next post-processors must return strings. The JSX conversion happens
inside
<Trans>. - Messages are compiled and cached per language for performance.
- The curly-tag conversion is intentionally minimal and safe. It only recognizes tags defined in the alias list.
- If you switch languages at runtime, the plugin automatically reuses or recompiles as needed.
- Unsupported MF2 syntax will fall back gracefully to raw string + curly tag conversion.
Acknowledgements Jump to heading
- i18next - the internalization framework
- react-i18next - React bindings for i18next
- @messageformat/core - MessageFormat2 engine used for compiling and evaluating MF2 syntax.