@intlayer/docs 7.0.4-canary.0 → 7.0.4
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/ar/intlayer_with_i18next.md +68 -106
- package/blog/ar/intlayer_with_next-i18next.md +84 -288
- package/blog/ar/intlayer_with_next-intl.md +58 -337
- package/blog/ar/intlayer_with_react-i18next.md +68 -290
- package/blog/ar/intlayer_with_react-intl.md +63 -266
- package/blog/de/intlayer_with_i18next.md +77 -97
- package/blog/de/intlayer_with_next-i18next.md +69 -296
- package/blog/de/intlayer_with_next-intl.md +59 -340
- package/blog/de/intlayer_with_react-i18next.md +68 -290
- package/blog/de/intlayer_with_react-intl.md +62 -264
- package/blog/en/intlayer_with_i18next.md +36 -1638
- package/blog/en/intlayer_with_next-i18next.md +22 -847
- package/blog/en/intlayer_with_next-intl.md +32 -1053
- package/blog/en/intlayer_with_react-i18next.md +38 -764
- package/blog/en/intlayer_with_react-intl.md +42 -1018
- package/blog/en-GB/intlayer_with_i18next.md +67 -103
- package/blog/en-GB/intlayer_with_next-i18next.md +71 -292
- package/blog/en-GB/intlayer_with_next-intl.md +58 -337
- package/blog/en-GB/intlayer_with_react-i18next.md +67 -289
- package/blog/en-GB/intlayer_with_react-intl.md +61 -264
- package/blog/es/intlayer_with_i18next.md +67 -103
- package/blog/es/intlayer_with_next-i18next.md +71 -296
- package/blog/es/intlayer_with_next-intl.md +57 -338
- package/blog/es/intlayer_with_react-i18next.md +68 -290
- package/blog/es/intlayer_with_react-intl.md +62 -265
- package/blog/fr/intlayer_with_i18next.md +66 -104
- package/blog/fr/intlayer_with_next-i18next.md +82 -285
- package/blog/fr/intlayer_with_next-intl.md +57 -338
- package/blog/fr/intlayer_with_react-i18next.md +67 -289
- package/blog/fr/intlayer_with_react-intl.md +61 -264
- package/blog/hi/intlayer_with_i18next.md +68 -104
- package/blog/hi/intlayer_with_next-i18next.md +74 -299
- package/blog/hi/intlayer_with_next-intl.md +57 -239
- package/blog/hi/intlayer_with_react-i18next.md +69 -291
- package/blog/hi/intlayer_with_react-intl.md +65 -268
- package/blog/id/intlayer_with_i18next.md +126 -0
- package/blog/id/intlayer_with_next-i18next.md +142 -0
- package/blog/id/intlayer_with_next-intl.md +113 -0
- package/blog/id/intlayer_with_react-i18next.md +124 -0
- package/blog/id/intlayer_with_react-intl.md +122 -0
- package/blog/it/intlayer_with_i18next.md +67 -103
- package/blog/it/intlayer_with_next-i18next.md +71 -296
- package/blog/it/intlayer_with_next-intl.md +57 -338
- package/blog/it/intlayer_with_react-i18next.md +68 -290
- package/blog/it/intlayer_with_react-intl.md +62 -265
- package/blog/ja/intlayer_with_i18next.md +68 -103
- package/blog/ja/intlayer_with_next-i18next.md +85 -283
- package/blog/ja/intlayer_with_next-intl.md +58 -336
- package/blog/ja/intlayer_with_react-i18next.md +68 -290
- package/blog/ja/intlayer_with_react-intl.md +62 -264
- package/blog/ko/intlayer_with_i18next.md +80 -96
- package/blog/ko/intlayer_with_next-i18next.md +85 -287
- package/blog/ko/intlayer_with_next-intl.md +68 -327
- package/blog/ko/intlayer_with_react-i18next.md +68 -290
- package/blog/ko/intlayer_with_react-intl.md +64 -266
- package/blog/pl/intlayer_with_i18next.md +126 -0
- package/blog/pl/intlayer_with_next-i18next.md +142 -0
- package/blog/pl/intlayer_with_next-intl.md +111 -0
- package/blog/pl/intlayer_with_react-i18next.md +124 -0
- package/blog/pl/intlayer_with_react-intl.md +122 -0
- package/blog/pt/intlayer_with_i18next.md +67 -103
- package/blog/pt/intlayer_with_next-i18next.md +72 -293
- package/blog/pt/intlayer_with_next-intl.md +57 -256
- package/blog/pt/intlayer_with_react-i18next.md +104 -78
- package/blog/pt/intlayer_with_react-intl.md +62 -266
- package/blog/ru/intlayer_with_i18next.md +66 -104
- package/blog/ru/intlayer_with_next-i18next.md +71 -296
- package/blog/ru/intlayer_with_next-intl.md +58 -337
- package/blog/ru/intlayer_with_react-i18next.md +68 -290
- package/blog/ru/intlayer_with_react-intl.md +62 -265
- package/blog/tr/intlayer_with_i18next.md +71 -107
- package/blog/tr/intlayer_with_next-i18next.md +72 -297
- package/blog/tr/intlayer_with_next-intl.md +58 -339
- package/blog/tr/intlayer_with_react-i18next.md +69 -291
- package/blog/tr/intlayer_with_react-intl.md +63 -285
- package/blog/vi/intlayer_with_i18next.md +126 -0
- package/blog/vi/intlayer_with_next-i18next.md +142 -0
- package/blog/vi/intlayer_with_next-intl.md +111 -0
- package/blog/vi/intlayer_with_react-i18next.md +124 -0
- package/blog/vi/intlayer_with_react-intl.md +122 -0
- package/blog/zh/intlayer_with_i18next.md +67 -102
- package/blog/zh/intlayer_with_next-i18next.md +72 -296
- package/blog/zh/intlayer_with_next-intl.md +58 -336
- package/blog/zh/intlayer_with_react-i18next.md +68 -290
- package/blog/zh/intlayer_with_react-intl.md +63 -106
- package/docs/ar/plugins/sync-json.md +244 -0
- package/docs/de/plugins/sync-json.md +244 -0
- package/docs/en/intlayer_cli.md +25 -0
- package/docs/en/intlayer_with_nextjs_14.md +2 -0
- package/docs/en/intlayer_with_nextjs_15.md +2 -0
- package/docs/en/intlayer_with_nextjs_16.md +2 -0
- package/docs/en/plugins/sync-json.md +1 -1
- package/docs/en-GB/plugins/sync-json.md +244 -0
- package/docs/es/plugins/sync-json.md +244 -0
- package/docs/fr/plugins/sync-json.md +244 -0
- package/docs/hi/plugins/sync-json.md +244 -0
- package/docs/id/plugins/sync-json.md +244 -0
- package/docs/it/plugins/sync-json.md +244 -0
- package/docs/ja/plugins/sync-json.md +244 -0
- package/docs/ko/plugins/sync-json.md +244 -0
- package/docs/pl/plugins/sync-json.md +244 -0
- package/docs/pt/plugins/sync-json.md +244 -0
- package/docs/ru/plugins/sync-json.md +244 -0
- package/docs/tr/plugins/sync-json.md +245 -0
- package/docs/vi/plugins/sync-json.md +244 -0
- package/docs/zh/plugins/sync-json.md +244 -0
- package/package.json +14 -14
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
createdAt: 2025-01-02
|
|
3
3
|
updatedAt: 2025-10-29
|
|
4
|
-
title:
|
|
5
|
-
description:
|
|
6
|
-
keywords:
|
|
7
|
-
- next-intl
|
|
8
|
-
- Intlayer
|
|
9
|
-
- Internationalization
|
|
10
|
-
- i18n
|
|
11
|
-
- Blog
|
|
12
|
-
- Next.js
|
|
13
|
-
- JavaScript
|
|
14
|
-
- React
|
|
15
|
-
- TypeScript
|
|
4
|
+
title: How to automate your next-intl JSON translations using Intlayer
|
|
5
|
+
description: Automate your JSON translations with Intlayer and next-intl for enhanced internationalization in Next.js applications.
|
|
16
6
|
slugs:
|
|
17
7
|
- blog
|
|
18
8
|
- intlayer-with-next-intl
|
|
@@ -22,86 +12,30 @@ history:
|
|
|
22
12
|
changes: Change to syncJSON plugin
|
|
23
13
|
---
|
|
24
14
|
|
|
25
|
-
#
|
|
15
|
+
# How to automate your next-intl JSON translations using Intlayer
|
|
26
16
|
|
|
27
|
-
## What is
|
|
17
|
+
## What is Intlayer?
|
|
28
18
|
|
|
29
|
-
**
|
|
19
|
+
**Intlayer** is an innovative, open-source internationalization library designed to address the shortcomings of traditional i18n solutions. It offers a modern approach to content management in Next.js applications.
|
|
30
20
|
|
|
31
|
-
|
|
21
|
+
See a concrete comparison with next-intl in our [next-i18next vs. next-intl vs. Intlayer](https://github.com/aymericzip/intlayer/blob/main/docs/blog/en/next-i18next_vs_next-intl_vs_intlayer.md) blog post.
|
|
32
22
|
|
|
33
|
-
|
|
34
|
-
- **Flexible message structure** using JSON files
|
|
35
|
-
- **Type-safe translations** with TypeScript support
|
|
36
|
-
- **Middleware for automatic locale detection** and routing
|
|
37
|
-
- **Rich formatting capabilities** for dates, times, numbers, and relative time
|
|
23
|
+
## Why Combine Intlayer with next-intl?
|
|
38
24
|
|
|
39
|
-
|
|
25
|
+
While Intlayer provides an excellent standalone i18n solution (see our [Next.js integration guide](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_with_nextjs_16.md)), you might want to combine it with next-intl for several reasons:
|
|
40
26
|
|
|
41
|
-
|
|
27
|
+
1. **Existing codebase**: You have an established next-intl implementation and want to gradually migrate to Intlayer's improved developer experience.
|
|
28
|
+
2. **Legacy requirements**: Your project requires compatibility with existing next-intl plugins or workflows.
|
|
29
|
+
3. **Team familiarity**: Your team is comfortable with next-intl but wants better content management.
|
|
42
30
|
|
|
43
|
-
|
|
31
|
+
**For that, Intlayer can be implemented as an adapter for next-intl to help automating your JSON translations in CLI or CI/CD pipelines, testing your translations, and more.**
|
|
44
32
|
|
|
45
|
-
|
|
46
|
-
- **Type-safe translations**: Automatic TypeScript type generation with full autocompletion
|
|
47
|
-
- **Rich content support**: Handle complex content structures including React components, dates, and more
|
|
48
|
-
- **Visual editor integration**: Manage translations through a visual interface
|
|
49
|
-
- **Advanced features**: Dynamic locale detection, SEO optimization, and more
|
|
33
|
+
This guide shows you how to leverage Intlayer's superior content declaration system while maintaining compatibility with next-intl.
|
|
50
34
|
|
|
51
35
|
## Table of Contents
|
|
52
36
|
|
|
53
37
|
<TOC/>
|
|
54
38
|
|
|
55
|
-
## Intlayer vs. next-intl: Key Differences
|
|
56
|
-
|
|
57
|
-
Both next-intl and Intlayer are excellent i18n solutions for Next.js, but they take different approaches:
|
|
58
|
-
|
|
59
|
-
### Content Organization
|
|
60
|
-
|
|
61
|
-
**next-intl**: Uses centralized JSON files for each locale, typically organized like:
|
|
62
|
-
|
|
63
|
-
```
|
|
64
|
-
messages/
|
|
65
|
-
├── en.json
|
|
66
|
-
├── fr.json
|
|
67
|
-
└── es.json
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
**Intlayer**: Allows you to declare content alongside your components:
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
components/
|
|
74
|
-
└── MyComponent/
|
|
75
|
-
├── index.tsx
|
|
76
|
-
└── index.content.ts # Translations for this component
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Flexibility
|
|
80
|
-
|
|
81
|
-
**next-intl**: Great for traditional key-value translation patterns with JSON files.
|
|
82
|
-
|
|
83
|
-
**Intlayer**: Offers more flexibility with support for complex content structures, React components in translations, and powerful content management features.
|
|
84
|
-
|
|
85
|
-
### TypeScript Support
|
|
86
|
-
|
|
87
|
-
**next-intl**: Provides type safety through TypeScript integration.
|
|
88
|
-
|
|
89
|
-
**Intlayer**: Offers automatic type generation from your content declarations, ensuring complete type safety and better developer experience with autocompletion.
|
|
90
|
-
|
|
91
|
-
For a detailed comparison of Intlayer with other i18n libraries including next-intl, check out the [next-i18next vs. next-intl vs. Intlayer blog post](https://github.com/aymericzip/intlayer/blob/main/docs/blog/en/next-i18next_vs_next-intl_vs_intlayer.md).
|
|
92
|
-
|
|
93
|
-
## Why Use Intlayer with next-intl?
|
|
94
|
-
|
|
95
|
-
While you can choose to use either library exclusively, combining them can be beneficial in certain scenarios:
|
|
96
|
-
|
|
97
|
-
1. **Gradual Migration**: If you have an existing next-intl project and want to leverage Intlayer's features
|
|
98
|
-
2. **Component-Centric Workflow**: Use Intlayer's flexible content declaration alongside next-intl's infrastructure
|
|
99
|
-
3. **Best of Both Worlds**: Leverage next-intl's proven routing and formatting with Intlayer's developer experience
|
|
100
|
-
|
|
101
|
-
This guide shows you how to use Intlayer to generate next-intl compatible messages, allowing you to benefit from Intlayer's content declaration approach while maintaining compatibility with next-intl.
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
39
|
## Step-by-Step Guide to Set Up Intlayer with next-intl
|
|
106
40
|
|
|
107
41
|
### Step 1: Install Dependencies
|
|
@@ -109,45 +43,40 @@ This guide shows you how to use Intlayer to generate next-intl compatible messag
|
|
|
109
43
|
Install the necessary packages:
|
|
110
44
|
|
|
111
45
|
```bash packageManager="npm"
|
|
112
|
-
npm install intlayer
|
|
46
|
+
npm install intlayer @intlayer/sync-json-plugin
|
|
113
47
|
```
|
|
114
48
|
|
|
115
49
|
```bash packageManager="pnpm"
|
|
116
|
-
pnpm add intlayer
|
|
50
|
+
pnpm add intlayer @intlayer/sync-json-plugin
|
|
117
51
|
```
|
|
118
52
|
|
|
119
53
|
```bash packageManager="yarn"
|
|
120
|
-
yarn add intlayer
|
|
54
|
+
yarn add intlayer @intlayer/sync-json-plugin
|
|
121
55
|
```
|
|
122
56
|
|
|
123
57
|
**Package descriptions:**
|
|
124
58
|
|
|
125
59
|
- **intlayer**: Core library for internationalization management, content declaration, and building
|
|
126
|
-
- **next-intl**: The next-intl library for Next.js internationalization
|
|
127
60
|
- **@intlayer/sync-json-plugin**: Plugin to export Intlayer content declarations to next-intl compatible JSON format
|
|
128
61
|
|
|
129
|
-
### Step 2:
|
|
62
|
+
### Step 2: Implement the Intlayer plugin to wrap the JSON
|
|
63
|
+
|
|
64
|
+
Create an Intlayer configuration file to define your supported locales:
|
|
130
65
|
|
|
131
|
-
|
|
66
|
+
**If you want to also export JSON dictionaries for next-intl**, add the `syncJSON` plugin:
|
|
132
67
|
|
|
133
|
-
```typescript fileName="intlayer.config.ts"
|
|
68
|
+
```typescript fileName="intlayer.config.ts"
|
|
134
69
|
import { Locales, type IntlayerConfig } from "intlayer";
|
|
135
70
|
import { syncJSON } from "@intlayer/sync-json-plugin";
|
|
136
71
|
|
|
137
72
|
const config: IntlayerConfig = {
|
|
138
73
|
internationalization: {
|
|
139
|
-
locales: [
|
|
140
|
-
Locales.ENGLISH,
|
|
141
|
-
Locales.FRENCH,
|
|
142
|
-
Locales.SPANISH,
|
|
143
|
-
// Add your other locales
|
|
144
|
-
],
|
|
74
|
+
locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
|
|
145
75
|
defaultLocale: Locales.ENGLISH,
|
|
146
76
|
},
|
|
147
77
|
plugins: [
|
|
148
78
|
syncJSON({
|
|
149
|
-
|
|
150
|
-
source: ({ key, locale }) => `./intl/messages/${locale}/${key}.json`,
|
|
79
|
+
source: ({ key, locale }) => `./messages/${locale}/${key}.json`,
|
|
151
80
|
}),
|
|
152
81
|
],
|
|
153
82
|
};
|
|
@@ -155,960 +84,16 @@ const config: IntlayerConfig = {
|
|
|
155
84
|
export default config;
|
|
156
85
|
```
|
|
157
86
|
|
|
158
|
-
|
|
159
|
-
import { Locales } from "intlayer";
|
|
160
|
-
import { syncJSON } from "@intlayer/sync-json-plugin";
|
|
161
|
-
|
|
162
|
-
/** @type {import('intlayer').IntlayerConfig} */
|
|
163
|
-
const config = {
|
|
164
|
-
internationalization: {
|
|
165
|
-
locales: [
|
|
166
|
-
Locales.ENGLISH,
|
|
167
|
-
Locales.FRENCH,
|
|
168
|
-
Locales.SPANISH,
|
|
169
|
-
// Add your other locales
|
|
170
|
-
],
|
|
171
|
-
defaultLocale: Locales.ENGLISH,
|
|
172
|
-
},
|
|
173
|
-
plugins: [
|
|
174
|
-
syncJSON({
|
|
175
|
-
source: ({ key, locale }) => `./intl/messages/${locale}/${key}.json`,
|
|
176
|
-
}),
|
|
177
|
-
],
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
export default config;
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
```javascript fileName="intlayer.config.cjs" codeFormat="commonjs"
|
|
184
|
-
const { Locales } = require("intlayer");
|
|
185
|
-
const { syncJSON } = require("@intlayer/sync-json-plugin");
|
|
186
|
-
|
|
187
|
-
/** @type {import('intlayer').IntlayerConfig} */
|
|
188
|
-
const config = {
|
|
189
|
-
internationalization: {
|
|
190
|
-
locales: [
|
|
191
|
-
Locales.ENGLISH,
|
|
192
|
-
Locales.FRENCH,
|
|
193
|
-
Locales.SPANISH,
|
|
194
|
-
// Add your other locales
|
|
195
|
-
],
|
|
196
|
-
defaultLocale: Locales.ENGLISH,
|
|
197
|
-
},
|
|
198
|
-
plugins: [
|
|
199
|
-
syncJSON({
|
|
200
|
-
source: ({ key, locale }) => `./intl/messages/${locale}/${key}.json`,
|
|
201
|
-
}),
|
|
202
|
-
],
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
module.exports = config;
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
> The `syncJSON` plugin tells Intlayer to export your content declarations to next-intl compatible JSON files. This allows you to write content using Intlayer's flexible format while consuming it through next-intl.
|
|
209
|
-
|
|
210
|
-
### Step 3: Integrate Intlayer in Your Next.js Configuration
|
|
211
|
-
|
|
212
|
-
If you plan to use additional Intlayer features beyond just generating next-intl messages, you can integrate Intlayer into your Next.js configuration:
|
|
213
|
-
|
|
214
|
-
```typescript fileName="next.config.ts" codeFormat="typescript"
|
|
215
|
-
import type { NextConfig } from "next";
|
|
216
|
-
import { withIntlayer } from "next-intlayer/server";
|
|
217
|
-
|
|
218
|
-
const nextConfig: NextConfig = {
|
|
219
|
-
/* your config options here */
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
export default withIntlayer(nextConfig);
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
```javascript fileName="next.config.mjs" codeFormat="esm"
|
|
226
|
-
import { withIntlayer } from "next-intlayer/server";
|
|
227
|
-
|
|
228
|
-
/** @type {import('next').NextConfig} */
|
|
229
|
-
const nextConfig = {
|
|
230
|
-
/* your config options here */
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
export default withIntlayer(nextConfig);
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
```javascript fileName="next.config.cjs" codeFormat="commonjs"
|
|
237
|
-
const { withIntlayer } = require("next-intlayer/server");
|
|
238
|
-
|
|
239
|
-
/** @type {import('next').NextConfig} */
|
|
240
|
-
const nextConfig = {
|
|
241
|
-
/* your config options here */
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
module.exports = withIntlayer(nextConfig);
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
> **Note**: This step is optional if you're only using Intlayer to generate next-intl messages. If you want to use Intlayer's hooks and components directly in your application, this integration is required.
|
|
248
|
-
|
|
249
|
-
### Step 4: Configure next-intl Middleware
|
|
250
|
-
|
|
251
|
-
Set up next-intl's middleware for automatic locale detection and routing:
|
|
252
|
-
|
|
253
|
-
```typescript fileName="src/middleware.ts" codeFormat="typescript"
|
|
254
|
-
import createMiddleware from "next-intl/middleware";
|
|
255
|
-
|
|
256
|
-
export default createMiddleware({
|
|
257
|
-
// List of all supported locales
|
|
258
|
-
locales: ["en", "fr", "es"],
|
|
259
|
-
|
|
260
|
-
// Default locale when no preferred locale can be detected
|
|
261
|
-
defaultLocale: "en",
|
|
262
|
-
|
|
263
|
-
// Whether to redirect to the default locale
|
|
264
|
-
localePrefix: "as-needed",
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
export const config = {
|
|
268
|
-
// Matcher configuration to exclude specific paths
|
|
269
|
-
matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
|
|
270
|
-
};
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
```javascript fileName="src/middleware.mjs" codeFormat="esm"
|
|
274
|
-
import createMiddleware from "next-intl/middleware";
|
|
275
|
-
|
|
276
|
-
export default createMiddleware({
|
|
277
|
-
locales: ["en", "fr", "es"],
|
|
278
|
-
defaultLocale: "en",
|
|
279
|
-
localePrefix: "as-needed",
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
export const config = {
|
|
283
|
-
matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
|
|
284
|
-
};
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
```javascript fileName="src/middleware.cjs" codeFormat="commonjs"
|
|
288
|
-
const createMiddleware = require("next-intl/middleware");
|
|
289
|
-
|
|
290
|
-
module.exports = createMiddleware({
|
|
291
|
-
locales: ["en", "fr", "es"],
|
|
292
|
-
defaultLocale: "en",
|
|
293
|
-
localePrefix: "as-needed",
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
module.exports.config = {
|
|
297
|
-
matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
|
|
298
|
-
};
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
> This middleware handles automatic locale detection from the user's browser settings and manages locale-based routing. For more details, see the [next-intl middleware documentation](https://next-intl-docs.vercel.app/docs/routing/middleware).
|
|
302
|
-
|
|
303
|
-
### Step 5: Define Dynamic Locale Routes
|
|
304
|
-
|
|
305
|
-
Set up your application structure to support dynamic locale routing. For the App Router (Next.js 13+):
|
|
306
|
-
|
|
307
|
-
Remove everything from your root `RootLayout` and replace it with:
|
|
308
|
-
|
|
309
|
-
```tsx fileName="src/app/layout.tsx" codeFormat="typescript"
|
|
310
|
-
import type { PropsWithChildren, FC } from "react";
|
|
311
|
-
import "./globals.css";
|
|
312
|
-
|
|
313
|
-
const RootLayout: FC<PropsWithChildren> = ({ children }) => children;
|
|
314
|
-
|
|
315
|
-
export default RootLayout;
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
```jsx fileName="src/app/layout.mjx" codeFormat="esm"
|
|
319
|
-
import "./globals.css";
|
|
320
|
-
|
|
321
|
-
const RootLayout = ({ children }) => children;
|
|
322
|
-
|
|
323
|
-
export default RootLayout;
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
```jsx fileName="src/app/layout.csx" codeFormat="commonjs"
|
|
327
|
-
require("./globals.css");
|
|
328
|
-
|
|
329
|
-
const RootLayout = ({ children }) => children;
|
|
330
|
-
|
|
331
|
-
module.exports = RootLayout;
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
Then create a locale-specific layout in `[locale]` directory:
|
|
335
|
-
|
|
336
|
-
```tsx fileName="src/app/[locale]/layout.tsx" codeFormat="typescript"
|
|
337
|
-
import { NextIntlClientProvider } from "next-intl";
|
|
338
|
-
import { getMessages } from "next-intl/server";
|
|
339
|
-
import { notFound } from "next/navigation";
|
|
340
|
-
import { routing } from "@/i18n/routing";
|
|
341
|
-
import type { ReactNode } from "react";
|
|
342
|
-
|
|
343
|
-
export default async function LocaleLayout({
|
|
344
|
-
children,
|
|
345
|
-
params: { locale },
|
|
346
|
-
}: {
|
|
347
|
-
children: ReactNode;
|
|
348
|
-
params: { locale: string };
|
|
349
|
-
}) {
|
|
350
|
-
// Ensure the incoming locale is valid
|
|
351
|
-
if (!routing.locales.includes(locale as any)) {
|
|
352
|
-
notFound();
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Providing all messages to the client side is the easiest way to get started
|
|
356
|
-
const messages = await getMessages();
|
|
357
|
-
|
|
358
|
-
return (
|
|
359
|
-
<html lang={locale}>
|
|
360
|
-
<body>
|
|
361
|
-
<NextIntlClientProvider messages={messages}>
|
|
362
|
-
{children}
|
|
363
|
-
</NextIntlClientProvider>
|
|
364
|
-
</body>
|
|
365
|
-
</html>
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
```jsx fileName="src/app/[locale]/layout.mjx" codeFormat="esm"
|
|
371
|
-
import { NextIntlClientProvider } from "next-intl";
|
|
372
|
-
import { getMessages } from "next-intl/server";
|
|
373
|
-
import { notFound } from "next/navigation";
|
|
374
|
-
import { routing } from "@/i18n/routing";
|
|
375
|
-
|
|
376
|
-
export default async function LocaleLayout({ children, params: { locale } }) {
|
|
377
|
-
if (!routing.locales.includes(locale)) {
|
|
378
|
-
notFound();
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const messages = await getMessages();
|
|
382
|
-
|
|
383
|
-
return (
|
|
384
|
-
<html lang={locale}>
|
|
385
|
-
<body>
|
|
386
|
-
<NextIntlClientProvider messages={messages}>
|
|
387
|
-
{children}
|
|
388
|
-
</NextIntlClientProvider>
|
|
389
|
-
</body>
|
|
390
|
-
</html>
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
```jsx fileName="src/app/[locale]/layout.csx" codeFormat="commonjs"
|
|
396
|
-
const { NextIntlClientProvider } = require("next-intl");
|
|
397
|
-
const { getMessages } = require("next-intl/server");
|
|
398
|
-
const { notFound } = require("next/navigation");
|
|
399
|
-
const { routing } = require("@/i18n/routing");
|
|
400
|
-
|
|
401
|
-
async function LocaleLayout({ children, params: { locale } }) {
|
|
402
|
-
if (!routing.locales.includes(locale)) {
|
|
403
|
-
notFound();
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const messages = await getMessages();
|
|
407
|
-
|
|
408
|
-
return (
|
|
409
|
-
<html lang={locale}>
|
|
410
|
-
<body>
|
|
411
|
-
<NextIntlClientProvider messages={messages}>
|
|
412
|
-
{children}
|
|
413
|
-
</NextIntlClientProvider>
|
|
414
|
-
</body>
|
|
415
|
-
</html>
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
module.exports = LocaleLayout;
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
Create the routing configuration file:
|
|
423
|
-
|
|
424
|
-
```typescript fileName="src/i18n/routing.ts" codeFormat="typescript"
|
|
425
|
-
import { defineRouting } from "next-intl/routing";
|
|
426
|
-
import { createSharedPathnamesNavigation } from "next-intl/navigation";
|
|
427
|
-
|
|
428
|
-
export const routing = defineRouting({
|
|
429
|
-
locales: ["en", "fr", "es"],
|
|
430
|
-
defaultLocale: "en",
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
export const { Link, redirect, usePathname, useRouter } =
|
|
434
|
-
createSharedPathnamesNavigation(routing);
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
```javascript fileName="src/i18n/routing.mjs" codeFormat="esm"
|
|
438
|
-
import { defineRouting } from "next-intl/routing";
|
|
439
|
-
import { createSharedPathnamesNavigation } from "next-intl/navigation";
|
|
440
|
-
|
|
441
|
-
export const routing = defineRouting({
|
|
442
|
-
locales: ["en", "fr", "es"],
|
|
443
|
-
defaultLocale: "en",
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
export const { Link, redirect, usePathname, useRouter } =
|
|
447
|
-
createSharedPathnamesNavigation(routing);
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
```javascript fileName="src/i18n/routing.cjs" codeFormat="commonjs"
|
|
451
|
-
const { defineRouting } = require("next-intl/routing");
|
|
452
|
-
const { createSharedPathnamesNavigation } = require("next-intl/navigation");
|
|
453
|
-
|
|
454
|
-
const routing = defineRouting({
|
|
455
|
-
locales: ["en", "fr", "es"],
|
|
456
|
-
defaultLocale: "en",
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
const { Link, redirect, usePathname, useRouter } =
|
|
460
|
-
createSharedPathnamesNavigation(routing);
|
|
461
|
-
|
|
462
|
-
module.exports = { routing, Link, redirect, usePathname, useRouter };
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
Create the next-intl configuration file:
|
|
466
|
-
|
|
467
|
-
```typescript fileName="src/i18n/request.ts" codeFormat="typescript"
|
|
468
|
-
import { getRequestConfig } from "next-intl/server";
|
|
469
|
-
import { routing } from "./routing";
|
|
470
|
-
|
|
471
|
-
export default getRequestConfig(async ({ requestLocale }) => {
|
|
472
|
-
// This typically corresponds to the `[locale]` segment
|
|
473
|
-
let locale = await requestLocale;
|
|
474
|
-
|
|
475
|
-
// Ensure a valid locale is used
|
|
476
|
-
if (!locale || !routing.locales.includes(locale as any)) {
|
|
477
|
-
locale = routing.defaultLocale;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
return {
|
|
481
|
-
locale,
|
|
482
|
-
messages: (await import(`../../intl/messages/${locale}.json`)).default,
|
|
483
|
-
};
|
|
484
|
-
});
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
```javascript fileName="src/i18n/request.mjs" codeFormat="esm"
|
|
488
|
-
import { getRequestConfig } from "next-intl/server";
|
|
489
|
-
import { routing } from "./routing";
|
|
490
|
-
|
|
491
|
-
export default getRequestConfig(async ({ requestLocale }) => {
|
|
492
|
-
let locale = await requestLocale;
|
|
493
|
-
|
|
494
|
-
if (!locale || !routing.locales.includes(locale)) {
|
|
495
|
-
locale = routing.defaultLocale;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
return {
|
|
499
|
-
locale,
|
|
500
|
-
messages: (await import(`../../intl/messages/${locale}.json`)).default,
|
|
501
|
-
};
|
|
502
|
-
});
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
```javascript fileName="src/i18n/request.cjs" codeFormat="commonjs"
|
|
506
|
-
const { getRequestConfig } = require("next-intl/server");
|
|
507
|
-
const { routing } = require("./routing");
|
|
508
|
-
|
|
509
|
-
module.exports = getRequestConfig(async ({ requestLocale }) => {
|
|
510
|
-
let locale = await requestLocale;
|
|
511
|
-
|
|
512
|
-
if (!locale || !routing.locales.includes(locale)) {
|
|
513
|
-
locale = routing.defaultLocale;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
return {
|
|
517
|
-
locale,
|
|
518
|
-
messages: (await import(`../../intl/messages/${locale}.json`)).default,
|
|
519
|
-
};
|
|
520
|
-
});
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
Update your Next.js config to use the next-intl plugin:
|
|
524
|
-
|
|
525
|
-
```typescript fileName="next.config.ts" codeFormat="typescript"
|
|
526
|
-
import type { NextConfig } from "next";
|
|
527
|
-
import createNextIntlPlugin from "next-intl/plugin";
|
|
528
|
-
|
|
529
|
-
const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");
|
|
530
|
-
|
|
531
|
-
const nextConfig: NextConfig = {
|
|
532
|
-
/* your config options here */
|
|
533
|
-
};
|
|
534
|
-
|
|
535
|
-
export default withNextIntl(nextConfig);
|
|
536
|
-
```
|
|
537
|
-
|
|
538
|
-
```javascript fileName="next.config.mjs" codeFormat="esm"
|
|
539
|
-
import createNextIntlPlugin from "next-intl/plugin";
|
|
540
|
-
|
|
541
|
-
const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");
|
|
542
|
-
|
|
543
|
-
/** @type {import('next').NextConfig} */
|
|
544
|
-
const nextConfig = {
|
|
545
|
-
/* your config options here */
|
|
546
|
-
};
|
|
547
|
-
|
|
548
|
-
export default withNextIntl(nextConfig);
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
```javascript fileName="next.config.cjs" codeFormat="commonjs"
|
|
552
|
-
const createNextIntlPlugin = require("next-intl/plugin");
|
|
553
|
-
|
|
554
|
-
const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");
|
|
555
|
-
|
|
556
|
-
/** @type {import('next').NextConfig} */
|
|
557
|
-
const nextConfig = {
|
|
558
|
-
/* your config options here */
|
|
559
|
-
};
|
|
560
|
-
|
|
561
|
-
module.exports = withNextIntl(nextConfig);
|
|
562
|
-
```
|
|
563
|
-
|
|
564
|
-
### Step 6: Declare Your Content
|
|
565
|
-
|
|
566
|
-
Create content declarations using Intlayer's flexible format:
|
|
567
|
-
|
|
568
|
-
```typescript fileName="src/app/[locale]/page.content.ts" contentDeclarationFormat="typescript"
|
|
569
|
-
import { t, type Dictionary } from "intlayer";
|
|
570
|
-
|
|
571
|
-
const pageContent = {
|
|
572
|
-
key: "page",
|
|
573
|
-
content: {
|
|
574
|
-
getStarted: {
|
|
575
|
-
main: t({
|
|
576
|
-
en: "Get started by editing",
|
|
577
|
-
fr: "Commencez par éditer",
|
|
578
|
-
es: "Comience por editar",
|
|
579
|
-
}),
|
|
580
|
-
pageLink: "src/app/page.tsx",
|
|
581
|
-
},
|
|
582
|
-
documentationLink: {
|
|
583
|
-
text: t({
|
|
584
|
-
en: "Read our documentation",
|
|
585
|
-
fr: "Lisez notre documentation",
|
|
586
|
-
es: "Lea nuestra documentación",
|
|
587
|
-
}),
|
|
588
|
-
href: "https://intlayer.org",
|
|
589
|
-
},
|
|
590
|
-
},
|
|
591
|
-
} satisfies Dictionary;
|
|
592
|
-
|
|
593
|
-
export default pageContent;
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
```javascript fileName="src/app/[locale]/page.content.mjs" contentDeclarationFormat="esm"
|
|
597
|
-
import { t } from "intlayer";
|
|
598
|
-
|
|
599
|
-
/** @type {import('intlayer').Dictionary} */
|
|
600
|
-
const pageContent = {
|
|
601
|
-
key: "page",
|
|
602
|
-
content: {
|
|
603
|
-
getStarted: {
|
|
604
|
-
main: t({
|
|
605
|
-
en: "Get started by editing",
|
|
606
|
-
fr: "Commencez par éditer",
|
|
607
|
-
es: "Comience por editar",
|
|
608
|
-
}),
|
|
609
|
-
pageLink: "src/app/page.tsx",
|
|
610
|
-
},
|
|
611
|
-
documentationLink: {
|
|
612
|
-
text: t({
|
|
613
|
-
en: "Read our documentation",
|
|
614
|
-
fr: "Lisez notre documentation",
|
|
615
|
-
es: "Lea nuestra documentación",
|
|
616
|
-
}),
|
|
617
|
-
href: "https://intlayer.org",
|
|
618
|
-
},
|
|
619
|
-
},
|
|
620
|
-
};
|
|
621
|
-
|
|
622
|
-
export default pageContent;
|
|
623
|
-
```
|
|
624
|
-
|
|
625
|
-
```javascript fileName="src/app/[locale]/page.content.cjs" contentDeclarationFormat="commonjs"
|
|
626
|
-
const { t } = require("intlayer");
|
|
87
|
+
The `syncJSON` plugin will automatically wrap the JSON. It will read and write the JSON files without changing the content architecture.
|
|
627
88
|
|
|
628
|
-
|
|
629
|
-
const pageContent = {
|
|
630
|
-
key: "page",
|
|
631
|
-
content: {
|
|
632
|
-
getStarted: {
|
|
633
|
-
main: t({
|
|
634
|
-
en: "Get started by editing",
|
|
635
|
-
fr: "Commencez par éditer",
|
|
636
|
-
es: "Comience por editar",
|
|
637
|
-
}),
|
|
638
|
-
pageLink: "src/app/page.tsx",
|
|
639
|
-
},
|
|
640
|
-
documentationLink: {
|
|
641
|
-
text: t({
|
|
642
|
-
en: "Read our documentation",
|
|
643
|
-
fr: "Lisez notre documentation",
|
|
644
|
-
es: "Lea nuestra documentación",
|
|
645
|
-
}),
|
|
646
|
-
href: "https://intlayer.org",
|
|
647
|
-
},
|
|
648
|
-
},
|
|
649
|
-
};
|
|
650
|
-
|
|
651
|
-
module.exports = pageContent;
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
```json fileName="src/app/[locale]/page.content.json" contentDeclarationFormat="json"
|
|
655
|
-
{
|
|
656
|
-
"$schema": "https://intlayer.org/schema.json",
|
|
657
|
-
"key": "page",
|
|
658
|
-
"content": {
|
|
659
|
-
"getStarted": {
|
|
660
|
-
"main": {
|
|
661
|
-
"nodeType": "translation",
|
|
662
|
-
"translation": {
|
|
663
|
-
"en": "Get started by editing",
|
|
664
|
-
"fr": "Commencez par éditer",
|
|
665
|
-
"es": "Comience por editar"
|
|
666
|
-
}
|
|
667
|
-
},
|
|
668
|
-
"pageLink": "src/app/page.tsx"
|
|
669
|
-
},
|
|
670
|
-
"documentationLink": {
|
|
671
|
-
"text": {
|
|
672
|
-
"nodeType": "translation",
|
|
673
|
-
"translation": {
|
|
674
|
-
"en": "Read our documentation",
|
|
675
|
-
"fr": "Lisez notre documentation",
|
|
676
|
-
"es": "Lea nuestra documentación"
|
|
677
|
-
}
|
|
678
|
-
},
|
|
679
|
-
"href": "https://intlayer.org"
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
```
|
|
89
|
+
If you want to make coexist that JSON with intlayer content declaration files (`.content` files), Intlayer will proceed this way:
|
|
684
90
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
> For more details about content declaration, refer to the [content declaration documentation](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/content_file.md).
|
|
688
|
-
|
|
689
|
-
### Step 7: Build and Generate next-intl Messages
|
|
690
|
-
|
|
691
|
-
Build your content declarations to generate next-intl compatible message files:
|
|
692
|
-
|
|
693
|
-
```bash packageManager="npm"
|
|
694
|
-
npx intlayer build
|
|
695
|
-
```
|
|
696
|
-
|
|
697
|
-
```bash packageManager="yarn"
|
|
698
|
-
yarn intlayer build
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
```bash packageManager="pnpm"
|
|
702
|
-
pnpm intlayer build
|
|
703
|
-
```
|
|
704
|
-
|
|
705
|
-
This will generate resources in the `./intl/messages` directory. The expected output:
|
|
706
|
-
|
|
707
|
-
```bash
|
|
708
|
-
.
|
|
709
|
-
└── intl
|
|
710
|
-
└── messages
|
|
711
|
-
├── en.json
|
|
712
|
-
├── fr.json
|
|
713
|
-
└── es.json
|
|
714
|
-
```
|
|
91
|
+
1. load both JSON and content declaration files and transform them into a intlayer dictionary.
|
|
92
|
+
2. if there is conflicts between the JSON and the content declaration files, Intlayer will process to the merge of that all dictionaries. Depending of the priority of the plugins, and the one of the content declaration file (all are configurable).
|
|
715
93
|
|
|
716
|
-
|
|
94
|
+
If changes are made using the CLI to translate the JSON, or using the CMS, Intlayer will update the JSON file with the new translations.
|
|
717
95
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
Access your translations in your components using next-intl's hooks:
|
|
721
|
-
|
|
722
|
-
```tsx fileName="src/app/[locale]/page.tsx" codeFormat="typescript"
|
|
723
|
-
import type { FC } from "react";
|
|
724
|
-
import { useTranslations } from "next-intl";
|
|
725
|
-
|
|
726
|
-
const Page: FC = () => {
|
|
727
|
-
const t = useTranslations("page");
|
|
728
|
-
|
|
729
|
-
return (
|
|
730
|
-
<div>
|
|
731
|
-
<h1>{t("getStarted.main")}</h1>
|
|
732
|
-
<code>{t("getStarted.pageLink")}</code>
|
|
733
|
-
|
|
734
|
-
<a href={t("documentationLink.href")}>{t("documentationLink.text")}</a>
|
|
735
|
-
</div>
|
|
736
|
-
);
|
|
737
|
-
};
|
|
738
|
-
|
|
739
|
-
export default Page;
|
|
740
|
-
```
|
|
741
|
-
|
|
742
|
-
```jsx fileName="src/app/[locale]/page.mjx" codeFormat="esm"
|
|
743
|
-
import { useTranslations } from "next-intl";
|
|
744
|
-
|
|
745
|
-
export default function Page() {
|
|
746
|
-
const t = useTranslations("page");
|
|
747
|
-
|
|
748
|
-
return (
|
|
749
|
-
<div>
|
|
750
|
-
<h1>{t("getStarted.main")}</h1>
|
|
751
|
-
<code>{t("getStarted.pageLink")}</code>
|
|
752
|
-
|
|
753
|
-
<a href={t("documentationLink.href")}>{t("documentationLink.text")}</a>
|
|
754
|
-
</div>
|
|
755
|
-
);
|
|
756
|
-
}
|
|
757
|
-
```
|
|
758
|
-
|
|
759
|
-
```jsx fileName="src/app/[locale]/page.csx" codeFormat="commonjs"
|
|
760
|
-
const { useTranslations } = require("next-intl");
|
|
761
|
-
|
|
762
|
-
function Page() {
|
|
763
|
-
const t = useTranslations("page");
|
|
764
|
-
|
|
765
|
-
return (
|
|
766
|
-
<div>
|
|
767
|
-
<h1>{t("getStarted.main")}</h1>
|
|
768
|
-
<code>{t("getStarted.pageLink")}</code>
|
|
769
|
-
|
|
770
|
-
<a href={t("documentationLink.href")}>{t("documentationLink.text")}</a>
|
|
771
|
-
</div>
|
|
772
|
-
);
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
module.exports = Page;
|
|
776
|
-
```
|
|
777
|
-
|
|
778
|
-
> The first parameter of `useTranslations` corresponds to the `key` in your Intlayer content declaration. The nested properties are accessed using dot notation.
|
|
779
|
-
|
|
780
|
-
### (Optional) Step 9: Internationalization of Your Metadata
|
|
781
|
-
|
|
782
|
-
For metadata like page titles and descriptions:
|
|
783
|
-
|
|
784
|
-
```typescript fileName="src/app/[locale]/metadata.content.ts" contentDeclarationFormat="typescript"
|
|
785
|
-
import { t, type Dictionary } from "intlayer";
|
|
786
|
-
import type { Metadata } from "next";
|
|
787
|
-
|
|
788
|
-
const metadataContent = {
|
|
789
|
-
key: "metadata",
|
|
790
|
-
content: {
|
|
791
|
-
title: t({
|
|
792
|
-
en: "My Application",
|
|
793
|
-
fr: "Mon Application",
|
|
794
|
-
es: "Mi Aplicación",
|
|
795
|
-
}),
|
|
796
|
-
description: t({
|
|
797
|
-
en: "Welcome to my Next.js application",
|
|
798
|
-
fr: "Bienvenue dans mon application Next.js",
|
|
799
|
-
es: "Bienvenido a mi aplicación Next.js",
|
|
800
|
-
}),
|
|
801
|
-
},
|
|
802
|
-
} satisfies Dictionary<Metadata>;
|
|
803
|
-
|
|
804
|
-
export default metadataContent;
|
|
805
|
-
```
|
|
806
|
-
|
|
807
|
-
```javascript fileName="src/app/[locale]/metadata.content.mjs" contentDeclarationFormat="esm"
|
|
808
|
-
import { t } from "intlayer";
|
|
809
|
-
|
|
810
|
-
/** @type {import('intlayer').Dictionary<import('next').Metadata>} */
|
|
811
|
-
const metadataContent = {
|
|
812
|
-
key: "metadata",
|
|
813
|
-
content: {
|
|
814
|
-
title: t({
|
|
815
|
-
en: "My Application",
|
|
816
|
-
fr: "Mon Application",
|
|
817
|
-
es: "Mi Aplicación",
|
|
818
|
-
}),
|
|
819
|
-
description: t({
|
|
820
|
-
en: "Welcome to my Next.js application",
|
|
821
|
-
fr: "Bienvenue dans mon application Next.js",
|
|
822
|
-
es: "Bienvenido a mi aplicación Next.js",
|
|
823
|
-
}),
|
|
824
|
-
},
|
|
825
|
-
};
|
|
826
|
-
|
|
827
|
-
export default metadataContent;
|
|
828
|
-
```
|
|
829
|
-
|
|
830
|
-
```javascript fileName="src/app/[locale]/metadata.content.cjs" contentDeclarationFormat="commonjs"
|
|
831
|
-
const { t } = require("intlayer");
|
|
832
|
-
|
|
833
|
-
/** @type {import('intlayer').Dictionary<import('next').Metadata>} */
|
|
834
|
-
const metadataContent = {
|
|
835
|
-
key: "metadata",
|
|
836
|
-
content: {
|
|
837
|
-
title: t({
|
|
838
|
-
en: "My Application",
|
|
839
|
-
fr: "Mon Application",
|
|
840
|
-
es: "Mi Aplicación",
|
|
841
|
-
}),
|
|
842
|
-
description: t({
|
|
843
|
-
en: "Welcome to my Next.js application",
|
|
844
|
-
fr: "Bienvenue dans mon application Next.js",
|
|
845
|
-
es: "Bienvenido a mi aplicación Next.js",
|
|
846
|
-
}),
|
|
847
|
-
},
|
|
848
|
-
};
|
|
849
|
-
|
|
850
|
-
module.exports = metadataContent;
|
|
851
|
-
```
|
|
852
|
-
|
|
853
|
-
Use in your layout or page:
|
|
854
|
-
|
|
855
|
-
```tsx fileName="src/app/[locale]/layout.tsx" codeFormat="typescript"
|
|
856
|
-
import { getTranslations } from "next-intl/server";
|
|
857
|
-
import type { Metadata } from "next";
|
|
858
|
-
|
|
859
|
-
export async function generateMetadata({
|
|
860
|
-
params: { locale },
|
|
861
|
-
}: {
|
|
862
|
-
params: { locale: string };
|
|
863
|
-
}): Promise<Metadata> {
|
|
864
|
-
const t = await getTranslations({ locale, namespace: "metadata" });
|
|
865
|
-
|
|
866
|
-
return {
|
|
867
|
-
title: t("title"),
|
|
868
|
-
description: t("description"),
|
|
869
|
-
};
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
// ... rest of your layout
|
|
873
|
-
```
|
|
874
|
-
|
|
875
|
-
### (Optional) Step 10: Internationalization of sitemap.xml and robots.txt
|
|
876
|
-
|
|
877
|
-
For SEO optimization, internationalize your sitemap and robots files:
|
|
878
|
-
|
|
879
|
-
```typescript fileName="src/app/sitemap.ts" codeFormat="typescript"
|
|
880
|
-
import { MetadataRoute } from "next";
|
|
881
|
-
import { routing } from "@/i18n/routing";
|
|
882
|
-
|
|
883
|
-
export default function sitemap(): MetadataRoute.Sitemap {
|
|
884
|
-
return [
|
|
885
|
-
{
|
|
886
|
-
url: "https://example.com",
|
|
887
|
-
lastModified: new Date(),
|
|
888
|
-
alternates: {
|
|
889
|
-
languages: Object.fromEntries(
|
|
890
|
-
routing.locales.map((locale) => [
|
|
891
|
-
locale,
|
|
892
|
-
`https://example.com/${locale}`,
|
|
893
|
-
])
|
|
894
|
-
),
|
|
895
|
-
},
|
|
896
|
-
},
|
|
897
|
-
];
|
|
898
|
-
}
|
|
899
|
-
```
|
|
900
|
-
|
|
901
|
-
```javascript fileName="src/app/sitemap.mjs" codeFormat="esm"
|
|
902
|
-
import { routing } from "@/i18n/routing";
|
|
903
|
-
|
|
904
|
-
export default function sitemap() {
|
|
905
|
-
return [
|
|
906
|
-
{
|
|
907
|
-
url: "https://example.com",
|
|
908
|
-
lastModified: new Date(),
|
|
909
|
-
alternates: {
|
|
910
|
-
languages: Object.fromEntries(
|
|
911
|
-
routing.locales.map((locale) => [
|
|
912
|
-
locale,
|
|
913
|
-
`https://example.com/${locale}`,
|
|
914
|
-
])
|
|
915
|
-
),
|
|
916
|
-
},
|
|
917
|
-
},
|
|
918
|
-
];
|
|
919
|
-
}
|
|
920
|
-
```
|
|
921
|
-
|
|
922
|
-
```javascript fileName="src/app/sitemap.cjs" codeFormat="commonjs"
|
|
923
|
-
const { routing } = require("@/i18n/routing");
|
|
924
|
-
|
|
925
|
-
function sitemap() {
|
|
926
|
-
return [
|
|
927
|
-
{
|
|
928
|
-
url: "https://example.com",
|
|
929
|
-
lastModified: new Date(),
|
|
930
|
-
alternates: {
|
|
931
|
-
languages: Object.fromEntries(
|
|
932
|
-
routing.locales.map((locale) => [
|
|
933
|
-
locale,
|
|
934
|
-
`https://example.com/${locale}`,
|
|
935
|
-
])
|
|
936
|
-
),
|
|
937
|
-
},
|
|
938
|
-
},
|
|
939
|
-
];
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
module.exports = sitemap;
|
|
943
|
-
```
|
|
944
|
-
|
|
945
|
-
### (Optional) Step 11: Change the Language of Your Content
|
|
946
|
-
|
|
947
|
-
Create a language switcher component:
|
|
948
|
-
|
|
949
|
-
```tsx fileName="src/components/LanguageSwitcher.tsx" codeFormat="typescript"
|
|
950
|
-
"use client";
|
|
951
|
-
|
|
952
|
-
import { useLocale } from "next-intl";
|
|
953
|
-
import { useRouter, usePathname } from "@/i18n/routing";
|
|
954
|
-
import { routing } from "@/i18n/routing";
|
|
955
|
-
import type { FC } from "react";
|
|
956
|
-
|
|
957
|
-
export const LanguageSwitcher: FC = () => {
|
|
958
|
-
const locale = useLocale();
|
|
959
|
-
const router = useRouter();
|
|
960
|
-
const pathname = usePathname();
|
|
961
|
-
|
|
962
|
-
const handleLocaleChange = (newLocale: string) => {
|
|
963
|
-
router.replace(pathname, { locale: newLocale });
|
|
964
|
-
};
|
|
965
|
-
|
|
966
|
-
return (
|
|
967
|
-
<select value={locale} onChange={(e) => handleLocaleChange(e.target.value)}>
|
|
968
|
-
{routing.locales.map((loc) => (
|
|
969
|
-
<option key={loc} value={loc}>
|
|
970
|
-
{loc.toUpperCase()}
|
|
971
|
-
</option>
|
|
972
|
-
))}
|
|
973
|
-
</select>
|
|
974
|
-
);
|
|
975
|
-
};
|
|
976
|
-
```
|
|
977
|
-
|
|
978
|
-
```jsx fileName="src/components/LanguageSwitcher.mjx" codeFormat="esm"
|
|
979
|
-
"use client";
|
|
980
|
-
|
|
981
|
-
import { useLocale } from "next-intl";
|
|
982
|
-
import { useRouter, usePathname } from "@/i18n/routing";
|
|
983
|
-
import { routing } from "@/i18n/routing";
|
|
984
|
-
|
|
985
|
-
export const LanguageSwitcher = () => {
|
|
986
|
-
const locale = useLocale();
|
|
987
|
-
const router = useRouter();
|
|
988
|
-
const pathname = usePathname();
|
|
989
|
-
|
|
990
|
-
const handleLocaleChange = (newLocale) => {
|
|
991
|
-
router.replace(pathname, { locale: newLocale });
|
|
992
|
-
};
|
|
993
|
-
|
|
994
|
-
return (
|
|
995
|
-
<select value={locale} onChange={(e) => handleLocaleChange(e.target.value)}>
|
|
996
|
-
{routing.locales.map((loc) => (
|
|
997
|
-
<option key={loc} value={loc}>
|
|
998
|
-
{loc.toUpperCase()}
|
|
999
|
-
</option>
|
|
1000
|
-
))}
|
|
1001
|
-
</select>
|
|
1002
|
-
);
|
|
1003
|
-
};
|
|
1004
|
-
```
|
|
1005
|
-
|
|
1006
|
-
```jsx fileName="src/components/LanguageSwitcher.csx" codeFormat="commonjs"
|
|
1007
|
-
"use client";
|
|
1008
|
-
|
|
1009
|
-
const { useLocale } = require("next-intl");
|
|
1010
|
-
const { useRouter, usePathname } = require("@/i18n/routing");
|
|
1011
|
-
const { routing } = require("@/i18n/routing");
|
|
1012
|
-
|
|
1013
|
-
const LanguageSwitcher = () => {
|
|
1014
|
-
const locale = useLocale();
|
|
1015
|
-
const router = useRouter();
|
|
1016
|
-
const pathname = usePathname();
|
|
1017
|
-
|
|
1018
|
-
const handleLocaleChange = (newLocale) => {
|
|
1019
|
-
router.replace(pathname, { locale: newLocale });
|
|
1020
|
-
};
|
|
1021
|
-
|
|
1022
|
-
return (
|
|
1023
|
-
<select value={locale} onChange={(e) => handleLocaleChange(e.target.value)}>
|
|
1024
|
-
{routing.locales.map((loc) => (
|
|
1025
|
-
<option key={loc} value={loc}>
|
|
1026
|
-
{loc.toUpperCase()}
|
|
1027
|
-
</option>
|
|
1028
|
-
))}
|
|
1029
|
-
</select>
|
|
1030
|
-
);
|
|
1031
|
-
};
|
|
1032
|
-
|
|
1033
|
-
module.exports = { LanguageSwitcher };
|
|
1034
|
-
```
|
|
1035
|
-
|
|
1036
|
-
### (Optional) Step 12: Creating a Localized Link Component
|
|
1037
|
-
|
|
1038
|
-
For locale-aware navigation, use the `Link` component from your routing configuration:
|
|
1039
|
-
|
|
1040
|
-
```tsx fileName="src/components/LocalizedLink.tsx" codeFormat="typescript"
|
|
1041
|
-
import { Link } from "@/i18n/routing";
|
|
1042
|
-
import type { ComponentProps, FC } from "react";
|
|
1043
|
-
|
|
1044
|
-
export const LocalizedLink: FC<ComponentProps<typeof Link>> = (props) => {
|
|
1045
|
-
return <Link {...props} />;
|
|
1046
|
-
};
|
|
1047
|
-
```
|
|
1048
|
-
|
|
1049
|
-
```jsx fileName="src/components/LocalizedLink.mjx" codeFormat="esm"
|
|
1050
|
-
import { Link } from "@/i18n/routing";
|
|
1051
|
-
|
|
1052
|
-
export const LocalizedLink = (props) => {
|
|
1053
|
-
return <Link {...props} />;
|
|
1054
|
-
};
|
|
1055
|
-
```
|
|
1056
|
-
|
|
1057
|
-
```jsx fileName="src/components/LocalizedLink.csx" codeFormat="commonjs"
|
|
1058
|
-
const { Link } = require("@/i18n/routing");
|
|
1059
|
-
|
|
1060
|
-
const LocalizedLink = (props) => {
|
|
1061
|
-
return <Link {...props} />;
|
|
1062
|
-
};
|
|
1063
|
-
|
|
1064
|
-
module.exports = { LocalizedLink };
|
|
1065
|
-
```
|
|
1066
|
-
|
|
1067
|
-
Use it in your application:
|
|
1068
|
-
|
|
1069
|
-
```tsx
|
|
1070
|
-
import { LocalizedLink } from "@/components/LocalizedLink";
|
|
1071
|
-
|
|
1072
|
-
export default function Navigation() {
|
|
1073
|
-
return (
|
|
1074
|
-
<nav>
|
|
1075
|
-
<LocalizedLink href="/">Home</LocalizedLink>
|
|
1076
|
-
<LocalizedLink href="/about">About</LocalizedLink>
|
|
1077
|
-
<LocalizedLink href="/contact">Contact</LocalizedLink>
|
|
1078
|
-
</nav>
|
|
1079
|
-
);
|
|
1080
|
-
}
|
|
1081
|
-
```
|
|
1082
|
-
|
|
1083
|
-
---
|
|
1084
|
-
|
|
1085
|
-
## Updating or Adding New Translations
|
|
1086
|
-
|
|
1087
|
-
1. **Add or modify** content in any `*.content.*` file
|
|
1088
|
-
2. Run `intlayer build` to regenerate the JSON files
|
|
1089
|
-
3. Next.js (and next-intl) will automatically pick up the changes on the next rebuild or in development mode
|
|
1090
|
-
|
|
1091
|
-
---
|
|
1092
|
-
|
|
1093
|
-
## TypeScript Configuration
|
|
1094
|
-
|
|
1095
|
-
Intlayer provides **autogenerated type definitions** for your content. Ensure TypeScript picks them up:
|
|
1096
|
-
|
|
1097
|
-
```json5 fileName="tsconfig.json"
|
|
1098
|
-
{
|
|
1099
|
-
"compilerOptions": {
|
|
1100
|
-
// ... your other compiler options
|
|
1101
|
-
},
|
|
1102
|
-
"include": [
|
|
1103
|
-
"src",
|
|
1104
|
-
".intlayer/**/*.ts", // Include Intlayer auto-generated types
|
|
1105
|
-
],
|
|
1106
|
-
}
|
|
1107
|
-
```
|
|
1108
|
-
|
|
1109
|
-
This provides autocompletion and type checking for your translation keys.
|
|
1110
|
-
|
|
1111
|
-
---
|
|
96
|
+
To see more details about the `syncJSON` plugin, please refer to the [syncJSON plugin documentation](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/plugins/sync-json.md).
|
|
1112
97
|
|
|
1113
98
|
## Git Configuration
|
|
1114
99
|
|
|
@@ -1117,18 +102,12 @@ It's recommended to ignore auto-generated Intlayer files:
|
|
|
1117
102
|
```plaintext fileName=".gitignore"
|
|
1118
103
|
# Ignore files generated by Intlayer
|
|
1119
104
|
.intlayer
|
|
1120
|
-
intl
|
|
1121
105
|
```
|
|
1122
106
|
|
|
1123
107
|
These files can be regenerated during your build process and don't need to be committed to version control.
|
|
1124
108
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
## Further Resources and Learning
|
|
109
|
+
### VS Code Extension
|
|
1128
110
|
|
|
1129
|
-
|
|
1130
|
-
- **[next-intl Documentation](https://next-intl-docs.vercel.app)**: Official next-intl documentation
|
|
1131
|
-
- **[Intlayer Visual Editor](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_visual_editor.md)**: Manage translations through a visual interface
|
|
1132
|
-
- **[Intlayer CLI Documentation](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_cli.md)**: Command-line tools for managing translations
|
|
111
|
+
For improved developer experience, install the official **Intlayer VS Code Extension**:
|
|
1133
112
|
|
|
1134
|
-
|
|
113
|
+
[Install from the VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=intlayer.intlayer-vs-code-extension)
|