@intlayer/docs 7.0.3 → 7.0.4-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/blog/en/intlayer_with_i18next.md +1620 -54
- package/blog/en/intlayer_with_next-i18next.md +763 -163
- package/blog/en/intlayer_with_next-intl.md +986 -217
- package/blog/en/intlayer_with_react-i18next.md +645 -147
- package/blog/en/intlayer_with_react-intl.md +900 -147
- package/blog/en/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/dist/cjs/generated/blog.entry.cjs +13 -1
- package/dist/cjs/generated/blog.entry.cjs.map +1 -1
- package/dist/cjs/generated/docs.entry.cjs +13 -1
- package/dist/cjs/generated/docs.entry.cjs.map +1 -1
- package/dist/cjs/generated/frequentQuestions.entry.cjs +13 -1
- package/dist/cjs/generated/frequentQuestions.entry.cjs.map +1 -1
- package/dist/cjs/generated/legal.entry.cjs +13 -1
- package/dist/cjs/generated/legal.entry.cjs.map +1 -1
- package/dist/esm/generated/blog.entry.mjs +13 -2
- package/dist/esm/generated/blog.entry.mjs.map +1 -1
- package/dist/esm/generated/docs.entry.mjs +13 -2
- package/dist/esm/generated/docs.entry.mjs.map +1 -1
- package/dist/esm/generated/frequentQuestions.entry.mjs +13 -2
- package/dist/esm/generated/frequentQuestions.entry.mjs.map +1 -1
- package/dist/esm/generated/legal.entry.mjs +13 -2
- package/dist/esm/generated/legal.entry.mjs.map +1 -1
- package/dist/types/generated/blog.entry.d.ts.map +1 -1
- package/dist/types/generated/docs.entry.d.ts.map +1 -1
- package/dist/types/generated/frequentQuestions.entry.d.ts.map +1 -1
- package/dist/types/generated/legal.entry.d.ts.map +1 -1
- package/docs/de/releases/v7.md +1 -18
- package/docs/en/CI_CD.md +1 -1
- package/docs/en/configuration.md +1 -1
- package/docs/en/formatters.md +1 -1
- package/docs/en/how_works_intlayer.md +1 -1
- package/docs/en/intlayer_CMS.md +1 -1
- package/docs/en/intlayer_cli.md +1 -1
- package/docs/en/intlayer_with_nextjs_14.md +1 -1
- package/docs/en/intlayer_with_nextjs_15.md +1 -1
- package/docs/en/intlayer_with_nextjs_16.md +1 -1
- package/docs/en/intlayer_with_nextjs_page_router.md +1 -1
- package/docs/en/intlayer_with_nuxt.md +1 -1
- package/docs/en/intlayer_with_react_native+expo.md +1 -1
- package/docs/en/intlayer_with_react_router_v7.md +1 -1
- package/docs/en/intlayer_with_tanstack.md +1 -1
- package/docs/en/intlayer_with_vite+preact.md +1 -1
- package/docs/en/intlayer_with_vite+react.md +1 -1
- package/docs/en/intlayer_with_vite+solid.md +1 -1
- package/docs/en/intlayer_with_vite+svelte.md +1 -1
- package/docs/en/intlayer_with_vite+vue.md +1 -1
- package/docs/en/roadmap.md +1 -1
- package/docs/es/releases/v7.md +1 -18
- package/docs/fr/intlayer_with_nextjs_16.md +2 -51
- package/docs/fr/releases/v7.md +1 -18
- package/docs/hi/intlayer_with_nextjs_16.md +3 -2
- package/docs/id/releases/v7.md +1 -18
- package/docs/it/releases/v7.md +1 -18
- package/docs/ja/intlayer_with_nextjs_16.md +44 -205
- package/docs/ja/releases/v7.md +1 -18
- package/docs/ko/releases/v7.md +1 -18
- package/docs/pt/intlayer_with_nextjs_16.md +1 -52
- package/package.json +14 -14
- package/src/generated/blog.entry.ts +26 -3
- package/src/generated/docs.entry.ts +26 -3
- package/src/generated/frequentQuestions.entry.ts +26 -3
- package/src/generated/legal.entry.ts +26 -3
|
@@ -1,345 +1,1098 @@
|
|
|
1
1
|
---
|
|
2
2
|
createdAt: 2025-01-02
|
|
3
|
-
updatedAt: 2025-
|
|
4
|
-
title: Intlayer
|
|
5
|
-
description:
|
|
3
|
+
updatedAt: 2025-10-29
|
|
4
|
+
title: How to Integrate Intlayer with react-intl – Complete i18n Guide
|
|
5
|
+
description: Learn how to integrate Intlayer with react-intl for a React app. Comprehensive guide with code examples for managing translations efficiently.
|
|
6
6
|
keywords:
|
|
7
7
|
- react-intl
|
|
8
8
|
- Intlayer
|
|
9
9
|
- Internationalization
|
|
10
10
|
- Blog
|
|
11
|
-
-
|
|
11
|
+
- i18n
|
|
12
12
|
- JavaScript
|
|
13
13
|
- React
|
|
14
|
+
- FormatJS
|
|
14
15
|
slugs:
|
|
15
16
|
- blog
|
|
16
17
|
- intlayer-with-react-intl
|
|
18
|
+
history:
|
|
19
|
+
- version: 7.0.0
|
|
20
|
+
date: 2025-10-29
|
|
21
|
+
changes: Change to syncJSON plugin
|
|
17
22
|
---
|
|
18
23
|
|
|
19
24
|
# React Internationalization (i18n) with **react-intl** and Intlayer
|
|
20
25
|
|
|
21
|
-
This guide
|
|
26
|
+
This comprehensive guide demonstrates how to integrate **Intlayer** with **react-intl** to manage translations in a React application efficiently. You'll learn to declare your translatable content with Intlayer and consume those messages with **react-intl**, a popular library from the [FormatJS](https://formatjs.io/docs/react-intl) ecosystem.
|
|
22
27
|
|
|
23
|
-
##
|
|
28
|
+
## Table of Contents
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
- **react-intl** provides React components and hooks (like `<FormattedMessage>` and `useIntl()`) to display localized strings.
|
|
30
|
+
<TOC/>
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
## What is Intlayer?
|
|
33
|
+
|
|
34
|
+
**Intlayer** is an innovative, open-source internationalization (i18n) library designed to simplify multilingual support in modern web applications. Intlayer seamlessly integrates with popular React frameworks, including **react-intl**.
|
|
35
|
+
|
|
36
|
+
With Intlayer, you can:
|
|
37
|
+
|
|
38
|
+
- **Easily manage translations** using declarative dictionaries at the component level.
|
|
39
|
+
- **Dynamically localize content** throughout your application.
|
|
40
|
+
- **Access translations in both client-side and server-side components**.
|
|
41
|
+
- **Ensure TypeScript support** with autogenerated types, improving autocompletion and error detection.
|
|
42
|
+
- **Benefit from advanced features**, like dynamic locale detection and switching.
|
|
43
|
+
- **Maintain component-level translations** to prevent orphaned translations when components are moved or deleted.
|
|
44
|
+
|
|
45
|
+
> Intlayer also integrates with Next.js, Express, React, and other popular frameworks. Check out our documentation for framework-specific guides.
|
|
29
46
|
|
|
30
47
|
---
|
|
31
48
|
|
|
32
|
-
##
|
|
49
|
+
## Intlayer vs. react-intl: Key Differences
|
|
50
|
+
|
|
51
|
+
For a deeper analysis of how Intlayer compares to other i18n libraries for React (such as react-intl), check out the [react-i18next vs. react-intl vs. Intlayer blog post](https://github.com/aymericzip/intlayer/blob/main/docs/blog/en/react-i18next_vs_react-intl_vs_intlayer.md).
|
|
33
52
|
|
|
34
|
-
|
|
35
|
-
Intlayer content declaration files can live alongside your React components, preventing “orphaned” translations if components are moved or removed. For example:
|
|
53
|
+
**Key advantages of using Intlayer with react-intl:**
|
|
36
54
|
|
|
37
|
-
|
|
38
|
-
.
|
|
39
|
-
└── src
|
|
40
|
-
└── components
|
|
41
|
-
└── MyComponent
|
|
42
|
-
├── index.content.ts # Intlayer content declaration
|
|
43
|
-
└── index.tsx # React component
|
|
44
|
-
```
|
|
55
|
+
1. **Component-Level Dictionaries**
|
|
56
|
+
Intlayer content declaration files can live alongside your React components, preventing "orphaned" translations if components are moved or removed.
|
|
45
57
|
|
|
46
58
|
2. **Centralized Translations**
|
|
47
|
-
Each content declaration file collects all translations needed by a component. This is particularly helpful in TypeScript projects
|
|
59
|
+
Each content declaration file collects all translations needed by a component. This is particularly helpful in TypeScript projects where missing translations can be caught at compile time.
|
|
48
60
|
|
|
49
61
|
3. **Automatic Build and Regeneration**
|
|
50
|
-
Whenever you add or update translations, Intlayer regenerates message JSON files.
|
|
62
|
+
Whenever you add or update translations, Intlayer regenerates message JSON files automatically.
|
|
63
|
+
|
|
64
|
+
4. **Type Safety**
|
|
65
|
+
TypeScript integration ensures that missing or incorrect translation keys are caught during development.
|
|
51
66
|
|
|
52
67
|
---
|
|
53
68
|
|
|
54
|
-
##
|
|
69
|
+
## Step-by-Step Guide to Set Up Intlayer in a React Application with react-intl
|
|
55
70
|
|
|
56
|
-
|
|
71
|
+
### Step 1: Install Dependencies
|
|
57
72
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
npm
|
|
73
|
+
Install the necessary packages using your preferred package manager:
|
|
74
|
+
|
|
75
|
+
```bash packageManager="npm"
|
|
76
|
+
npm install intlayer react-intl @intlayer/sync-json-plugin
|
|
77
|
+
```
|
|
61
78
|
|
|
62
|
-
|
|
63
|
-
|
|
79
|
+
```bash packageManager="pnpm"
|
|
80
|
+
pnpm add intlayer react-intl @intlayer/sync-json-plugin
|
|
81
|
+
```
|
|
64
82
|
|
|
65
|
-
|
|
66
|
-
|
|
83
|
+
```bash packageManager="yarn"
|
|
84
|
+
yarn add intlayer react-intl @intlayer/sync-json-plugin
|
|
67
85
|
```
|
|
68
86
|
|
|
69
|
-
|
|
87
|
+
#### Why These Packages?
|
|
70
88
|
|
|
71
89
|
- **intlayer**: Core CLI and library that scans for content declarations, merges them, and builds dictionary outputs.
|
|
72
|
-
- **react-intl**: The main library from FormatJS that provides `<IntlProvider>`, `<FormattedMessage>`, `useIntl()
|
|
90
|
+
- **react-intl**: The main library from FormatJS that provides `<IntlProvider>`, `<FormattedMessage>`, `useIntl()`, and other internationalization primitives.
|
|
91
|
+
- **@intlayer/sync-json-plugin**: Plugin to automatically sync Intlayer dictionaries to react-intl compatible JSON files.
|
|
73
92
|
|
|
74
|
-
> If you don
|
|
93
|
+
> If you don't already have React installed, you'll also need `react` and `react-dom`.
|
|
75
94
|
|
|
76
|
-
|
|
95
|
+
### Step 2: Configure Your Project
|
|
77
96
|
|
|
78
|
-
|
|
97
|
+
Create a configuration file to define the languages and output settings for your application:
|
|
79
98
|
|
|
80
|
-
```typescript
|
|
99
|
+
```typescript fileName="intlayer.config.ts" codeFormat="typescript"
|
|
81
100
|
import { Locales, type IntlayerConfig } from "intlayer";
|
|
101
|
+
import { syncJSON } from "@intlayer/sync-json-plugin";
|
|
82
102
|
|
|
83
103
|
const config: IntlayerConfig = {
|
|
84
104
|
internationalization: {
|
|
85
|
-
|
|
86
|
-
|
|
105
|
+
locales: [
|
|
106
|
+
Locales.ENGLISH,
|
|
107
|
+
Locales.FRENCH,
|
|
108
|
+
Locales.SPANISH,
|
|
109
|
+
// Add your other locales here
|
|
110
|
+
],
|
|
87
111
|
defaultLocale: Locales.ENGLISH,
|
|
88
112
|
},
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
113
|
+
plugins: [
|
|
114
|
+
syncJSON({
|
|
115
|
+
// Define the output directory for react-intl message files
|
|
116
|
+
source: ({ key, locale }) => `./intl/messages/${locale}/${key}.json`,
|
|
117
|
+
}),
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export default config;
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```javascript fileName="intlayer.config.mjs" codeFormat="esm"
|
|
125
|
+
import { Locales } from "intlayer";
|
|
126
|
+
import { syncJSON } from "@intlayer/sync-json-plugin";
|
|
92
127
|
|
|
93
|
-
|
|
94
|
-
|
|
128
|
+
/** @type {import('intlayer').IntlayerConfig} */
|
|
129
|
+
const config = {
|
|
130
|
+
internationalization: {
|
|
131
|
+
locales: [
|
|
132
|
+
Locales.ENGLISH,
|
|
133
|
+
Locales.FRENCH,
|
|
134
|
+
Locales.SPANISH,
|
|
135
|
+
// Add your other locales here
|
|
136
|
+
],
|
|
137
|
+
defaultLocale: Locales.ENGLISH,
|
|
95
138
|
},
|
|
139
|
+
plugins: [
|
|
140
|
+
syncJSON({
|
|
141
|
+
// Define the output directory for react-intl message files
|
|
142
|
+
source: ({ key, locale }) => `./intl/messages/${locale}/${key}.json`,
|
|
143
|
+
}),
|
|
144
|
+
],
|
|
96
145
|
};
|
|
97
146
|
|
|
98
147
|
export default config;
|
|
99
148
|
```
|
|
100
149
|
|
|
101
|
-
|
|
150
|
+
```javascript fileName="intlayer.config.cjs" codeFormat="commonjs"
|
|
151
|
+
const { Locales } = require("intlayer");
|
|
152
|
+
const { syncJSON } = require("@intlayer/sync-json-plugin");
|
|
102
153
|
|
|
103
|
-
|
|
154
|
+
/** @type {import('intlayer').IntlayerConfig} */
|
|
155
|
+
const config = {
|
|
156
|
+
internationalization: {
|
|
157
|
+
locales: [
|
|
158
|
+
Locales.ENGLISH,
|
|
159
|
+
Locales.FRENCH,
|
|
160
|
+
Locales.SPANISH,
|
|
161
|
+
// Add your other locales here
|
|
162
|
+
],
|
|
163
|
+
defaultLocale: Locales.ENGLISH,
|
|
164
|
+
},
|
|
165
|
+
plugins: [
|
|
166
|
+
syncJSON({
|
|
167
|
+
// Define the output directory for react-intl message files
|
|
168
|
+
source: ({ key, locale }) => `./intl/messages/${locale}/${key}.json`,
|
|
169
|
+
}),
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
module.exports = config;
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
> Through this configuration file, you can set up locales, output directories, content file patterns, and more. For a complete list of available parameters, refer to the [configuration documentation](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/configuration.md).
|
|
104
177
|
|
|
105
|
-
|
|
178
|
+
### Step 3: Integrate Intlayer in Your Build Process
|
|
179
|
+
|
|
180
|
+
Configure your build scripts to run Intlayer when building your application. Add the Intlayer build command to your `package.json`:
|
|
181
|
+
|
|
182
|
+
```json fileName="package.json"
|
|
183
|
+
{
|
|
184
|
+
"scripts": {
|
|
185
|
+
"build": "intlayer build && vite build",
|
|
186
|
+
"dev": "intlayer build && vite dev"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
106
190
|
|
|
107
|
-
|
|
108
|
-
Here’s a **TypeScript** example:
|
|
191
|
+
> The `intlayer build` command scans your content declaration files, compiles them, and generates the JSON message files that react-intl will consume.
|
|
109
192
|
|
|
110
|
-
|
|
193
|
+
### Step 4: Declare Your Content
|
|
194
|
+
|
|
195
|
+
Create and manage your content declarations to store translations. Intlayer scans your codebase (by default, under `./src`) for files matching `*.content.{ts,tsx,js,jsx,mjs,mjx,cjs,cjx,json}`.
|
|
196
|
+
|
|
197
|
+
Here's a **TypeScript** example:
|
|
198
|
+
|
|
199
|
+
```typescript fileName="src/components/MyComponent/index.content.ts" contentDeclarationFormat="typescript"
|
|
111
200
|
import { t, type Dictionary } from "intlayer";
|
|
112
201
|
|
|
113
202
|
const content = {
|
|
114
|
-
// "key" becomes the
|
|
203
|
+
// "key" becomes the namespace in your react-intl JSON files
|
|
115
204
|
key: "my-component",
|
|
116
205
|
|
|
117
206
|
content: {
|
|
118
207
|
// Each call to t() declares a translatable field
|
|
119
208
|
helloWorld: t({
|
|
120
209
|
en: "Hello World",
|
|
121
|
-
es: "Hola Mundo",
|
|
122
210
|
fr: "Bonjour le monde",
|
|
211
|
+
es: "Hola Mundo",
|
|
123
212
|
}),
|
|
124
213
|
description: t({
|
|
125
214
|
en: "This is a description",
|
|
126
215
|
fr: "Ceci est une description",
|
|
127
216
|
es: "Esta es una descripción",
|
|
128
217
|
}),
|
|
218
|
+
welcomeMessage: t({
|
|
219
|
+
en: "Welcome to our application",
|
|
220
|
+
fr: "Bienvenue dans notre application",
|
|
221
|
+
es: "Bienvenido a nuestra aplicación",
|
|
222
|
+
}),
|
|
129
223
|
},
|
|
130
224
|
} satisfies Dictionary;
|
|
131
225
|
|
|
132
226
|
export default content;
|
|
133
227
|
```
|
|
134
228
|
|
|
135
|
-
|
|
229
|
+
```javascript fileName="src/components/MyComponent/index.content.mjs" contentDeclarationFormat="esm"
|
|
230
|
+
import { t } from "intlayer";
|
|
136
231
|
|
|
137
|
-
|
|
232
|
+
/** @type {import('intlayer').Dictionary} */
|
|
233
|
+
const content = {
|
|
234
|
+
key: "my-component",
|
|
138
235
|
|
|
139
|
-
|
|
236
|
+
content: {
|
|
237
|
+
helloWorld: t({
|
|
238
|
+
en: "Hello World",
|
|
239
|
+
fr: "Bonjour le monde",
|
|
240
|
+
es: "Hola Mundo",
|
|
241
|
+
}),
|
|
242
|
+
description: t({
|
|
243
|
+
en: "This is a description",
|
|
244
|
+
fr: "Ceci est une description",
|
|
245
|
+
es: "Esta es una descripción",
|
|
246
|
+
}),
|
|
247
|
+
welcomeMessage: t({
|
|
248
|
+
en: "Welcome to our application",
|
|
249
|
+
fr: "Bienvenue dans notre application",
|
|
250
|
+
es: "Bienvenido a nuestra aplicación",
|
|
251
|
+
}),
|
|
252
|
+
},
|
|
253
|
+
};
|
|
140
254
|
|
|
141
|
-
|
|
255
|
+
export default content;
|
|
256
|
+
```
|
|
142
257
|
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
npx intlayer dictionaries build
|
|
258
|
+
```javascript fileName="src/components/MyComponent/index.content.cjs" contentDeclarationFormat="commonjs"
|
|
259
|
+
const { t } = require("intlayer");
|
|
146
260
|
|
|
147
|
-
|
|
148
|
-
|
|
261
|
+
/** @type {import('intlayer').Dictionary} */
|
|
262
|
+
const content = {
|
|
263
|
+
key: "my-component",
|
|
149
264
|
|
|
150
|
-
|
|
265
|
+
content: {
|
|
266
|
+
helloWorld: t({
|
|
267
|
+
en: "Hello World",
|
|
268
|
+
fr: "Bonjour le monde",
|
|
269
|
+
es: "Hola Mundo",
|
|
270
|
+
}),
|
|
271
|
+
description: t({
|
|
272
|
+
en: "This is a description",
|
|
273
|
+
fr: "Ceci est une description",
|
|
274
|
+
es: "Esta es una descripción",
|
|
275
|
+
}),
|
|
276
|
+
welcomeMessage: t({
|
|
277
|
+
en: "Welcome to our application",
|
|
278
|
+
fr: "Bienvenue dans notre application",
|
|
279
|
+
es: "Bienvenido a nuestra aplicación",
|
|
280
|
+
}),
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
module.exports = content;
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
```json fileName="src/components/MyComponent/index.content.json" contentDeclarationFormat="json"
|
|
288
|
+
{
|
|
289
|
+
"$schema": "https://intlayer.org/schema.json",
|
|
290
|
+
"key": "my-component",
|
|
291
|
+
"content": {
|
|
292
|
+
"helloWorld": {
|
|
293
|
+
"nodeType": "translation",
|
|
294
|
+
"translation": {
|
|
295
|
+
"en": "Hello World",
|
|
296
|
+
"fr": "Bonjour le monde",
|
|
297
|
+
"es": "Hola Mundo"
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
"description": {
|
|
301
|
+
"nodeType": "translation",
|
|
302
|
+
"translation": {
|
|
303
|
+
"en": "This is a description",
|
|
304
|
+
"fr": "Ceci est une description",
|
|
305
|
+
"es": "Esta es una descripción"
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
"welcomeMessage": {
|
|
309
|
+
"nodeType": "translation",
|
|
310
|
+
"translation": {
|
|
311
|
+
"en": "Welcome to our application",
|
|
312
|
+
"fr": "Bienvenue dans notre application",
|
|
313
|
+
"es": "Bienvenido a nuestra aplicación"
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
> Your content declarations can be defined anywhere in your application as long as they are included in the `contentDir` directory (by default, `./src`) and match the content declaration file extension pattern.
|
|
321
|
+
|
|
322
|
+
> For more details, refer to the [content declaration documentation](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/content_file.md).
|
|
323
|
+
|
|
324
|
+
### Step 5: Build the react-intl Messages
|
|
325
|
+
|
|
326
|
+
To generate the actual message JSON files for **react-intl**, run:
|
|
327
|
+
|
|
328
|
+
```bash packageManager="npm"
|
|
329
|
+
npx intlayer build
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
```bash packageManager="pnpm"
|
|
151
333
|
pnpm intlayer build
|
|
152
334
|
```
|
|
153
335
|
|
|
154
|
-
|
|
155
|
-
|
|
336
|
+
```bash packageManager="yarn"
|
|
337
|
+
yarn intlayer build
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
This command scans all `*.content.*` files, compiles them, and writes the results to the directory specified in your **`intlayer.config.ts`**—in this example, `./intl/messages`.
|
|
341
|
+
|
|
342
|
+
A typical output structure might look like:
|
|
156
343
|
|
|
157
344
|
```bash
|
|
158
345
|
.
|
|
159
|
-
└──
|
|
346
|
+
└── intl
|
|
160
347
|
└── messages
|
|
161
|
-
├── en
|
|
162
|
-
|
|
163
|
-
|
|
348
|
+
├── en
|
|
349
|
+
│ └── my-component.json
|
|
350
|
+
├── fr
|
|
351
|
+
│ └── my-component.json
|
|
352
|
+
└── es
|
|
353
|
+
└── my-component.json
|
|
164
354
|
```
|
|
165
355
|
|
|
166
|
-
Each file is a JSON object
|
|
167
|
-
|
|
168
|
-
For example, the **en.json** might look like:
|
|
356
|
+
Each file is a JSON object with keys corresponding to the `content` properties defined in your Intlayer dictionaries. For example, **en/my-component.json** might look like:
|
|
169
357
|
|
|
170
|
-
```json fileName="
|
|
358
|
+
```json fileName="intl/messages/en/my-component.json"
|
|
171
359
|
{
|
|
172
360
|
"helloWorld": "Hello World",
|
|
173
|
-
"description": "This is a description"
|
|
361
|
+
"description": "This is a description",
|
|
362
|
+
"welcomeMessage": "Welcome to our application"
|
|
174
363
|
}
|
|
175
364
|
```
|
|
176
365
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
## Initializing react-intl in Your React App
|
|
366
|
+
### Step 6: Initialize react-intl in Your React App
|
|
180
367
|
|
|
181
|
-
|
|
368
|
+
#### 6.1. Load the Generated Messages
|
|
182
369
|
|
|
183
|
-
|
|
370
|
+
In your application's entry point (e.g., `src/main.tsx` or `src/index.tsx`), you need to:
|
|
184
371
|
|
|
185
372
|
1. **Import** the generated message files (either statically or dynamically).
|
|
186
373
|
2. **Provide** them to `<IntlProvider>` from `react-intl`.
|
|
187
374
|
|
|
188
|
-
|
|
375
|
+
Here's an example using **Vite's** `import.meta.glob`:
|
|
189
376
|
|
|
190
|
-
```typescript
|
|
377
|
+
```typescript fileName="src/main.tsx" codeFormat="typescript"
|
|
191
378
|
import React from "react";
|
|
192
379
|
import ReactDOM from "react-dom/client";
|
|
193
380
|
import { IntlProvider } from "react-intl";
|
|
194
381
|
import App from "./App";
|
|
195
382
|
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
383
|
+
// Dynamically import all JSON message files
|
|
384
|
+
const messages = import.meta.glob<Record<string, string>>(
|
|
385
|
+
"../intl/messages/**/*.json",
|
|
386
|
+
{
|
|
387
|
+
eager: true,
|
|
388
|
+
}
|
|
389
|
+
);
|
|
201
390
|
|
|
391
|
+
// Structure messages by locale
|
|
392
|
+
const messagesRecord: Record<string, Record<string, string>> = {};
|
|
202
393
|
|
|
394
|
+
Object.entries(messages).forEach(([path, module]) => {
|
|
395
|
+
// Extract locale and namespace from file path
|
|
396
|
+
// Example path: "../intl/messages/en/my-component.json"
|
|
397
|
+
const match = path.match(/messages\/(\w+)\/(.+?)\.json$/);
|
|
398
|
+
if (match) {
|
|
399
|
+
const [, locale, namespace] = match;
|
|
400
|
+
if (!messagesRecord[locale]) {
|
|
401
|
+
messagesRecord[locale] = {};
|
|
402
|
+
}
|
|
403
|
+
// Flatten all messages for the locale
|
|
404
|
+
Object.assign(messagesRecord[locale], module.default || module);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// Detect user locale (you can implement more sophisticated logic)
|
|
409
|
+
const userLocale = navigator.language.split("-")[0] || "en";
|
|
410
|
+
const locale = messagesRecord[userLocale] ? userLocale : "en";
|
|
411
|
+
|
|
412
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
413
|
+
<React.StrictMode>
|
|
414
|
+
<IntlProvider locale={locale} messages={messagesRecord[locale]}>
|
|
415
|
+
<App />
|
|
416
|
+
</IntlProvider>
|
|
417
|
+
</React.StrictMode>
|
|
418
|
+
);
|
|
419
|
+
```
|
|
203
420
|
|
|
204
|
-
|
|
205
|
-
|
|
421
|
+
```javascript fileName="src/main.mjs" codeFormat="esm"
|
|
422
|
+
import React from "react";
|
|
423
|
+
import ReactDOM from "react-dom/client";
|
|
424
|
+
import { IntlProvider } from "react-intl";
|
|
425
|
+
import App from "./App";
|
|
426
|
+
|
|
427
|
+
// Dynamically import all JSON message files
|
|
428
|
+
const messages = import.meta.glob("../intl/messages/**/*.json", {
|
|
206
429
|
eager: true,
|
|
207
430
|
});
|
|
208
431
|
|
|
209
|
-
//
|
|
210
|
-
const messagesRecord
|
|
432
|
+
// Structure messages by locale
|
|
433
|
+
const messagesRecord = {};
|
|
211
434
|
|
|
212
435
|
Object.entries(messages).forEach(([path, module]) => {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
436
|
+
const match = path.match(/messages\/(\w+)\/(.+?)\.json$/);
|
|
437
|
+
if (match) {
|
|
438
|
+
const [, locale, namespace] = match;
|
|
439
|
+
if (!messagesRecord[locale]) {
|
|
440
|
+
messagesRecord[locale] = {};
|
|
441
|
+
}
|
|
442
|
+
Object.assign(messagesRecord[locale], module.default || module);
|
|
218
443
|
}
|
|
219
444
|
});
|
|
220
445
|
|
|
221
|
-
//
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
(acc, namespaceMessages) => ({ ...acc, ...namespaceMessages }),
|
|
225
|
-
{}
|
|
226
|
-
);
|
|
446
|
+
// Detect user locale
|
|
447
|
+
const userLocale = navigator.language.split("-")[0] || "en";
|
|
448
|
+
const locale = messagesRecord[userLocale] ? userLocale : "en";
|
|
227
449
|
|
|
228
|
-
|
|
229
|
-
// For simplicity, let's pick English.
|
|
230
|
-
const locale = "en";
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
|
450
|
+
ReactDOM.createRoot(document.getElementById("root")).render(
|
|
234
451
|
<React.StrictMode>
|
|
235
|
-
<IntlProvider locale={locale} messages={
|
|
452
|
+
<IntlProvider locale={locale} messages={messagesRecord[locale]}>
|
|
236
453
|
<App />
|
|
237
454
|
</IntlProvider>
|
|
238
455
|
</React.StrictMode>
|
|
239
456
|
);
|
|
240
457
|
```
|
|
241
458
|
|
|
242
|
-
|
|
459
|
+
```javascript fileName="src/main.cjs" codeFormat="commonjs"
|
|
460
|
+
const React = require("react");
|
|
461
|
+
const ReactDOM = require("react-dom/client");
|
|
462
|
+
const { IntlProvider } = require("react-intl");
|
|
463
|
+
const App = require("./App");
|
|
464
|
+
|
|
465
|
+
// For CommonJS, you'll need to load messages differently
|
|
466
|
+
// This example assumes you have a way to import JSON files
|
|
467
|
+
const enMessages = require("../intl/messages/en/my-component.json");
|
|
468
|
+
const frMessages = require("../intl/messages/fr/my-component.json");
|
|
469
|
+
const esMessages = require("../intl/messages/es/my-component.json");
|
|
470
|
+
|
|
471
|
+
const messagesRecord = {
|
|
472
|
+
en: enMessages,
|
|
473
|
+
fr: frMessages,
|
|
474
|
+
es: esMessages,
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// Detect user locale
|
|
478
|
+
const userLocale = navigator.language.split("-")[0] || "en";
|
|
479
|
+
const locale = messagesRecord[userLocale] ? userLocale : "en";
|
|
480
|
+
|
|
481
|
+
ReactDOM.createRoot(document.getElementById("root")).render(
|
|
482
|
+
React.createElement(
|
|
483
|
+
React.StrictMode,
|
|
484
|
+
null,
|
|
485
|
+
React.createElement(
|
|
486
|
+
IntlProvider,
|
|
487
|
+
{ locale: locale, messages: messagesRecord[locale] },
|
|
488
|
+
React.createElement(App)
|
|
489
|
+
)
|
|
490
|
+
)
|
|
491
|
+
);
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
> **Tip**: For production applications, consider:
|
|
243
495
|
>
|
|
244
|
-
> -
|
|
245
|
-
> -
|
|
496
|
+
> - Implementing proper locale detection based on user preferences, browser settings, or user accounts
|
|
497
|
+
> - Loading messages dynamically to reduce initial bundle size
|
|
498
|
+
> - Using a locale switcher component to allow users to change languages
|
|
499
|
+
|
|
500
|
+
#### 6.2. Create a Locale Context (Optional but Recommended)
|
|
501
|
+
|
|
502
|
+
For better locale management, create a context that allows components to access and change the current locale:
|
|
503
|
+
|
|
504
|
+
```typescript fileName="src/context/LocaleContext.tsx" codeFormat="typescript"
|
|
505
|
+
import React, {
|
|
506
|
+
createContext,
|
|
507
|
+
useContext,
|
|
508
|
+
useState,
|
|
509
|
+
ReactNode,
|
|
510
|
+
useEffect,
|
|
511
|
+
} from "react";
|
|
512
|
+
import { IntlProvider } from "react-intl";
|
|
246
513
|
|
|
247
|
-
|
|
514
|
+
interface LocaleContextType {
|
|
515
|
+
locale: string;
|
|
516
|
+
setLocale: (locale: string) => void;
|
|
517
|
+
availableLocales: string[];
|
|
518
|
+
}
|
|
248
519
|
|
|
249
|
-
|
|
520
|
+
const LocaleContext = createContext<LocaleContextType | undefined>(undefined);
|
|
250
521
|
|
|
251
|
-
|
|
252
|
-
|
|
522
|
+
// Load messages
|
|
523
|
+
const messages = import.meta.glob<Record<string, string>>(
|
|
524
|
+
"../../intl/messages/**/*.json",
|
|
525
|
+
{
|
|
526
|
+
eager: true,
|
|
527
|
+
}
|
|
528
|
+
);
|
|
253
529
|
|
|
254
|
-
|
|
530
|
+
const messagesRecord: Record<string, Record<string, string>> = {};
|
|
531
|
+
|
|
532
|
+
Object.entries(messages).forEach(([path, module]) => {
|
|
533
|
+
const match = path.match(/messages\/(\w+)\/(.+?)\.json$/);
|
|
534
|
+
if (match) {
|
|
535
|
+
const [, locale] = match;
|
|
536
|
+
if (!messagesRecord[locale]) {
|
|
537
|
+
messagesRecord[locale] = {};
|
|
538
|
+
}
|
|
539
|
+
Object.assign(messagesRecord[locale], module.default || module);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
255
542
|
|
|
256
|
-
|
|
543
|
+
const availableLocales = Object.keys(messagesRecord);
|
|
544
|
+
|
|
545
|
+
export const LocaleProvider: React.FC<{ children: ReactNode }> = ({
|
|
546
|
+
children,
|
|
547
|
+
}) => {
|
|
548
|
+
const [locale, setLocaleState] = useState<string>(() => {
|
|
549
|
+
// Get locale from localStorage or browser
|
|
550
|
+
const saved = localStorage.getItem("locale");
|
|
551
|
+
if (saved && availableLocales.includes(saved)) {
|
|
552
|
+
return saved;
|
|
553
|
+
}
|
|
554
|
+
const browserLocale = navigator.language.split("-")[0];
|
|
555
|
+
return availableLocales.includes(browserLocale) ? browserLocale : "en";
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
const setLocale = (newLocale: string) => {
|
|
559
|
+
if (availableLocales.includes(newLocale)) {
|
|
560
|
+
setLocaleState(newLocale);
|
|
561
|
+
localStorage.setItem("locale", newLocale);
|
|
562
|
+
}
|
|
563
|
+
};
|
|
257
564
|
|
|
258
|
-
|
|
565
|
+
return (
|
|
566
|
+
<LocaleContext.Provider value={{ locale, setLocale, availableLocales }}>
|
|
567
|
+
<IntlProvider locale={locale} messages={messagesRecord[locale]}>
|
|
568
|
+
{children}
|
|
569
|
+
</IntlProvider>
|
|
570
|
+
</LocaleContext.Provider>
|
|
571
|
+
);
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
export const useLocale = () => {
|
|
575
|
+
const context = useContext(LocaleContext);
|
|
576
|
+
if (!context) {
|
|
577
|
+
throw new Error("useLocale must be used within LocaleProvider");
|
|
578
|
+
}
|
|
579
|
+
return context;
|
|
580
|
+
};
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
```javascript fileName="src/context/LocaleContext.mjs" codeFormat="esm"
|
|
584
|
+
import React, { createContext, useContext, useState } from "react";
|
|
585
|
+
import { IntlProvider } from "react-intl";
|
|
586
|
+
|
|
587
|
+
const LocaleContext = createContext(undefined);
|
|
588
|
+
|
|
589
|
+
// Load messages
|
|
590
|
+
const messages = import.meta.glob("../../intl/messages/**/*.json", {
|
|
591
|
+
eager: true,
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
const messagesRecord = {};
|
|
595
|
+
|
|
596
|
+
Object.entries(messages).forEach(([path, module]) => {
|
|
597
|
+
const match = path.match(/messages\/(\w+)\/(.+?)\.json$/);
|
|
598
|
+
if (match) {
|
|
599
|
+
const [, locale] = match;
|
|
600
|
+
if (!messagesRecord[locale]) {
|
|
601
|
+
messagesRecord[locale] = {};
|
|
602
|
+
}
|
|
603
|
+
Object.assign(messagesRecord[locale], module.default || module);
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
const availableLocales = Object.keys(messagesRecord);
|
|
608
|
+
|
|
609
|
+
export const LocaleProvider = ({ children }) => {
|
|
610
|
+
const [locale, setLocaleState] = useState(() => {
|
|
611
|
+
const saved = localStorage.getItem("locale");
|
|
612
|
+
if (saved && availableLocales.includes(saved)) {
|
|
613
|
+
return saved;
|
|
614
|
+
}
|
|
615
|
+
const browserLocale = navigator.language.split("-")[0];
|
|
616
|
+
return availableLocales.includes(browserLocale) ? browserLocale : "en";
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
const setLocale = (newLocale) => {
|
|
620
|
+
if (availableLocales.includes(newLocale)) {
|
|
621
|
+
setLocaleState(newLocale);
|
|
622
|
+
localStorage.setItem("locale", newLocale);
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
return (
|
|
627
|
+
<LocaleContext.Provider value={{ locale, setLocale, availableLocales }}>
|
|
628
|
+
<IntlProvider locale={locale} messages={messagesRecord[locale]}>
|
|
629
|
+
{children}
|
|
630
|
+
</IntlProvider>
|
|
631
|
+
</LocaleContext.Provider>
|
|
632
|
+
);
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
export const useLocale = () => {
|
|
636
|
+
const context = useContext(LocaleContext);
|
|
637
|
+
if (!context) {
|
|
638
|
+
throw new Error("useLocale must be used within LocaleProvider");
|
|
639
|
+
}
|
|
640
|
+
return context;
|
|
641
|
+
};
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
Now update your main entry file to use the `LocaleProvider`:
|
|
645
|
+
|
|
646
|
+
```typescript fileName="src/main.tsx" codeFormat="typescript"
|
|
647
|
+
import React from "react";
|
|
648
|
+
import ReactDOM from "react-dom/client";
|
|
649
|
+
import { LocaleProvider } from "./context/LocaleContext";
|
|
650
|
+
import App from "./App";
|
|
651
|
+
|
|
652
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
653
|
+
<React.StrictMode>
|
|
654
|
+
<LocaleProvider>
|
|
655
|
+
<App />
|
|
656
|
+
</LocaleProvider>
|
|
657
|
+
</React.StrictMode>
|
|
658
|
+
);
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Step 7: Utilize Content in Your Code
|
|
662
|
+
|
|
663
|
+
Access your translations throughout your application using react-intl's components and hooks.
|
|
664
|
+
|
|
665
|
+
#### Approach A: Using `<FormattedMessage>`
|
|
259
666
|
|
|
260
667
|
For quick inline usage:
|
|
261
668
|
|
|
262
|
-
```tsx
|
|
669
|
+
```tsx fileName="src/components/MyComponent/index.tsx" codeFormat="typescript"
|
|
263
670
|
import React from "react";
|
|
264
671
|
import { FormattedMessage } from "react-intl";
|
|
265
672
|
|
|
266
|
-
|
|
673
|
+
const MyComponent: React.FC = () => {
|
|
267
674
|
return (
|
|
268
675
|
<div>
|
|
269
676
|
<h1>
|
|
270
|
-
{/* “my-component.helloWorld” references the key from en.json, fr.json, etc. */}
|
|
271
677
|
<FormattedMessage id="my-component.helloWorld" />
|
|
272
678
|
</h1>
|
|
679
|
+
<p>
|
|
680
|
+
<FormattedMessage id="my-component.description" />
|
|
681
|
+
</p>
|
|
682
|
+
<p>
|
|
683
|
+
<FormattedMessage id="my-component.welcomeMessage" />
|
|
684
|
+
</p>
|
|
685
|
+
</div>
|
|
686
|
+
);
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
export default MyComponent;
|
|
690
|
+
```
|
|
273
691
|
|
|
692
|
+
```jsx fileName="src/components/MyComponent/index.mjx" codeFormat="esm"
|
|
693
|
+
import React from "react";
|
|
694
|
+
import { FormattedMessage } from "react-intl";
|
|
695
|
+
|
|
696
|
+
const MyComponent = () => {
|
|
697
|
+
return (
|
|
698
|
+
<div>
|
|
699
|
+
<h1>
|
|
700
|
+
<FormattedMessage id="my-component.helloWorld" />
|
|
701
|
+
</h1>
|
|
274
702
|
<p>
|
|
275
703
|
<FormattedMessage id="my-component.description" />
|
|
276
704
|
</p>
|
|
705
|
+
<p>
|
|
706
|
+
<FormattedMessage id="my-component.welcomeMessage" />
|
|
707
|
+
</p>
|
|
277
708
|
</div>
|
|
278
709
|
);
|
|
279
|
-
}
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
export default MyComponent;
|
|
280
713
|
```
|
|
281
714
|
|
|
282
|
-
|
|
715
|
+
```jsx fileName="src/components/MyComponent/index.csx" codeFormat="commonjs"
|
|
716
|
+
const React = require("react");
|
|
717
|
+
const { FormattedMessage } = require("react-intl");
|
|
283
718
|
|
|
284
|
-
|
|
719
|
+
const MyComponent = () => {
|
|
720
|
+
return (
|
|
721
|
+
<div>
|
|
722
|
+
<h1>
|
|
723
|
+
<FormattedMessage id="my-component.helloWorld" />
|
|
724
|
+
</h1>
|
|
725
|
+
<p>
|
|
726
|
+
<FormattedMessage id="my-component.description" />
|
|
727
|
+
</p>
|
|
728
|
+
<p>
|
|
729
|
+
<FormattedMessage id="my-component.welcomeMessage" />
|
|
730
|
+
</p>
|
|
731
|
+
</div>
|
|
732
|
+
);
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
module.exports = MyComponent;
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
> The **`id`** prop in `<FormattedMessage>` must match the flattened key structure: `namespace.key` (e.g., `my-component.helloWorld`).
|
|
739
|
+
|
|
740
|
+
#### Approach B: Using `useIntl()`
|
|
741
|
+
|
|
742
|
+
For more dynamic usage and to access translations in JavaScript logic:
|
|
743
|
+
|
|
744
|
+
```tsx fileName="src/components/MyComponent/MyComponent.tsx" codeFormat="typescript"
|
|
745
|
+
import React from "react";
|
|
746
|
+
import { useIntl } from "react-intl";
|
|
747
|
+
|
|
748
|
+
const MyComponent: React.FC = () => {
|
|
749
|
+
const intl = useIntl();
|
|
750
|
+
|
|
751
|
+
return (
|
|
752
|
+
<div>
|
|
753
|
+
<h1>{intl.formatMessage({ id: "my-component.helloWorld" })}</h1>
|
|
754
|
+
<p>{intl.formatMessage({ id: "my-component.description" })}</p>
|
|
755
|
+
<button
|
|
756
|
+
aria-label={intl.formatMessage({ id: "my-component.welcomeMessage" })}
|
|
757
|
+
>
|
|
758
|
+
{intl.formatMessage({ id: "my-component.welcomeMessage" })}
|
|
759
|
+
</button>
|
|
760
|
+
</div>
|
|
761
|
+
);
|
|
762
|
+
};
|
|
285
763
|
|
|
286
|
-
|
|
764
|
+
export default MyComponent;
|
|
765
|
+
```
|
|
287
766
|
|
|
288
|
-
```
|
|
767
|
+
```jsx fileName="src/components/MyComponent/MyComponent.mjx" codeFormat="esm"
|
|
289
768
|
import React from "react";
|
|
290
769
|
import { useIntl } from "react-intl";
|
|
291
770
|
|
|
292
|
-
|
|
771
|
+
const MyComponent = () => {
|
|
293
772
|
const intl = useIntl();
|
|
294
773
|
|
|
295
774
|
return (
|
|
296
775
|
<div>
|
|
297
776
|
<h1>{intl.formatMessage({ id: "my-component.helloWorld" })}</h1>
|
|
298
777
|
<p>{intl.formatMessage({ id: "my-component.description" })}</p>
|
|
778
|
+
<button
|
|
779
|
+
aria-label={intl.formatMessage({ id: "my-component.welcomeMessage" })}
|
|
780
|
+
>
|
|
781
|
+
{intl.formatMessage({ id: "my-component.welcomeMessage" })}
|
|
782
|
+
</button>
|
|
783
|
+
</div>
|
|
784
|
+
);
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
export default MyComponent;
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
```jsx fileName="src/components/MyComponent/MyComponent.csx" codeFormat="commonjs"
|
|
791
|
+
const React = require("react");
|
|
792
|
+
const { useIntl } = require("react-intl");
|
|
793
|
+
|
|
794
|
+
const MyComponent = () => {
|
|
795
|
+
const intl = useIntl();
|
|
796
|
+
|
|
797
|
+
return (
|
|
798
|
+
<div>
|
|
799
|
+
<h1>{intl.formatMessage({ id: "my-component.helloWorld" })}</h1>
|
|
800
|
+
<p>{intl.formatMessage({ id: "my-component.description" })}</p>
|
|
801
|
+
<button
|
|
802
|
+
aria-label={intl.formatMessage({ id: "my-component.welcomeMessage" })}
|
|
803
|
+
>
|
|
804
|
+
{intl.formatMessage({ id: "my-component.welcomeMessage" })}
|
|
805
|
+
</button>
|
|
806
|
+
</div>
|
|
807
|
+
);
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
module.exports = MyComponent;
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
### (Optional) Step 8: Change the Language of Your Content
|
|
814
|
+
|
|
815
|
+
To allow users to switch languages, create a locale switcher component:
|
|
816
|
+
|
|
817
|
+
```tsx fileName="src/components/LocaleSwitcher.tsx" codeFormat="typescript"
|
|
818
|
+
import React from "react";
|
|
819
|
+
import { useLocale } from "../context/LocaleContext";
|
|
820
|
+
|
|
821
|
+
const localeNames: Record<string, string> = {
|
|
822
|
+
en: "English",
|
|
823
|
+
fr: "Français",
|
|
824
|
+
es: "Español",
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
const LocaleSwitcher: React.FC = () => {
|
|
828
|
+
const { locale, setLocale, availableLocales } = useLocale();
|
|
829
|
+
|
|
830
|
+
return (
|
|
831
|
+
<div>
|
|
832
|
+
<label htmlFor="locale-select">Choose language: </label>
|
|
833
|
+
<select
|
|
834
|
+
id="locale-select"
|
|
835
|
+
value={locale}
|
|
836
|
+
onChange={(e) => setLocale(e.target.value)}
|
|
837
|
+
>
|
|
838
|
+
{availableLocales.map((loc) => (
|
|
839
|
+
<option key={loc} value={loc}>
|
|
840
|
+
{localeNames[loc] || loc}
|
|
841
|
+
</option>
|
|
842
|
+
))}
|
|
843
|
+
</select>
|
|
844
|
+
</div>
|
|
845
|
+
);
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
export default LocaleSwitcher;
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
```jsx fileName="src/components/LocaleSwitcher.mjx" codeFormat="esm"
|
|
852
|
+
import React from "react";
|
|
853
|
+
import { useLocale } from "../context/LocaleContext";
|
|
854
|
+
|
|
855
|
+
const localeNames = {
|
|
856
|
+
en: "English",
|
|
857
|
+
fr: "Français",
|
|
858
|
+
es: "Español",
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
const LocaleSwitcher = () => {
|
|
862
|
+
const { locale, setLocale, availableLocales } = useLocale();
|
|
863
|
+
|
|
864
|
+
return (
|
|
865
|
+
<div>
|
|
866
|
+
<label htmlFor="locale-select">Choose language: </label>
|
|
867
|
+
<select
|
|
868
|
+
id="locale-select"
|
|
869
|
+
value={locale}
|
|
870
|
+
onChange={(e) => setLocale(e.target.value)}
|
|
871
|
+
>
|
|
872
|
+
{availableLocales.map((loc) => (
|
|
873
|
+
<option key={loc} value={loc}>
|
|
874
|
+
{localeNames[loc] || loc}
|
|
875
|
+
</option>
|
|
876
|
+
))}
|
|
877
|
+
</select>
|
|
299
878
|
</div>
|
|
300
879
|
);
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
export default LocaleSwitcher;
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
```jsx fileName="src/components/LocaleSwitcher.csx" codeFormat="commonjs"
|
|
886
|
+
const React = require("react");
|
|
887
|
+
const { useLocale } = require("../context/LocaleContext");
|
|
888
|
+
|
|
889
|
+
const localeNames = {
|
|
890
|
+
en: "English",
|
|
891
|
+
fr: "Français",
|
|
892
|
+
es: "Español",
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
const LocaleSwitcher = () => {
|
|
896
|
+
const { locale, setLocale, availableLocales } = useLocale();
|
|
897
|
+
|
|
898
|
+
return React.createElement(
|
|
899
|
+
"div",
|
|
900
|
+
null,
|
|
901
|
+
React.createElement(
|
|
902
|
+
"label",
|
|
903
|
+
{ htmlFor: "locale-select" },
|
|
904
|
+
"Choose language: "
|
|
905
|
+
),
|
|
906
|
+
React.createElement(
|
|
907
|
+
"select",
|
|
908
|
+
{
|
|
909
|
+
id: "locale-select",
|
|
910
|
+
value: locale,
|
|
911
|
+
onChange: (e) => setLocale(e.target.value),
|
|
912
|
+
},
|
|
913
|
+
availableLocales.map((loc) =>
|
|
914
|
+
React.createElement(
|
|
915
|
+
"option",
|
|
916
|
+
{ key: loc, value: loc },
|
|
917
|
+
localeNames[loc] || loc
|
|
918
|
+
)
|
|
919
|
+
)
|
|
920
|
+
)
|
|
921
|
+
);
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
module.exports = LocaleSwitcher;
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
Then use the `LocaleSwitcher` component in your app:
|
|
928
|
+
|
|
929
|
+
```tsx fileName="src/App.tsx" codeFormat="typescript"
|
|
930
|
+
import React from "react";
|
|
931
|
+
import MyComponent from "./components/MyComponent";
|
|
932
|
+
import LocaleSwitcher from "./components/LocaleSwitcher";
|
|
933
|
+
|
|
934
|
+
const App: React.FC = () => {
|
|
935
|
+
return (
|
|
936
|
+
<div>
|
|
937
|
+
<LocaleSwitcher />
|
|
938
|
+
<MyComponent />
|
|
939
|
+
</div>
|
|
940
|
+
);
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
export default App;
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
### (Optional) Step 9: Advanced Message Formatting
|
|
947
|
+
|
|
948
|
+
react-intl supports advanced formatting features like pluralization, date/time formatting, and number formatting. Here are some examples:
|
|
949
|
+
|
|
950
|
+
#### Pluralization
|
|
951
|
+
|
|
952
|
+
```typescript fileName="src/components/ItemCount.content.ts" codeFormat="typescript"
|
|
953
|
+
import { t, type Dictionary } from "intlayer";
|
|
954
|
+
|
|
955
|
+
const content = {
|
|
956
|
+
key: "item-count",
|
|
957
|
+
content: {
|
|
958
|
+
items: t({
|
|
959
|
+
en: "{count, plural, =0 {No items} one {One item} other {# items}}",
|
|
960
|
+
fr: "{count, plural, =0 {Aucun article} one {Un article} other {# articles}}",
|
|
961
|
+
es: "{count, plural, =0 {Sin artículos} one {Un artículo} other {# artículos}}",
|
|
962
|
+
}),
|
|
963
|
+
},
|
|
964
|
+
} satisfies Dictionary;
|
|
965
|
+
|
|
966
|
+
export default content;
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
```tsx fileName="src/components/ItemCount.tsx" codeFormat="typescript"
|
|
970
|
+
import React from "react";
|
|
971
|
+
import { FormattedMessage } from "react-intl";
|
|
972
|
+
|
|
973
|
+
interface ItemCountProps {
|
|
974
|
+
count: number;
|
|
301
975
|
}
|
|
976
|
+
|
|
977
|
+
const ItemCount: React.FC<ItemCountProps> = ({ count }) => {
|
|
978
|
+
return (
|
|
979
|
+
<p>
|
|
980
|
+
<FormattedMessage id="item-count.items" values={{ count }} />
|
|
981
|
+
</p>
|
|
982
|
+
);
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
export default ItemCount;
|
|
302
986
|
```
|
|
303
987
|
|
|
304
|
-
|
|
988
|
+
#### Date and Number Formatting
|
|
305
989
|
|
|
306
|
-
|
|
990
|
+
```tsx fileName="src/components/FormattedData.tsx" codeFormat="typescript"
|
|
991
|
+
import React from "react";
|
|
992
|
+
import { FormattedDate, FormattedNumber, FormattedTime } from "react-intl";
|
|
307
993
|
|
|
308
|
-
|
|
994
|
+
const FormattedData: React.FC = () => {
|
|
995
|
+
const today = new Date();
|
|
996
|
+
const price = 1234.56;
|
|
309
997
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
998
|
+
return (
|
|
999
|
+
<div>
|
|
1000
|
+
<p>
|
|
1001
|
+
Date: <FormattedDate value={today} />
|
|
1002
|
+
</p>
|
|
1003
|
+
<p>
|
|
1004
|
+
Time: <FormattedTime value={today} />
|
|
1005
|
+
</p>
|
|
1006
|
+
<p>
|
|
1007
|
+
Price: <FormattedNumber value={price} style="currency" currency="USD" />
|
|
1008
|
+
</p>
|
|
1009
|
+
</div>
|
|
1010
|
+
);
|
|
1011
|
+
};
|
|
313
1012
|
|
|
314
|
-
|
|
1013
|
+
export default FormattedData;
|
|
1014
|
+
```
|
|
315
1015
|
|
|
316
|
-
|
|
1016
|
+
### (Optional) Step 10: Handle Missing Translations
|
|
317
1017
|
|
|
318
|
-
|
|
1018
|
+
By default, react-intl will render the message ID if a translation is missing. You can customize this behavior:
|
|
319
1019
|
|
|
320
|
-
|
|
1020
|
+
```typescript fileName="src/main.tsx" codeFormat="typescript"
|
|
1021
|
+
import React from "react";
|
|
1022
|
+
import ReactDOM from "react-dom/client";
|
|
1023
|
+
import { IntlProvider } from "react-intl";
|
|
1024
|
+
import App from "./App";
|
|
321
1025
|
|
|
322
|
-
|
|
1026
|
+
// Custom handler for missing translations
|
|
1027
|
+
const onError = (err: any) => {
|
|
1028
|
+
if (err.code === "MISSING_TRANSLATION") {
|
|
1029
|
+
console.warn("Missing translation", err.message);
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
throw err;
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
1036
|
+
<React.StrictMode>
|
|
1037
|
+
<IntlProvider locale="en" messages={{}} onError={onError}>
|
|
1038
|
+
<App />
|
|
1039
|
+
</IntlProvider>
|
|
1040
|
+
</React.StrictMode>
|
|
1041
|
+
);
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
### (Optional) Step 11: TypeScript Integration
|
|
1045
|
+
|
|
1046
|
+
Intlayer can generate TypeScript type definitions for your translations, providing compile-time type safety.
|
|
1047
|
+
|
|
1048
|
+
Ensure your `tsconfig.json` includes the generated types:
|
|
1049
|
+
|
|
1050
|
+
```json5 fileName="tsconfig.json"
|
|
323
1051
|
{
|
|
324
1052
|
"compilerOptions": {
|
|
325
|
-
// ...
|
|
1053
|
+
// ... your other compiler options
|
|
326
1054
|
},
|
|
327
|
-
"include": [
|
|
1055
|
+
"include": [
|
|
1056
|
+
"src",
|
|
1057
|
+
".intlayer/**/*.ts", // Include Intlayer generated types
|
|
1058
|
+
],
|
|
328
1059
|
}
|
|
329
1060
|
```
|
|
330
1061
|
|
|
331
|
-
|
|
1062
|
+
This enables autocompletion and compile-time checks for translation keys in your IDE.
|
|
332
1063
|
|
|
333
|
-
|
|
1064
|
+
### Configure TypeScript
|
|
1065
|
+
|
|
1066
|
+
Intlayer uses module augmentation to get benefits of TypeScript and make your codebase stronger.
|
|
1067
|
+
|
|
1068
|
+

|
|
334
1069
|
|
|
335
|
-
|
|
1070
|
+

|
|
336
1071
|
|
|
337
|
-
|
|
1072
|
+
Ensure your TypeScript configuration includes the autogenerated types.
|
|
338
1073
|
|
|
339
|
-
```
|
|
340
|
-
|
|
1074
|
+
```json5 fileName="tsconfig.json"
|
|
1075
|
+
{
|
|
1076
|
+
// ... Your existing TypeScript configurations
|
|
1077
|
+
"include": [
|
|
1078
|
+
// ... Your existing TypeScript configurations
|
|
1079
|
+
".intlayer/**/*.ts", // Include the auto-generated types
|
|
1080
|
+
],
|
|
1081
|
+
}
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
### Git Configuration
|
|
1085
|
+
|
|
1086
|
+
It's recommended to ignore the files generated by Intlayer. This allows you to avoid committing them to your Git repository.
|
|
1087
|
+
|
|
1088
|
+
Add the following to your `.gitignore` file:
|
|
1089
|
+
|
|
1090
|
+
```plaintext fileName=".gitignore"
|
|
1091
|
+
# Ignore the files generated by Intlayer
|
|
341
1092
|
.intlayer
|
|
342
|
-
|
|
1093
|
+
|
|
1094
|
+
# Optionally ignore generated message files if they're rebuilt in CI/CD
|
|
1095
|
+
intl
|
|
343
1096
|
```
|
|
344
1097
|
|
|
345
|
-
Depending on your workflow, you may
|
|
1098
|
+
Depending on your workflow, you may want to commit the `./intl/messages` files if they're needed for production deployments. If your CI/CD pipeline regenerates them, you can safely ignore them.
|