|
| 1 | +--- |
| 2 | +title: Using MF2 with React |
| 3 | +sidebar_title: React |
| 4 | +--- |
| 5 | + |
| 6 | +This guide explains how to localize React applications with MessageFormat 2 |
| 7 | +(MF2), using the `mf2react` package. |
| 8 | + |
| 9 | +The library builds ontop of [i18next](https://www.i18next.com/) and |
| 10 | +[react-i18next](https://react.i18next.com/), popular internationalization |
| 11 | +frameworks for JavaScript and React. |
| 12 | + |
| 13 | +The package contains a post-processor plugin for i18next that compiles MF2 |
| 14 | +messages and converts lightweight curly-tag markup into safe HTML tags, which |
| 15 | +`react-i18next` can render as JSX. For example the message |
| 16 | +`{#bold}Hello{/bold}, |
| 17 | +{$name}!` becomes `<strong>Hello</strong>, {$name}!` when |
| 18 | +rendered. |
| 19 | + |
| 20 | +MF2 features such as pluralization, select, and conditional logic are fully |
| 21 | +supported. For example, the following MF2 message: |
| 22 | + |
| 23 | +```mf2 |
| 24 | +.match {$count: number} |
| 25 | +one {{You have {$count} message}} |
| 26 | +* {{You have {$count} messages}} |
| 27 | +``` |
| 28 | + |
| 29 | +Can be used in a React component like this: |
| 30 | + |
| 31 | +```tsx |
| 32 | +import { Trans } from "react-i18next"; |
| 33 | +export default function MessagesComponent({ count }: { count: number }) { |
| 34 | + return <Trans i18nKey="messages" values={{ count }} />; |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +## Introduction |
| 39 | + |
| 40 | +> This guide assumes you have a basic understanding of React and i18next / |
| 41 | +> react-i18next. |
| 42 | +
|
| 43 | +## Installation and setup |
| 44 | + |
| 45 | +In an existing React project, install the `mf2react` package, along with the |
| 46 | +`i18next`, and `react-i18next` dependencies: |
| 47 | + |
| 48 | +```bash |
| 49 | +npm install mf2react i18next react-i18next |
| 50 | +``` |
| 51 | + |
| 52 | +You can also use a different package manager, such as `yarn`, `pnpm`, or `deno` |
| 53 | +to install the packages. |
| 54 | + |
| 55 | +### Defining your catalogs (translations) |
| 56 | + |
| 57 | +Create JSON files for each locale you want to support. For example, create a |
| 58 | +`locales/en/translation.json` file for English translations: |
| 59 | + |
| 60 | +```json |
| 61 | +{ |
| 62 | + "welcome": "Welcome to our application!", |
| 63 | + "goodbye": "Goodbye!", |
| 64 | + "greeting": "Hello, {$name}!", |
| 65 | + "apples": ".input {$value :number}\n.match $value\none {{{#bold}1{/bold} apple}}\n* {{{#bold}{$value}{/bold} apples}}" |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +And a `locales/no/translation.json` file for Norwegian translations: |
| 70 | + |
| 71 | +```json |
| 72 | +{ |
| 73 | + "welcome": "Velkommen til vår applikasjon!", |
| 74 | + "goodbye": "Ha det!", |
| 75 | + "greeting": "Hei, {$name}!", |
| 76 | + "apples": ".input {$value :number}\n.match $value\none {{{#bold}1{/bold} eple}}\n* {{{#bold}{$value}{/bold} epler}}" |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +### Setting up i18next |
| 81 | + |
| 82 | +Create a `i18n.ts` file in your project to configure i18next. |
| 83 | + |
| 84 | +```ts |
| 85 | +// i18n.ts |
| 86 | +import i18n from "i18next"; |
| 87 | +import { initReactI18next } from "react-i18next"; |
| 88 | +import { MF2PostProcessor, MF2ReactPreset } from "mf2react"; |
| 89 | + |
| 90 | +import en from "./locales/en/translation.json"; |
| 91 | +import no from "./locales/no/translation.json"; |
| 92 | + |
| 93 | +i18n |
| 94 | + .use(MF2PostProcessor) // Enable the post-processor |
| 95 | + .use(MF2ReactPreset) // Enable curly-tag -> JSX conversion |
| 96 | + .use(initReactI18next) |
| 97 | + .init({ |
| 98 | + lng: "en", |
| 99 | + postProcess: ["mf2"], // Apply MF2 to all translations |
| 100 | + resources: { |
| 101 | + // Reference the translation files |
| 102 | + en: { translation: en }, |
| 103 | + no: { translation: no }, |
| 104 | + }, |
| 105 | + }); |
| 106 | + |
| 107 | +export default i18n; |
| 108 | +``` |
| 109 | + |
| 110 | +> Instead of defining the selected locale (`lng`) and `resources` directly in |
| 111 | +> the `init` function, you may also choose to load them dynamically, e.g. via |
| 112 | +> [i18next-http-backend](https://github.com/i18next/i18next-http-backend), |
| 113 | +> [i18next-resources-to-backend](https://github.com/i18next/i18next-resources-to-backend). |
| 114 | +> Additionally you may want to auto-detect the user's locale using |
| 115 | +> [i18next-browser-languagedetector](https://github.com/i18next/i18next-browser-languagedetector). |
| 116 | +
|
| 117 | +### Wrapping your application with I18nextProvider |
| 118 | + |
| 119 | +To use translations in your React components, you need to wrap your application |
| 120 | +with the `I18nextProvider` from `react-i18next`. This is typically done in your |
| 121 | +main application file or layout component. |
| 122 | + |
| 123 | +```ts |
| 124 | +"use client"; |
| 125 | + |
| 126 | +import { I18nextProvider } from "react-i18next"; |
| 127 | +import { i18n } from "./i18n"; |
| 128 | + |
| 129 | +export default function AppLayout({ children }: { children: React.ReactNode }) { |
| 130 | + return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>; |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +> Good to know: because `I18nextProvider` uses React context, it can only be |
| 135 | +> used in a client component. |
| 136 | +
|
| 137 | +#### Choosing where to place the provider |
| 138 | + |
| 139 | +Where you place the provider affects what becomes client-rendered. You may |
| 140 | +either wrap your whole application in the provider, or each component that uses |
| 141 | +translations. This is because `I18nextProvider` must be used in a client |
| 142 | +component. Choose the placement based on how much of your UI should be |
| 143 | +client-rendered. |
| 144 | + |
| 145 | +### Using translations in components |
| 146 | + |
| 147 | +Now you can use the `<Trans>` component from `react-i18next` to render |
| 148 | +translations in your React components. For example: |
| 149 | + |
| 150 | +```tsx |
| 151 | +import { Trans } from "react-i18next"; |
| 152 | +export default function WelcomeComponent() { |
| 153 | + return ( |
| 154 | + <div> |
| 155 | + <h1> |
| 156 | + <Trans i18nKey="welcome" /> |
| 157 | + </h1> |
| 158 | + <p> |
| 159 | + <Trans i18nKey="goodbye" /> |
| 160 | + </p> |
| 161 | + </div> |
| 162 | + ); |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +> You can read more about the Trans component in the |
| 167 | +> [react-i18next documentation](https://react.i18next.com/latest/trans-component) |
| 168 | +
|
| 169 | +#### Passing variables to translations |
| 170 | + |
| 171 | +You can also pass variables to your translations using the `values` prop of the |
| 172 | +`<Trans>` component. For example, if you have a translation that includes a |
| 173 | +variable: |
| 174 | + |
| 175 | +```json |
| 176 | +{ |
| 177 | + "greeting": "Hello, {$name}!" |
| 178 | +} |
| 179 | +``` |
| 180 | + |
| 181 | +You can use it in your component like this: |
| 182 | + |
| 183 | +```tsx |
| 184 | +import { Trans } from "react-i18next"; |
| 185 | +export default function GreetingComponent({ name }: { name: string }) { |
| 186 | + return <Trans i18nKey="greeting" values={{ name }} />; |
| 187 | +} |
| 188 | +``` |
| 189 | + |
| 190 | +This also works for MF2 messages with pluralization and formatting: |
| 191 | + |
| 192 | +```tsx |
| 193 | +import { Trans } from "react-i18next"; |
| 194 | +export default function ApplesComponent({ count }: { count: number }) { |
| 195 | + return <Trans i18nKey="apples" values={{ value: count }} />; |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +> Output when `count` is 1: **1** apple |
| 200 | +> |
| 201 | +> Output when `count` is 5: **5** apples |
| 202 | +
|
| 203 | +#### Markup with curly-tags |
| 204 | + |
| 205 | +You can use curly-tags in your translations to add formatting. For example, in |
| 206 | +your translation file: |
| 207 | + |
| 208 | +```json |
| 209 | +{ |
| 210 | + "bold": "This is {#bold}bold text{/bold}." |
| 211 | +} |
| 212 | +``` |
| 213 | + |
| 214 | +You can render it in your component like this: |
| 215 | + |
| 216 | +```tsx |
| 217 | +import { Trans } from "react-i18next"; |
| 218 | +export default function BoldComponent() { |
| 219 | + return <Trans i18nKey="bold" />; |
| 220 | +} |
| 221 | +``` |
| 222 | + |
| 223 | +> Output: This is **bold text**. |
| 224 | +
|
| 225 | +This works because the `mf2react` post-processor converts the curly-tags into |
| 226 | +safe HTML tags, which `react-i18next` can render as JSX. The following tags are |
| 227 | +supported: |
| 228 | + |
| 229 | +```txt |
| 230 | +{#bold}…{/bold} |
| 231 | +{#strong}…{/strong} |
| 232 | +{#i}…{/i} |
| 233 | +{#em}…{/em} |
| 234 | +{#u}…{/u} |
| 235 | +{#s}…{/s} |
| 236 | +{#small}…{/small} |
| 237 | +{#code}…{/code} |
| 238 | +``` |
| 239 | + |
| 240 | +## Notes and limitations |
| 241 | + |
| 242 | +- i18next post-processors must return **strings**. The JSX conversion happens |
| 243 | + inside `<Trans>`. |
| 244 | +- Messages are compiled and cached per language for performance. |
| 245 | +- The curly-tag conversion is intentionally minimal and safe. It only recognizes |
| 246 | + tags defined in the alias list. |
| 247 | +- If you switch languages at runtime, the plugin automatically reuses or |
| 248 | + recompiles as needed. |
| 249 | +- Unsupported MF2 syntax will fall back gracefully to raw string + curly tag |
| 250 | + conversion. |
| 251 | + |
| 252 | +## Acknowledgements |
| 253 | + |
| 254 | +- [i18next](https://www.i18next.com/) - the internalization framework |
| 255 | +- [react-i18next](https://react.i18next.com/) - React bindings for i18next |
| 256 | +- [@messageformat/core](https://github.com/messageformat/messageformat) - |
| 257 | + MessageFormat2 engine used for compiling and evaluating MF2 syntax. |
0 commit comments