@intlayer/docs 7.0.3-canary.1 → 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 +14 -3
- package/dist/esm/generated/blog.entry.mjs.map +1 -1
- package/dist/esm/generated/docs.entry.mjs +14 -3
- package/dist/esm/generated/docs.entry.mjs.map +1 -1
- package/dist/esm/generated/frequentQuestions.entry.mjs +14 -3
- package/dist/esm/generated/frequentQuestions.entry.mjs.map +1 -1
- package/dist/esm/generated/legal.entry.mjs +14 -3
- 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 +17 -17
- package/src/generated/blog.entry.ts +27 -4
- package/src/generated/docs.entry.ts +27 -4
- package/src/generated/frequentQuestions.entry.ts +27 -4
- package/src/generated/legal.entry.ts +27 -4
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
createdAt: 2025-08-23
|
|
3
|
-
updatedAt: 2025-
|
|
3
|
+
updatedAt: 2025-10-29
|
|
4
4
|
title: Intlayer and next-i18next
|
|
5
|
-
description: Integrate Intlayer with next-i18next for a Next.js
|
|
5
|
+
description: Integrate Intlayer with next-i18next for a comprehensive Next.js internationalization solution
|
|
6
6
|
keywords:
|
|
7
7
|
- i18next
|
|
8
8
|
- next-i18next
|
|
@@ -15,106 +15,118 @@ keywords:
|
|
|
15
15
|
slugs:
|
|
16
16
|
- blog
|
|
17
17
|
- intlayer-with-next-i18next
|
|
18
|
+
history:
|
|
19
|
+
- version: 7.0.0
|
|
20
|
+
date: 2025-10-29
|
|
21
|
+
changes: Change to syncJSON plugin and comprehensive rewrite
|
|
18
22
|
---
|
|
19
23
|
|
|
20
24
|
# Next.js Internationalization (i18n) with next-i18next and Intlayer
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
## Table of Contents
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
<TOC/>
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
- Named `resource` in the case of `i18next`, the content declaration is a structured JSON object containing key-value pairs for translations in one or more languages. See [i18next documentation](https://www.i18next.com/translation-function/essentials) for more information.
|
|
28
|
-
- Named `content declaration file` in the case of `Intlayer`, the content declaration can be a JSON, JS, or TS file exporting the structured data. See [Intlayer documentation](https://intlayer.org/fr/doc/concept/content) for more information.
|
|
30
|
+
## What is next-i18next?
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
**next-i18next** is one of the most popular internationalization (i18n) frameworks for Next.js applications. Built on top of the powerful **i18next** ecosystem, it provides a comprehensive solution for managing translations, localization, and language switching in Next.js projects.
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
However, next-i18next comes with some challenges:
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
- **Complex configuration**: Setting up next-i18next requires multiple configuration files and careful setup of server-side and client-side i18n instances.
|
|
37
|
+
- **Scattered translations**: Translation files are typically stored in separate directories from components, making it harder to maintain consistency.
|
|
38
|
+
- **Manual namespace management**: Developers need to manually manage namespaces and ensure proper loading of translation resources.
|
|
39
|
+
- **Limited type safety**: TypeScript support requires additional configuration and doesn't provide automatic type generation for translations.
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
## What is Intlayer?
|
|
37
42
|
|
|
38
|
-
|
|
43
|
+
**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.
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
Key advantages of Intlayer:
|
|
41
46
|
|
|
42
|
-
|
|
47
|
+
- **Declarative content management**: Translations are defined alongside components, improving maintainability.
|
|
48
|
+
- **Type-safe by design**: Auto-generated TypeScript types ensure type safety and autocompletion.
|
|
49
|
+
- **Flexible content structure**: Supports complex content structures beyond simple key-value pairs.
|
|
50
|
+
- **Built-in Visual Editor**: Optional visual editing capabilities for non-technical team members.
|
|
51
|
+
- **Framework optimization**: Deep integration with Next.js for optimal performance.
|
|
43
52
|
|
|
44
|
-
|
|
53
|
+
## Why Combine Intlayer with next-i18next?
|
|
45
54
|
|
|
46
|
-
|
|
55
|
+
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-i18next for several reasons:
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
└── components
|
|
52
|
-
└── MyComponent
|
|
53
|
-
├── index.content.ts # Content declaration file
|
|
54
|
-
└── index.tsx
|
|
55
|
-
```
|
|
57
|
+
1. **Existing codebase**: You have an established next-i18next implementation and want to gradually migrate to Intlayer's improved developer experience.
|
|
58
|
+
2. **Legacy requirements**: Your project requires compatibility with existing i18next plugins or workflows.
|
|
59
|
+
3. **Team familiarity**: Your team is comfortable with next-i18next but wants better content management.
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
.
|
|
59
|
-
└── src
|
|
60
|
-
└── components
|
|
61
|
-
└── MyComponent
|
|
62
|
-
├── index.content.mjs # Content declaration file
|
|
63
|
-
└── index.mjx
|
|
64
|
-
```
|
|
61
|
+
This guide shows you how to leverage Intlayer's superior content declaration system while maintaining compatibility with next-i18next.
|
|
65
62
|
|
|
66
|
-
|
|
67
|
-
.
|
|
68
|
-
└── src
|
|
69
|
-
└── components
|
|
70
|
-
└── MyComponent
|
|
71
|
-
├── index.content.cjs # Content declaration file
|
|
72
|
-
└── index.cjx
|
|
73
|
-
```
|
|
63
|
+
## Intlayer vs. next-i18next: Key Differences
|
|
74
64
|
|
|
75
|
-
|
|
76
|
-
.
|
|
77
|
-
└── src
|
|
78
|
-
└── components
|
|
79
|
-
└── MyComponent
|
|
80
|
-
├── index.content.json # Content declaration file
|
|
81
|
-
└── index.jsx
|
|
82
|
-
```
|
|
65
|
+
For a detailed comparison of different i18n solutions including next-i18next and Intlayer, check out 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.
|
|
83
66
|
|
|
84
|
-
|
|
67
|
+
Key differences:
|
|
85
68
|
|
|
86
|
-
|
|
69
|
+
| Feature | next-i18next | Intlayer with next-i18next |
|
|
70
|
+
| ----------------- | ---------------------------- | -------------------------- |
|
|
71
|
+
| Content Location | Separate translation folders | Co-located with components |
|
|
72
|
+
| Type Safety | Manual setup required | Auto-generated types |
|
|
73
|
+
| Content Structure | Flat key-value pairs | Nested, structured objects |
|
|
74
|
+
| Visual Editor | Not included | Built-in optional editor |
|
|
75
|
+
| Setup Complexity | Multiple config files | Minimal configuration |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Step-by-Step Guide to Set Up Intlayer with next-i18next
|
|
80
|
+
|
|
81
|
+
### Step 1: Install Dependencies
|
|
82
|
+
|
|
83
|
+
Install the necessary packages using your preferred package manager:
|
|
87
84
|
|
|
88
85
|
```bash packageManager="npm"
|
|
89
|
-
npm install intlayer i18next next-i18next i18next-resources-to-backend
|
|
86
|
+
npm install intlayer next-intlayer i18next next-i18next i18next-resources-to-backend @intlayer/sync-json-plugin
|
|
90
87
|
```
|
|
91
88
|
|
|
92
|
-
```bash packageManager="
|
|
93
|
-
|
|
89
|
+
```bash packageManager="pnpm"
|
|
90
|
+
pnpm add intlayer next-intlayer i18next next-i18next i18next-resources-to-backend @intlayer/sync-json-plugin
|
|
94
91
|
```
|
|
95
92
|
|
|
96
|
-
```bash packageManager="
|
|
97
|
-
|
|
93
|
+
```bash packageManager="yarn"
|
|
94
|
+
yarn add intlayer next-intlayer i18next next-i18next i18next-resources-to-backend @intlayer/sync-json-plugin
|
|
98
95
|
```
|
|
99
96
|
|
|
100
|
-
|
|
97
|
+
**Package explanations:**
|
|
101
98
|
|
|
102
|
-
|
|
99
|
+
- **intlayer**: Core library for content declaration and management
|
|
100
|
+
- **next-intlayer**: Next.js integration layer with build plugins
|
|
101
|
+
- **i18next**: Core i18n framework
|
|
102
|
+
- **next-i18next**: Next.js wrapper for i18next
|
|
103
|
+
- **i18next-resources-to-backend**: Dynamic resource loading for i18next
|
|
104
|
+
- **@intlayer/sync-json-plugin**: Plugin to sync Intlayer content declarations to i18next JSON format
|
|
103
105
|
|
|
104
|
-
|
|
106
|
+
### Step 2: Configure Your Project
|
|
107
|
+
|
|
108
|
+
Create an Intlayer configuration file to define your locales and set up the sync plugin:
|
|
105
109
|
|
|
106
110
|
```typescript fileName="intlayer.config.ts" codeFormat="typescript"
|
|
107
111
|
import { Locales, type IntlayerConfig } from "intlayer";
|
|
112
|
+
import { syncJSON } from "@intlayer/sync-json-plugin";
|
|
108
113
|
|
|
109
114
|
const config: IntlayerConfig = {
|
|
110
115
|
internationalization: {
|
|
111
|
-
locales: [
|
|
116
|
+
locales: [
|
|
117
|
+
Locales.ENGLISH,
|
|
118
|
+
Locales.FRENCH,
|
|
119
|
+
Locales.SPANISH,
|
|
120
|
+
// Add your other locales
|
|
121
|
+
],
|
|
112
122
|
defaultLocale: Locales.ENGLISH,
|
|
113
123
|
},
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
124
|
+
plugins: [
|
|
125
|
+
syncJSON({
|
|
126
|
+
// Specify where i18next JSON files should be generated
|
|
127
|
+
source: ({ key, locale }) => `./intl/messages/${locale}/${key}.json`,
|
|
128
|
+
}),
|
|
129
|
+
],
|
|
118
130
|
};
|
|
119
131
|
|
|
120
132
|
export default config;
|
|
@@ -122,17 +134,24 @@ export default config;
|
|
|
122
134
|
|
|
123
135
|
```javascript fileName="intlayer.config.mjs" codeFormat="esm"
|
|
124
136
|
import { Locales } from "intlayer";
|
|
137
|
+
import { syncJSON } from "@intlayer/sync-json-plugin";
|
|
125
138
|
|
|
126
139
|
/** @type {import('intlayer').IntlayerConfig} */
|
|
127
140
|
const config = {
|
|
128
141
|
internationalization: {
|
|
129
|
-
locales: [
|
|
142
|
+
locales: [
|
|
143
|
+
Locales.ENGLISH,
|
|
144
|
+
Locales.FRENCH,
|
|
145
|
+
Locales.SPANISH,
|
|
146
|
+
// Add your other locales
|
|
147
|
+
],
|
|
130
148
|
defaultLocale: Locales.ENGLISH,
|
|
131
149
|
},
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
150
|
+
plugins: [
|
|
151
|
+
syncJSON({
|
|
152
|
+
source: ({ key, locale }) => `./intl/messages/${locale}/${key}.json`,
|
|
153
|
+
}),
|
|
154
|
+
],
|
|
136
155
|
};
|
|
137
156
|
|
|
138
157
|
export default config;
|
|
@@ -140,141 +159,354 @@ export default config;
|
|
|
140
159
|
|
|
141
160
|
```javascript fileName="intlayer.config.cjs" codeFormat="commonjs"
|
|
142
161
|
const { Locales } = require("intlayer");
|
|
162
|
+
const { syncJSON } = require("@intlayer/sync-json-plugin");
|
|
143
163
|
|
|
144
164
|
/** @type {import('intlayer').IntlayerConfig} */
|
|
145
165
|
const config = {
|
|
146
166
|
internationalization: {
|
|
147
|
-
locales: [
|
|
167
|
+
locales: [
|
|
168
|
+
Locales.ENGLISH,
|
|
169
|
+
Locales.FRENCH,
|
|
170
|
+
Locales.SPANISH,
|
|
171
|
+
// Add your other locales
|
|
172
|
+
],
|
|
148
173
|
defaultLocale: Locales.ENGLISH,
|
|
149
174
|
},
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
175
|
+
plugins: [
|
|
176
|
+
syncJSON({
|
|
177
|
+
source: ({ key, locale }) => `./intl/messages/${locale}/${key}.json`,
|
|
178
|
+
}),
|
|
179
|
+
],
|
|
154
180
|
};
|
|
155
181
|
|
|
156
182
|
module.exports = config;
|
|
157
183
|
```
|
|
158
184
|
|
|
159
|
-
|
|
185
|
+
> The `syncJSON` plugin automatically converts Intlayer content declarations to i18next-compatible JSON files. For more configuration options, refer to the [configuration documentation](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/configuration.md).
|
|
160
186
|
|
|
161
|
-
|
|
187
|
+
### Step 3: Integrate Intlayer in Your Next.js Configuration
|
|
188
|
+
|
|
189
|
+
Configure your Next.js setup to use Intlayer's build plugin:
|
|
190
|
+
|
|
191
|
+
```typescript fileName="next.config.ts" codeFormat="typescript"
|
|
192
|
+
import type { NextConfig } from "next";
|
|
193
|
+
import { withIntlayer } from "next-intlayer/server";
|
|
194
|
+
|
|
195
|
+
const nextConfig: NextConfig = {
|
|
196
|
+
/* your Next.js config options */
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export default withIntlayer(nextConfig);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
```javascript fileName="next.config.mjs" codeFormat="esm"
|
|
203
|
+
import { withIntlayer } from "next-intlayer/server";
|
|
204
|
+
|
|
205
|
+
/** @type {import('next').NextConfig} */
|
|
206
|
+
const nextConfig = {
|
|
207
|
+
/* your Next.js config options */
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export default withIntlayer(nextConfig);
|
|
211
|
+
```
|
|
162
212
|
|
|
163
|
-
|
|
213
|
+
```javascript fileName="next.config.cjs" codeFormat="commonjs"
|
|
214
|
+
const { withIntlayer } = require("next-intlayer/server");
|
|
215
|
+
|
|
216
|
+
/** @type {import('next').NextConfig} */
|
|
217
|
+
const nextConfig = {
|
|
218
|
+
/* your Next.js config options */
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
module.exports = withIntlayer(nextConfig);
|
|
222
|
+
```
|
|
164
223
|
|
|
165
|
-
|
|
224
|
+
> The `withIntlayer()` plugin ensures that Intlayer content declarations are built and monitored during development. It automatically regenerates i18next JSON files when you modify content declarations.
|
|
225
|
+
|
|
226
|
+
### Step 4: Set Up next-i18next Configuration
|
|
227
|
+
|
|
228
|
+
Create the standard next-i18next configuration file:
|
|
229
|
+
|
|
230
|
+
```javascript fileName="next-i18next.config.js"
|
|
231
|
+
module.exports = {
|
|
232
|
+
i18n: {
|
|
233
|
+
defaultLocale: "en",
|
|
234
|
+
locales: ["en", "fr", "es"],
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Update your Next.js config to use next-i18next:
|
|
240
|
+
|
|
241
|
+
```typescript fileName="next.config.ts" codeFormat="typescript"
|
|
242
|
+
import type { NextConfig } from "next";
|
|
243
|
+
import { withIntlayer } from "next-intlayer/server";
|
|
244
|
+
|
|
245
|
+
const nextConfig: NextConfig = {
|
|
246
|
+
/* your Next.js config options */
|
|
247
|
+
i18n: {
|
|
248
|
+
defaultLocale: "en",
|
|
249
|
+
locales: ["en", "fr", "es"],
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export default withIntlayer(nextConfig);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```javascript fileName="next.config.mjs" codeFormat="esm"
|
|
257
|
+
import { withIntlayer } from "next-intlayer/server";
|
|
258
|
+
|
|
259
|
+
/** @type {import('next').NextConfig} */
|
|
260
|
+
const nextConfig = {
|
|
261
|
+
/* your Next.js config options */
|
|
262
|
+
i18n: {
|
|
263
|
+
defaultLocale: "en",
|
|
264
|
+
locales: ["en", "fr", "es"],
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export default withIntlayer(nextConfig);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
```javascript fileName="next.config.cjs" codeFormat="commonjs"
|
|
272
|
+
const { withIntlayer } = require("next-intlayer/server");
|
|
273
|
+
|
|
274
|
+
/** @type {import('next').NextConfig} */
|
|
275
|
+
const nextConfig = {
|
|
276
|
+
/* your Next.js config options */
|
|
277
|
+
i18n: {
|
|
278
|
+
defaultLocale: "en",
|
|
279
|
+
locales: ["en", "fr", "es"],
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
module.exports = withIntlayer(nextConfig);
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Step 5: Set Up i18next Client Configuration
|
|
287
|
+
|
|
288
|
+
Create an i18next configuration file to load the generated resources:
|
|
166
289
|
|
|
167
290
|
```typescript fileName="i18n/client.ts" codeFormat="typescript"
|
|
168
291
|
import i18next from "i18next";
|
|
292
|
+
import { initReactI18next } from "react-i18next";
|
|
169
293
|
import resourcesToBackend from "i18next-resources-to-backend";
|
|
170
294
|
|
|
171
|
-
i18next
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
295
|
+
i18next
|
|
296
|
+
.use(initReactI18next)
|
|
297
|
+
.use(
|
|
298
|
+
resourcesToBackend(
|
|
299
|
+
(language: string, namespace: string) =>
|
|
300
|
+
import(`../intl/messages/${language}/${namespace}.json`)
|
|
301
|
+
)
|
|
175
302
|
)
|
|
176
|
-
|
|
303
|
+
.init({
|
|
304
|
+
fallbackLng: "en",
|
|
305
|
+
// Add other i18next configuration options as needed
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
export default i18next;
|
|
177
309
|
```
|
|
178
310
|
|
|
179
311
|
```javascript fileName="i18n/client.mjs" codeFormat="esm"
|
|
180
312
|
import i18next from "i18next";
|
|
313
|
+
import { initReactI18next } from "react-i18next";
|
|
181
314
|
import resourcesToBackend from "i18next-resources-to-backend";
|
|
182
315
|
|
|
183
|
-
i18next
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
316
|
+
i18next
|
|
317
|
+
.use(initReactI18next)
|
|
318
|
+
.use(
|
|
319
|
+
resourcesToBackend(
|
|
320
|
+
(language, namespace) =>
|
|
321
|
+
import(`../intl/messages/${language}/${namespace}.json`)
|
|
322
|
+
)
|
|
187
323
|
)
|
|
188
|
-
|
|
324
|
+
.init({
|
|
325
|
+
fallbackLng: "en",
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
export default i18next;
|
|
189
329
|
```
|
|
190
330
|
|
|
191
331
|
```javascript fileName="i18n/client.cjs" codeFormat="commonjs"
|
|
192
332
|
const i18next = require("i18next");
|
|
333
|
+
const { initReactI18next } = require("react-i18next");
|
|
193
334
|
const resourcesToBackend = require("i18next-resources-to-backend");
|
|
194
335
|
|
|
195
|
-
i18next
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
336
|
+
i18next
|
|
337
|
+
.use(initReactI18next)
|
|
338
|
+
.use(
|
|
339
|
+
resourcesToBackend(
|
|
340
|
+
(language, namespace) =>
|
|
341
|
+
import(`../intl/messages/${language}/${namespace}.json`)
|
|
342
|
+
)
|
|
199
343
|
)
|
|
200
|
-
|
|
344
|
+
.init({
|
|
345
|
+
fallbackLng: "en",
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
module.exports = i18next;
|
|
201
349
|
```
|
|
202
350
|
|
|
203
|
-
###
|
|
351
|
+
### Step 6: Declare Your Content
|
|
204
352
|
|
|
205
|
-
|
|
353
|
+
Create content declaration files alongside your components:
|
|
206
354
|
|
|
207
|
-
```typescript fileName="
|
|
355
|
+
```typescript fileName="src/components/Hero/Hero.content.ts" contentDeclarationFormat="typescript"
|
|
208
356
|
import { t, type Dictionary } from "intlayer";
|
|
209
357
|
|
|
210
|
-
const
|
|
211
|
-
key: "
|
|
358
|
+
const heroContent = {
|
|
359
|
+
key: "hero",
|
|
212
360
|
content: {
|
|
213
|
-
|
|
214
|
-
en: "
|
|
215
|
-
|
|
216
|
-
|
|
361
|
+
title: t({
|
|
362
|
+
en: "Welcome to Our Platform",
|
|
363
|
+
fr: "Bienvenue sur notre plateforme",
|
|
364
|
+
es: "Bienvenido a nuestra plataforma",
|
|
217
365
|
}),
|
|
366
|
+
description: t({
|
|
367
|
+
en: "Discover amazing features and possibilities",
|
|
368
|
+
fr: "Découvrez des fonctionnalités et possibilités incroyables",
|
|
369
|
+
es: "Descubre características y posibilidades increíbles",
|
|
370
|
+
}),
|
|
371
|
+
cta: {
|
|
372
|
+
primary: t({
|
|
373
|
+
en: "Get Started",
|
|
374
|
+
fr: "Commencer",
|
|
375
|
+
es: "Empezar",
|
|
376
|
+
}),
|
|
377
|
+
secondary: t({
|
|
378
|
+
en: "Learn More",
|
|
379
|
+
fr: "En savoir plus",
|
|
380
|
+
es: "Saber más",
|
|
381
|
+
}),
|
|
382
|
+
},
|
|
218
383
|
},
|
|
219
384
|
} satisfies Dictionary;
|
|
220
385
|
|
|
221
|
-
export default
|
|
386
|
+
export default heroContent;
|
|
222
387
|
```
|
|
223
388
|
|
|
224
|
-
```javascript fileName="
|
|
389
|
+
```javascript fileName="src/components/Hero/Hero.content.mjs" contentDeclarationFormat="esm"
|
|
225
390
|
import { t } from "intlayer";
|
|
226
391
|
|
|
227
392
|
/** @type {import('intlayer').Dictionary} */
|
|
228
|
-
const
|
|
229
|
-
key: "
|
|
393
|
+
const heroContent = {
|
|
394
|
+
key: "hero",
|
|
230
395
|
content: {
|
|
231
|
-
|
|
232
|
-
en: "
|
|
233
|
-
|
|
234
|
-
|
|
396
|
+
title: t({
|
|
397
|
+
en: "Welcome to Our Platform",
|
|
398
|
+
fr: "Bienvenue sur notre plateforme",
|
|
399
|
+
es: "Bienvenido a nuestra plataforma",
|
|
400
|
+
}),
|
|
401
|
+
description: t({
|
|
402
|
+
en: "Discover amazing features and possibilities",
|
|
403
|
+
fr: "Découvrez des fonctionnalités et possibilités incroyables",
|
|
404
|
+
es: "Descubre características y posibilidades increíbles",
|
|
235
405
|
}),
|
|
406
|
+
cta: {
|
|
407
|
+
primary: t({
|
|
408
|
+
en: "Get Started",
|
|
409
|
+
fr: "Commencer",
|
|
410
|
+
es: "Empezar",
|
|
411
|
+
}),
|
|
412
|
+
secondary: t({
|
|
413
|
+
en: "Learn More",
|
|
414
|
+
fr: "En savoir plus",
|
|
415
|
+
es: "Saber más",
|
|
416
|
+
}),
|
|
417
|
+
},
|
|
236
418
|
},
|
|
237
419
|
};
|
|
420
|
+
|
|
421
|
+
export default heroContent;
|
|
238
422
|
```
|
|
239
423
|
|
|
240
|
-
```javascript fileName="
|
|
424
|
+
```javascript fileName="src/components/Hero/Hero.content.cjs" contentDeclarationFormat="commonjs"
|
|
241
425
|
const { t } = require("intlayer");
|
|
242
426
|
|
|
243
|
-
|
|
244
|
-
|
|
427
|
+
/** @type {import('intlayer').Dictionary} */
|
|
428
|
+
const heroContent = {
|
|
429
|
+
key: "hero",
|
|
245
430
|
content: {
|
|
246
|
-
|
|
247
|
-
en: "
|
|
248
|
-
|
|
249
|
-
|
|
431
|
+
title: t({
|
|
432
|
+
en: "Welcome to Our Platform",
|
|
433
|
+
fr: "Bienvenue sur notre plateforme",
|
|
434
|
+
es: "Bienvenido a nuestra plataforma",
|
|
250
435
|
}),
|
|
436
|
+
description: t({
|
|
437
|
+
en: "Discover amazing features and possibilities",
|
|
438
|
+
fr: "Découvrez des fonctionnalités et possibilités incroyables",
|
|
439
|
+
es: "Descubre características y posibilidades increíbles",
|
|
440
|
+
}),
|
|
441
|
+
cta: {
|
|
442
|
+
primary: t({
|
|
443
|
+
en: "Get Started",
|
|
444
|
+
fr: "Commencer",
|
|
445
|
+
es: "Empezar",
|
|
446
|
+
}),
|
|
447
|
+
secondary: t({
|
|
448
|
+
en: "Learn More",
|
|
449
|
+
fr: "En savoir plus",
|
|
450
|
+
es: "Saber más",
|
|
451
|
+
}),
|
|
452
|
+
},
|
|
251
453
|
},
|
|
252
454
|
};
|
|
455
|
+
|
|
456
|
+
module.exports = heroContent;
|
|
253
457
|
```
|
|
254
458
|
|
|
255
|
-
```json fileName="
|
|
459
|
+
```json fileName="src/components/Hero/Hero.content.json" contentDeclarationFormat="json"
|
|
256
460
|
{
|
|
257
461
|
"$schema": "https://intlayer.org/schema.json",
|
|
258
|
-
"key": "
|
|
462
|
+
"key": "hero",
|
|
259
463
|
"content": {
|
|
260
|
-
"
|
|
464
|
+
"title": {
|
|
465
|
+
"nodeType": "translation",
|
|
466
|
+
"translation": {
|
|
467
|
+
"en": "Welcome to Our Platform",
|
|
468
|
+
"fr": "Bienvenue sur notre plateforme",
|
|
469
|
+
"es": "Bienvenido a nuestra plataforma"
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
"description": {
|
|
261
473
|
"nodeType": "translation",
|
|
262
474
|
"translation": {
|
|
263
|
-
"en": "
|
|
264
|
-
"fr": "
|
|
265
|
-
"es": "
|
|
475
|
+
"en": "Discover amazing features and possibilities",
|
|
476
|
+
"fr": "Découvrez des fonctionnalités et possibilités incroyables",
|
|
477
|
+
"es": "Descubre características y posibilidades increíbles"
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
"cta": {
|
|
481
|
+
"primary": {
|
|
482
|
+
"nodeType": "translation",
|
|
483
|
+
"translation": {
|
|
484
|
+
"en": "Get Started",
|
|
485
|
+
"fr": "Commencer",
|
|
486
|
+
"es": "Empezar"
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
"secondary": {
|
|
490
|
+
"nodeType": "translation",
|
|
491
|
+
"translation": {
|
|
492
|
+
"en": "Learn More",
|
|
493
|
+
"fr": "En savoir plus",
|
|
494
|
+
"es": "Saber más"
|
|
495
|
+
}
|
|
266
496
|
}
|
|
267
497
|
}
|
|
268
498
|
}
|
|
269
499
|
}
|
|
270
500
|
```
|
|
271
501
|
|
|
272
|
-
|
|
502
|
+
> Content declarations can be placed anywhere in your `src` directory (or configured `contentDir`). The file must match the pattern `*.content.{ts,tsx,js,jsx,mjs,cjs,json}`. For more details, see the [content declaration documentation](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/content_file.md).
|
|
503
|
+
|
|
504
|
+
### Step 7: Build i18next Resources
|
|
273
505
|
|
|
274
|
-
|
|
506
|
+
Generate the i18next JSON files from your Intlayer content declarations:
|
|
275
507
|
|
|
276
508
|
```bash packageManager="npm"
|
|
277
|
-
npx
|
|
509
|
+
npx intlayer build
|
|
278
510
|
```
|
|
279
511
|
|
|
280
512
|
```bash packageManager="yarn"
|
|
@@ -285,83 +517,451 @@ yarn intlayer build
|
|
|
285
517
|
pnpm intlayer build
|
|
286
518
|
```
|
|
287
519
|
|
|
288
|
-
This will generate
|
|
520
|
+
This will generate files in the `./intl/messages` directory:
|
|
289
521
|
|
|
290
522
|
```bash
|
|
291
|
-
|
|
292
|
-
└──
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
└── my-content.json
|
|
523
|
+
intl
|
|
524
|
+
└── messages
|
|
525
|
+
├── en
|
|
526
|
+
│ └── hero.json
|
|
527
|
+
├── fr
|
|
528
|
+
│ └── hero.json
|
|
298
529
|
└── es
|
|
299
|
-
|
|
530
|
+
└── hero.json
|
|
300
531
|
```
|
|
301
532
|
|
|
302
|
-
|
|
533
|
+
Each JSON file contains translations extracted from the corresponding Intlayer content declarations.
|
|
303
534
|
|
|
304
|
-
###
|
|
535
|
+
### Step 8: Utilize Content in Your Code
|
|
305
536
|
|
|
306
|
-
|
|
537
|
+
Access your translations using next-i18next's hooks:
|
|
307
538
|
|
|
308
|
-
```typescript fileName="
|
|
309
|
-
import {
|
|
539
|
+
```typescript fileName="src/components/Hero/Hero.tsx" codeFormat="typescript"
|
|
540
|
+
import type { FC } from "react";
|
|
541
|
+
import { useTranslation } from "next-i18next";
|
|
310
542
|
|
|
311
|
-
|
|
312
|
-
const
|
|
543
|
+
const Hero: FC = () => {
|
|
544
|
+
const { t } = useTranslation("hero");
|
|
313
545
|
|
|
314
|
-
|
|
546
|
+
return (
|
|
547
|
+
<section>
|
|
548
|
+
<h1>{t("title")}</h1>
|
|
549
|
+
<p>{t("description")}</p>
|
|
550
|
+
<div>
|
|
551
|
+
<button>{t("cta.primary")}</button>
|
|
552
|
+
<button>{t("cta.secondary")}</button>
|
|
553
|
+
</div>
|
|
554
|
+
</section>
|
|
555
|
+
);
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
export default Hero;
|
|
315
559
|
```
|
|
316
560
|
|
|
317
|
-
|
|
561
|
+
```jsx fileName="src/components/Hero/Hero.mjx" codeFormat="esm"
|
|
562
|
+
import { useTranslation } from "next-i18next";
|
|
318
563
|
|
|
319
|
-
|
|
564
|
+
const Hero = () => {
|
|
565
|
+
const { t } = useTranslation("hero");
|
|
320
566
|
|
|
321
|
-
|
|
567
|
+
return (
|
|
568
|
+
<section>
|
|
569
|
+
<h1>{t("title")}</h1>
|
|
570
|
+
<p>{t("description")}</p>
|
|
571
|
+
<div>
|
|
572
|
+
<button>{t("cta.primary")}</button>
|
|
573
|
+
<button>{t("cta.secondary")}</button>
|
|
574
|
+
</div>
|
|
575
|
+
</section>
|
|
576
|
+
);
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
export default Hero;
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
```jsx fileName="src/components/Hero/Hero.csx" codeFormat="commonjs"
|
|
583
|
+
const { useTranslation } = require("next-i18next");
|
|
584
|
+
|
|
585
|
+
const Hero = () => {
|
|
586
|
+
const { t } = useTranslation("hero");
|
|
587
|
+
|
|
588
|
+
return (
|
|
589
|
+
<section>
|
|
590
|
+
<h1>{t("title")}</h1>
|
|
591
|
+
<p>{t("description")}</p>
|
|
592
|
+
<div>
|
|
593
|
+
<button>{t("cta.primary")}</button>
|
|
594
|
+
<button>{t("cta.secondary")}</button>
|
|
595
|
+
</div>
|
|
596
|
+
</section>
|
|
597
|
+
);
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
module.exports = Hero;
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
> The namespace in `useTranslation("hero")` corresponds to the `key` field in your Intlayer content declaration.
|
|
604
|
+
|
|
605
|
+
### (Optional) Step 9: Internationalization of Metadata
|
|
606
|
+
|
|
607
|
+
For page metadata, use next-i18next's `serverSideTranslations`:
|
|
608
|
+
|
|
609
|
+
```typescript fileName="src/pages/index.tsx" codeFormat="typescript"
|
|
610
|
+
import { GetStaticProps } from "next";
|
|
611
|
+
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
|
612
|
+
import { useTranslation } from "next-i18next";
|
|
613
|
+
import Head from "next/head";
|
|
614
|
+
|
|
615
|
+
export default function Home() {
|
|
616
|
+
const { t } = useTranslation("common");
|
|
617
|
+
|
|
618
|
+
return (
|
|
619
|
+
<>
|
|
620
|
+
<Head>
|
|
621
|
+
<title>{t("meta.title")}</title>
|
|
622
|
+
<meta name="description" content={t("meta.description")} />
|
|
623
|
+
</Head>
|
|
624
|
+
{/* Page content */}
|
|
625
|
+
</>
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export const getStaticProps: GetStaticProps = async ({ locale }) => {
|
|
630
|
+
return {
|
|
631
|
+
props: {
|
|
632
|
+
...(await serverSideTranslations(locale ?? "en", ["common"])),
|
|
633
|
+
},
|
|
634
|
+
};
|
|
635
|
+
};
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
Create a corresponding metadata content declaration:
|
|
639
|
+
|
|
640
|
+
```typescript fileName="src/content/metadata.content.ts" contentDeclarationFormat="typescript"
|
|
641
|
+
import { t, type Dictionary } from "intlayer";
|
|
642
|
+
|
|
643
|
+
const metadataContent = {
|
|
644
|
+
key: "common",
|
|
645
|
+
content: {
|
|
646
|
+
meta: {
|
|
647
|
+
title: t({
|
|
648
|
+
en: "My Amazing App",
|
|
649
|
+
fr: "Mon Application Incroyable",
|
|
650
|
+
es: "Mi Aplicación Increíble",
|
|
651
|
+
}),
|
|
652
|
+
description: t({
|
|
653
|
+
en: "Discover the best features of our platform",
|
|
654
|
+
fr: "Découvrez les meilleures fonctionnalités de notre plateforme",
|
|
655
|
+
es: "Descubre las mejores características de nuestra plataforma",
|
|
656
|
+
}),
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
} satisfies Dictionary;
|
|
660
|
+
|
|
661
|
+
export default metadataContent;
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
### (Optional) Step 10: Internationalization of sitemap.xml and robots.txt
|
|
665
|
+
|
|
666
|
+
For sitemap and robots.txt internationalization, you can use next-i18next's locale information:
|
|
667
|
+
|
|
668
|
+
```typescript fileName="src/pages/sitemap.xml.ts" codeFormat="typescript"
|
|
669
|
+
import { GetServerSideProps } from "next";
|
|
670
|
+
|
|
671
|
+
const SITE_URL = "https://example.com";
|
|
672
|
+
|
|
673
|
+
function generateSiteMap(locales: string[]) {
|
|
674
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
675
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
|
676
|
+
xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
|
677
|
+
${locales
|
|
678
|
+
.map((locale) => {
|
|
679
|
+
return `
|
|
680
|
+
<url>
|
|
681
|
+
<loc>${SITE_URL}/${locale}</loc>
|
|
682
|
+
${locales
|
|
683
|
+
.map((altLocale) => {
|
|
684
|
+
return `
|
|
685
|
+
<xhtml:link
|
|
686
|
+
rel="alternate"
|
|
687
|
+
hreflang="${altLocale}"
|
|
688
|
+
href="${SITE_URL}/${altLocale}"/>
|
|
689
|
+
`;
|
|
690
|
+
})
|
|
691
|
+
.join("")}
|
|
692
|
+
</url>
|
|
693
|
+
`;
|
|
694
|
+
})
|
|
695
|
+
.join("")}
|
|
696
|
+
</urlset>
|
|
697
|
+
`;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
export const getServerSideProps: GetServerSideProps = async ({ res }) => {
|
|
701
|
+
const locales = ["en", "fr", "es"];
|
|
702
|
+
const sitemap = generateSiteMap(locales);
|
|
703
|
+
|
|
704
|
+
res.setHeader("Content-Type", "text/xml");
|
|
705
|
+
res.write(sitemap);
|
|
706
|
+
res.end();
|
|
707
|
+
|
|
708
|
+
return {
|
|
709
|
+
props: {},
|
|
710
|
+
};
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
export default function SiteMap() {
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
### (Optional) Step 11: Change the Language of Your Content
|
|
719
|
+
|
|
720
|
+
Implement a language switcher using next-i18next:
|
|
721
|
+
|
|
722
|
+
```typescript fileName="src/components/LanguageSwitcher.tsx" codeFormat="typescript"
|
|
723
|
+
import { useRouter } from "next/router";
|
|
724
|
+
import Link from "next/link";
|
|
322
725
|
import type { FC } from "react";
|
|
323
|
-
import { useTranslation } from "react-i18next";
|
|
324
726
|
|
|
325
|
-
const
|
|
326
|
-
const
|
|
727
|
+
const LanguageSwitcher: FC = () => {
|
|
728
|
+
const router = useRouter();
|
|
729
|
+
const { locales, locale: currentLocale, asPath } = router;
|
|
327
730
|
|
|
328
731
|
return (
|
|
329
732
|
<div>
|
|
330
|
-
<
|
|
331
|
-
<
|
|
733
|
+
<p>Current Language: {currentLocale}</p>
|
|
734
|
+
<ul>
|
|
735
|
+
{locales?.map((locale) => (
|
|
736
|
+
<li key={locale}>
|
|
737
|
+
<Link href={asPath} locale={locale}>
|
|
738
|
+
<a style={{ fontWeight: locale === currentLocale ? "bold" : "normal" }}>
|
|
739
|
+
{locale.toUpperCase()}
|
|
740
|
+
</a>
|
|
741
|
+
</Link>
|
|
742
|
+
</li>
|
|
743
|
+
))}
|
|
744
|
+
</ul>
|
|
332
745
|
</div>
|
|
333
746
|
);
|
|
334
747
|
};
|
|
335
748
|
|
|
336
|
-
export default
|
|
749
|
+
export default LanguageSwitcher;
|
|
337
750
|
```
|
|
338
751
|
|
|
339
|
-
```jsx fileName="src/components/
|
|
340
|
-
import {
|
|
752
|
+
```jsx fileName="src/components/LanguageSwitcher.mjx" codeFormat="esm"
|
|
753
|
+
import { useRouter } from "next/router";
|
|
754
|
+
import Link from "next/link";
|
|
341
755
|
|
|
342
|
-
const
|
|
343
|
-
const
|
|
756
|
+
const LanguageSwitcher = () => {
|
|
757
|
+
const router = useRouter();
|
|
758
|
+
const { locales, locale: currentLocale, asPath } = router;
|
|
344
759
|
|
|
345
760
|
return (
|
|
346
761
|
<div>
|
|
347
|
-
<
|
|
348
|
-
<
|
|
762
|
+
<p>Current Language: {currentLocale}</p>
|
|
763
|
+
<ul>
|
|
764
|
+
{locales?.map((locale) => (
|
|
765
|
+
<li key={locale}>
|
|
766
|
+
<Link href={asPath} locale={locale}>
|
|
767
|
+
<a
|
|
768
|
+
style={{
|
|
769
|
+
fontWeight: locale === currentLocale ? "bold" : "normal",
|
|
770
|
+
}}
|
|
771
|
+
>
|
|
772
|
+
{locale.toUpperCase()}
|
|
773
|
+
</a>
|
|
774
|
+
</Link>
|
|
775
|
+
</li>
|
|
776
|
+
))}
|
|
777
|
+
</ul>
|
|
349
778
|
</div>
|
|
350
779
|
);
|
|
351
780
|
};
|
|
781
|
+
|
|
782
|
+
export default LanguageSwitcher;
|
|
352
783
|
```
|
|
353
784
|
|
|
354
|
-
```jsx fileName="src/components/
|
|
355
|
-
const {
|
|
785
|
+
```jsx fileName="src/components/LanguageSwitcher.csx" codeFormat="commonjs"
|
|
786
|
+
const { useRouter } = require("next/router");
|
|
787
|
+
const Link = require("next/link");
|
|
356
788
|
|
|
357
|
-
const
|
|
358
|
-
const
|
|
789
|
+
const LanguageSwitcher = () => {
|
|
790
|
+
const router = useRouter();
|
|
791
|
+
const { locales, locale: currentLocale, asPath } = router;
|
|
359
792
|
|
|
360
793
|
return (
|
|
361
794
|
<div>
|
|
362
|
-
<
|
|
363
|
-
<
|
|
795
|
+
<p>Current Language: {currentLocale}</p>
|
|
796
|
+
<ul>
|
|
797
|
+
{locales?.map((locale) => (
|
|
798
|
+
<li key={locale}>
|
|
799
|
+
<Link href={asPath} locale={locale}>
|
|
800
|
+
<a
|
|
801
|
+
style={{
|
|
802
|
+
fontWeight: locale === currentLocale ? "bold" : "normal",
|
|
803
|
+
}}
|
|
804
|
+
>
|
|
805
|
+
{locale.toUpperCase()}
|
|
806
|
+
</a>
|
|
807
|
+
</Link>
|
|
808
|
+
</li>
|
|
809
|
+
))}
|
|
810
|
+
</ul>
|
|
364
811
|
</div>
|
|
365
812
|
);
|
|
366
813
|
};
|
|
814
|
+
|
|
815
|
+
module.exports = LanguageSwitcher;
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
### (Optional) Step 12: Creating a Localized Link Component
|
|
819
|
+
|
|
820
|
+
Create a custom Link component that preserves locale context:
|
|
821
|
+
|
|
822
|
+
```typescript fileName="src/components/LocalizedLink.tsx" codeFormat="typescript"
|
|
823
|
+
import Link, { LinkProps } from "next/link";
|
|
824
|
+
import { useRouter } from "next/router";
|
|
825
|
+
import type { FC, PropsWithChildren } from "react";
|
|
826
|
+
|
|
827
|
+
const LocalizedLink: FC<PropsWithChildren<LinkProps>> = ({
|
|
828
|
+
children,
|
|
829
|
+
href,
|
|
830
|
+
...props
|
|
831
|
+
}) => {
|
|
832
|
+
const router = useRouter();
|
|
833
|
+
const { locale } = router;
|
|
834
|
+
|
|
835
|
+
return (
|
|
836
|
+
<Link href={href} locale={locale} {...props}>
|
|
837
|
+
{children}
|
|
838
|
+
</Link>
|
|
839
|
+
);
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
export default LocalizedLink;
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
```jsx fileName="src/components/LocalizedLink.mjx" codeFormat="esm"
|
|
846
|
+
import Link from "next/link";
|
|
847
|
+
import { useRouter } from "next/router";
|
|
848
|
+
|
|
849
|
+
const LocalizedLink = ({ children, href, ...props }) => {
|
|
850
|
+
const router = useRouter();
|
|
851
|
+
const { locale } = router;
|
|
852
|
+
|
|
853
|
+
return (
|
|
854
|
+
<Link href={href} locale={locale} {...props}>
|
|
855
|
+
{children}
|
|
856
|
+
</Link>
|
|
857
|
+
);
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
export default LocalizedLink;
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
```jsx fileName="src/components/LocalizedLink.csx" codeFormat="commonjs"
|
|
864
|
+
const Link = require("next/link");
|
|
865
|
+
const { useRouter } = require("next/router");
|
|
866
|
+
|
|
867
|
+
const LocalizedLink = ({ children, href, ...props }) => {
|
|
868
|
+
const router = useRouter();
|
|
869
|
+
const { locale } = router;
|
|
870
|
+
|
|
871
|
+
return (
|
|
872
|
+
<Link href={href} locale={locale} {...props}>
|
|
873
|
+
{children}
|
|
874
|
+
</Link>
|
|
875
|
+
);
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
module.exports = LocalizedLink;
|
|
367
879
|
```
|
|
880
|
+
|
|
881
|
+
---
|
|
882
|
+
|
|
883
|
+
## TypeScript Configuration
|
|
884
|
+
|
|
885
|
+
Intlayer generates TypeScript type definitions for your content. To enable these types:
|
|
886
|
+
|
|
887
|
+
```json5 fileName="tsconfig.json"
|
|
888
|
+
{
|
|
889
|
+
"compilerOptions": {
|
|
890
|
+
// Your existing TypeScript config
|
|
891
|
+
},
|
|
892
|
+
"include": [
|
|
893
|
+
"src",
|
|
894
|
+
".intlayer/**/*.ts", // Include Intlayer auto-generated types
|
|
895
|
+
],
|
|
896
|
+
}
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
This provides:
|
|
900
|
+
|
|
901
|
+
- **Autocompletion** for translation keys
|
|
902
|
+
- **Type checking** to catch missing translations
|
|
903
|
+
- **IntelliSense** support in your IDE
|
|
904
|
+
|
|
905
|
+
---
|
|
906
|
+
|
|
907
|
+
## Git Configuration
|
|
908
|
+
|
|
909
|
+
Exclude generated files from version control:
|
|
910
|
+
|
|
911
|
+
```plaintext fileName=".gitignore"
|
|
912
|
+
# Ignore files generated by Intlayer
|
|
913
|
+
.intlayer
|
|
914
|
+
intl
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
These files are automatically regenerated during the build process and don't need to be committed to your repository.
|
|
918
|
+
|
|
919
|
+
---
|
|
920
|
+
|
|
921
|
+
## VS Code Extension
|
|
922
|
+
|
|
923
|
+
Intlayer provides a VS Code extension to enhance your development experience:
|
|
924
|
+
|
|
925
|
+
[Install from VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=intlayer.intlayer-vs-code-extension)
|
|
926
|
+
|
|
927
|
+
Features:
|
|
928
|
+
|
|
929
|
+
- **Autocompletion** for content keys
|
|
930
|
+
- **Inline previews** of translations
|
|
931
|
+
- **Error detection** for missing translations
|
|
932
|
+
- **Quick actions** for managing content
|
|
933
|
+
|
|
934
|
+
For more details, see the [VS Code Extension documentation](https://intlayer.org/doc/vs-code-extension).
|
|
935
|
+
|
|
936
|
+
---
|
|
937
|
+
|
|
938
|
+
## Development Workflow
|
|
939
|
+
|
|
940
|
+
When working with this setup:
|
|
941
|
+
|
|
942
|
+
1. **Declare content** in `*.content.*` files alongside your components
|
|
943
|
+
2. **Build translations** with `intlayer build` (or let Next.js plugin auto-rebuild in dev mode)
|
|
944
|
+
3. **Use translations** in components via `useTranslation` from next-i18next
|
|
945
|
+
4. **Benefit from TypeScript** autocompletion and type checking
|
|
946
|
+
|
|
947
|
+
---
|
|
948
|
+
|
|
949
|
+
## Going Further
|
|
950
|
+
|
|
951
|
+
- **Visual Editor**: Consider implementing the [Intlayer Visual Editor](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_visual_editor.md) for non-technical team members
|
|
952
|
+
- **CMS Integration**: Externalize your content using the [Intlayer CMS](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_CMS.md)
|
|
953
|
+
- **Full Intlayer Migration**: For new projects or major refactors, consider using [pure Intlayer](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_with_nextjs_16.md) for the best developer experience
|
|
954
|
+
|
|
955
|
+
---
|
|
956
|
+
|
|
957
|
+
## Conclusion
|
|
958
|
+
|
|
959
|
+
By combining Intlayer's superior content declaration system with next-i18next's mature i18n infrastructure, you get:
|
|
960
|
+
|
|
961
|
+
✅ **Better content organization** - Co-located with components
|
|
962
|
+
✅ **Type safety** - Auto-generated TypeScript types
|
|
963
|
+
✅ **Easier maintenance** - Single source of truth for translations
|
|
964
|
+
✅ **Compatibility** - Works with existing i18next ecosystem
|
|
965
|
+
✅ **Flexibility** - Gradual migration path from pure next-i18next
|
|
966
|
+
|
|
967
|
+
This approach is ideal for teams that want to improve their i18n workflow while maintaining compatibility with the i18next ecosystem.
|