@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: 2024-12-24
|
|
3
|
-
updatedAt: 2025-
|
|
4
|
-
title: Intlayer
|
|
5
|
-
description:
|
|
3
|
+
updatedAt: 2025-10-29
|
|
4
|
+
title: Migrate from i18next to Intlayer - Complete Integration Guide
|
|
5
|
+
description: Comprehensive guide to migrating from i18next to Intlayer or using them together. Learn the differences, configuration, and best practices for optimal internationalization in Next.js applications.
|
|
6
6
|
keywords:
|
|
7
7
|
- Intlayer
|
|
8
8
|
- i18next
|
|
@@ -14,61 +14,303 @@ keywords:
|
|
|
14
14
|
- Next.js
|
|
15
15
|
- JavaScript
|
|
16
16
|
- TypeScript
|
|
17
|
+
- Migration
|
|
18
|
+
- Integration
|
|
17
19
|
slugs:
|
|
18
20
|
- blog
|
|
19
21
|
- intlayer-with-i18next
|
|
22
|
+
history:
|
|
23
|
+
- version: 7.0.0
|
|
24
|
+
date: 2025-10-29
|
|
25
|
+
changes: Complete rewrite with step-by-step guide and code examples
|
|
20
26
|
---
|
|
21
27
|
|
|
22
|
-
#
|
|
28
|
+
# Migrate from i18next to Intlayer: Complete Integration Guide
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
## Table of Contents
|
|
25
31
|
|
|
26
|
-
|
|
32
|
+
<TOC/>
|
|
33
|
+
|
|
34
|
+
## What is i18next?
|
|
35
|
+
|
|
36
|
+
**i18next** is an open-source internationalization (i18n) framework designed for JavaScript applications. It has been widely used for managing translations, localization, and language switching in software projects for many years. While powerful and mature, i18next has some limitations that can complicate scalability and modern development workflows.
|
|
37
|
+
|
|
38
|
+
## What is Intlayer?
|
|
39
|
+
|
|
40
|
+
**Intlayer** is a modern, open-source internationalization framework designed to simplify multilingual support in web applications. Intlayer addresses many of the limitations found in traditional i18n solutions like i18next, offering a more flexible and developer-friendly approach to content declaration and management.
|
|
27
41
|
|
|
28
42
|
## Intlayer vs. i18next: Key Differences
|
|
29
43
|
|
|
30
|
-
|
|
44
|
+
Before diving into the integration, let's understand the key differences between these two frameworks:
|
|
45
|
+
|
|
46
|
+
### 1. Content Declaration & Dictionary Management
|
|
47
|
+
|
|
48
|
+
**i18next:**
|
|
49
|
+
|
|
50
|
+
- Requires translation dictionaries to be declared in specific folders (typically `public/locales/` or `locales/`)
|
|
51
|
+
- Separate JSON files for each namespace and locale
|
|
52
|
+
- Difficult to track which translations belong to which components
|
|
53
|
+
- Can complicate application scalability as the project grows
|
|
54
|
+
|
|
55
|
+
```plaintext
|
|
56
|
+
# i18next typical structure
|
|
57
|
+
locales/
|
|
58
|
+
├── en/
|
|
59
|
+
│ ├── common.json
|
|
60
|
+
│ ├── home.json
|
|
61
|
+
│ └── about.json
|
|
62
|
+
└── fr/
|
|
63
|
+
├── common.json
|
|
64
|
+
├── home.json
|
|
65
|
+
└── about.json
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Intlayer:**
|
|
69
|
+
|
|
70
|
+
- Allows content to be declared right next to your components
|
|
71
|
+
- Co-location of content with components
|
|
72
|
+
- Automatic detection and adaptation when components move or are removed
|
|
73
|
+
- Better maintainability and developer experience
|
|
74
|
+
|
|
75
|
+
```plaintext
|
|
76
|
+
# Intlayer structure
|
|
77
|
+
components/
|
|
78
|
+
├── Header/
|
|
79
|
+
│ ├── Header.tsx
|
|
80
|
+
│ └── Header.content.ts # Translations live with the component
|
|
81
|
+
└── Footer/
|
|
82
|
+
├── Footer.tsx
|
|
83
|
+
└── Footer.content.ts
|
|
84
|
+
```
|
|
31
85
|
|
|
32
|
-
|
|
86
|
+
**Advantages:**
|
|
33
87
|
|
|
34
|
-
- **Simplified Content Editing**:
|
|
35
|
-
- **Automatic Adaptation**: If a component changes location or is removed, Intlayer detects and adapts automatically
|
|
88
|
+
- **Simplified Content Editing**: Developers don't have to search through multiple folders to find the correct dictionary
|
|
89
|
+
- **Automatic Adaptation**: If a component changes location or is removed, Intlayer detects and adapts automatically
|
|
90
|
+
- **Better Code Organization**: Keeps related code together, improving maintainability
|
|
36
91
|
|
|
37
92
|
### 2. Configuration Complexity
|
|
38
93
|
|
|
39
|
-
|
|
94
|
+
**i18next:**
|
|
95
|
+
|
|
96
|
+
- Complex configuration, especially with server-side rendering
|
|
97
|
+
- Requires manual setup for Next.js middleware
|
|
98
|
+
- Separate configuration for client and server
|
|
99
|
+
- Multiple plugins needed for full functionality
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// i18next configuration can get complex
|
|
103
|
+
import i18n from "i18next";
|
|
104
|
+
import { initReactI18next } from "react-i18next";
|
|
105
|
+
import Backend from "i18next-http-backend";
|
|
106
|
+
import LanguageDetector from "i18next-browser-languagedetector";
|
|
107
|
+
|
|
108
|
+
i18n
|
|
109
|
+
.use(Backend)
|
|
110
|
+
.use(LanguageDetector)
|
|
111
|
+
.use(initReactI18next)
|
|
112
|
+
.init({
|
|
113
|
+
fallbackLng: "en",
|
|
114
|
+
debug: true,
|
|
115
|
+
interpolation: {
|
|
116
|
+
escapeValue: false,
|
|
117
|
+
},
|
|
118
|
+
// ... many more options
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Intlayer:**
|
|
123
|
+
|
|
124
|
+
- Streamlined configuration process
|
|
125
|
+
- Built-in support for Next.js with simple setup
|
|
126
|
+
- Unified configuration for client and server
|
|
127
|
+
- Minimal configuration required to get started
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// Intlayer configuration is straightforward
|
|
131
|
+
import { Locales, type IntlayerConfig } from "intlayer";
|
|
132
|
+
|
|
133
|
+
const config: IntlayerConfig = {
|
|
134
|
+
internationalization: {
|
|
135
|
+
locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
|
|
136
|
+
defaultLocale: Locales.ENGLISH,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export default config;
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 3. TypeScript Integration
|
|
144
|
+
|
|
145
|
+
**i18next:**
|
|
146
|
+
|
|
147
|
+
- TypeScript support requires additional setup
|
|
148
|
+
- Type safety for translation keys is limited
|
|
149
|
+
- Requires manual type definitions
|
|
150
|
+
- Auto-completion is basic
|
|
151
|
+
|
|
152
|
+
**Intlayer:**
|
|
153
|
+
|
|
154
|
+
- First-class TypeScript support out of the box
|
|
155
|
+
- Fully typed content declarations
|
|
156
|
+
- Auto-generated types for all translations
|
|
157
|
+
- Excellent IDE auto-completion and error detection
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// Intlayer provides full type safety
|
|
161
|
+
import { t, type Dictionary } from "intlayer";
|
|
162
|
+
|
|
163
|
+
const content = {
|
|
164
|
+
key: "homepage",
|
|
165
|
+
content: {
|
|
166
|
+
title: t({
|
|
167
|
+
en: "Welcome to our site",
|
|
168
|
+
fr: "Bienvenue sur notre site",
|
|
169
|
+
es: "Bienvenido a nuestro sitio",
|
|
170
|
+
}),
|
|
171
|
+
description: t({
|
|
172
|
+
en: "Get started by exploring our features",
|
|
173
|
+
fr: "Commencez par explorer nos fonctionnalités",
|
|
174
|
+
es: "Comience explorando nuestras características",
|
|
175
|
+
}),
|
|
176
|
+
},
|
|
177
|
+
} satisfies Dictionary;
|
|
178
|
+
|
|
179
|
+
// TypeScript knows exactly what keys and values are available
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 4. Consistency & Validation
|
|
183
|
+
|
|
184
|
+
**i18next:**
|
|
185
|
+
|
|
186
|
+
- Easy to miss translations for specific locales
|
|
187
|
+
- No built-in validation for translation completeness
|
|
188
|
+
- Can lead to runtime errors with missing keys
|
|
189
|
+
- Requires manual checking for consistency
|
|
190
|
+
|
|
191
|
+
**Intlayer:**
|
|
192
|
+
|
|
193
|
+
- Enforces translation completeness at build time
|
|
194
|
+
- Ensures all locales have all required keys
|
|
195
|
+
- TypeScript errors for missing translations
|
|
196
|
+
- Prevents runtime errors from missing content
|
|
197
|
+
|
|
198
|
+
### 5. Content Sharing Across Applications
|
|
199
|
+
|
|
200
|
+
**i18next:**
|
|
201
|
+
|
|
202
|
+
- Difficult to share translations across multiple apps
|
|
203
|
+
- Requires custom solutions for shared translations
|
|
204
|
+
- No standard approach for monorepo setups
|
|
205
|
+
|
|
206
|
+
**Intlayer:**
|
|
207
|
+
|
|
208
|
+
- Built-in support for sharing content declarations
|
|
209
|
+
- Works seamlessly in monorepo architectures
|
|
210
|
+
- Easy to share translations across multiple applications and libraries
|
|
211
|
+
- Promotes consistency across your entire codebase
|
|
212
|
+
|
|
213
|
+
### 6. Server Components & Next.js App Router
|
|
214
|
+
|
|
215
|
+
**i18next:**
|
|
216
|
+
|
|
217
|
+
- Limited support for Next.js App Router
|
|
218
|
+
- Complex setup for Server Components
|
|
219
|
+
- Requires workarounds for async components
|
|
220
|
+
- Documentation not always up-to-date with latest Next.js features
|
|
221
|
+
|
|
222
|
+
**Intlayer:**
|
|
223
|
+
|
|
224
|
+
- Native support for Next.js App Router
|
|
225
|
+
- First-class support for Server Components
|
|
226
|
+
- Optimized for Next.js 14, 15, and 16
|
|
227
|
+
- Works seamlessly with Turbopack and React Server Components
|
|
228
|
+
|
|
229
|
+
## When to Use i18next vs. Intlayer
|
|
230
|
+
|
|
231
|
+
### Use i18next if:
|
|
232
|
+
|
|
233
|
+
- You have an existing large application already using i18next
|
|
234
|
+
- You need specific i18next plugins or ecosystem tools
|
|
235
|
+
- You're working on a non-React application
|
|
236
|
+
- You need interpolation patterns that i18next handles uniquely
|
|
237
|
+
|
|
238
|
+
### Use Intlayer if:
|
|
239
|
+
|
|
240
|
+
- You're starting a new project
|
|
241
|
+
- You want better TypeScript integration
|
|
242
|
+
- You're building a modern Next.js application
|
|
243
|
+
- You want co-located translations with components
|
|
244
|
+
- You want better developer experience and maintainability
|
|
245
|
+
- You're working in a monorepo or multi-app architecture
|
|
246
|
+
|
|
247
|
+
### Use Both Together if:
|
|
248
|
+
|
|
249
|
+
- You're migrating a large existing codebase gradually
|
|
250
|
+
- You want to leverage Intlayer's content management while maintaining i18next compatibility
|
|
251
|
+
- You need to support legacy code while adopting modern patterns
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Step-by-Step Guide: Using Intlayer with i18next
|
|
256
|
+
|
|
257
|
+
This guide will show you how to set up a Next.js application with Intlayer, with optional i18next integration for backward compatibility or gradual migration.
|
|
258
|
+
|
|
259
|
+
### Step 1: Install Dependencies
|
|
40
260
|
|
|
41
|
-
|
|
261
|
+
Install the necessary packages for Intlayer:
|
|
42
262
|
|
|
43
|
-
|
|
263
|
+
```bash packageManager="npm"
|
|
264
|
+
npm install intlayer next-intlayer
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
```bash packageManager="pnpm"
|
|
268
|
+
pnpm add intlayer next-intlayer
|
|
269
|
+
```
|
|
44
270
|
|
|
45
|
-
|
|
271
|
+
```bash packageManager="yarn"
|
|
272
|
+
yarn add intlayer next-intlayer
|
|
273
|
+
```
|
|
46
274
|
|
|
47
|
-
|
|
275
|
+
If you plan to use i18next alongside Intlayer (for migration or compatibility):
|
|
48
276
|
|
|
49
|
-
|
|
277
|
+
```bash packageManager="npm"
|
|
278
|
+
npm install i18next react-i18next i18next-resources-to-backend @intlayer/sync-json-plugin
|
|
279
|
+
```
|
|
50
280
|
|
|
51
|
-
|
|
281
|
+
```bash packageManager="pnpm"
|
|
282
|
+
pnpm add i18next react-i18next i18next-resources-to-backend @intlayer/sync-json-plugin
|
|
283
|
+
```
|
|
52
284
|
|
|
53
|
-
|
|
285
|
+
```bash packageManager="yarn"
|
|
286
|
+
yarn add i18next react-i18next i18next-resources-to-backend @intlayer/sync-json-plugin
|
|
287
|
+
```
|
|
54
288
|
|
|
55
|
-
|
|
289
|
+
**What each package does:**
|
|
56
290
|
|
|
57
|
-
|
|
291
|
+
- **intlayer**: Core package providing internationalization tools for configuration management, translation, content declaration, transpilation, and CLI commands.
|
|
292
|
+
- **next-intlayer**: Integration package for Next.js with context providers, hooks, and plugins for Webpack/Turbopack.
|
|
293
|
+
- **i18next** (optional): The core i18next internationalization framework.
|
|
294
|
+
- **react-i18next** (optional): React bindings for i18next.
|
|
295
|
+
- **i18next-resources-to-backend** (optional): Dynamically imports i18next resources.
|
|
296
|
+
- **@intlayer/sync-json-plugin** (optional): Plugin to export Intlayer dictionaries as JSON files compatible with i18next.
|
|
58
297
|
|
|
59
|
-
|
|
298
|
+
### Step 2: Configure Your Project
|
|
60
299
|
|
|
61
|
-
|
|
300
|
+
Create an Intlayer configuration file to define your supported locales:
|
|
62
301
|
|
|
63
302
|
```typescript fileName="intlayer.config.ts" codeFormat="typescript"
|
|
64
303
|
import { Locales, type IntlayerConfig } from "intlayer";
|
|
65
304
|
|
|
66
305
|
const config: IntlayerConfig = {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
306
|
+
internationalization: {
|
|
307
|
+
locales: [
|
|
308
|
+
Locales.ENGLISH,
|
|
309
|
+
Locales.FRENCH,
|
|
310
|
+
Locales.SPANISH,
|
|
311
|
+
// Add your other locales
|
|
312
|
+
],
|
|
313
|
+
defaultLocale: Locales.ENGLISH,
|
|
72
314
|
},
|
|
73
315
|
};
|
|
74
316
|
|
|
@@ -80,11 +322,14 @@ import { Locales } from "intlayer";
|
|
|
80
322
|
|
|
81
323
|
/** @type {import('intlayer').IntlayerConfig} */
|
|
82
324
|
const config = {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
325
|
+
internationalization: {
|
|
326
|
+
locales: [
|
|
327
|
+
Locales.ENGLISH,
|
|
328
|
+
Locales.FRENCH,
|
|
329
|
+
Locales.SPANISH,
|
|
330
|
+
// Add your other locales
|
|
331
|
+
],
|
|
332
|
+
defaultLocale: Locales.ENGLISH,
|
|
88
333
|
},
|
|
89
334
|
};
|
|
90
335
|
|
|
@@ -96,67 +341,1388 @@ const { Locales } = require("intlayer");
|
|
|
96
341
|
|
|
97
342
|
/** @type {import('intlayer').IntlayerConfig} */
|
|
98
343
|
const config = {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
344
|
+
internationalization: {
|
|
345
|
+
locales: [
|
|
346
|
+
Locales.ENGLISH,
|
|
347
|
+
Locales.FRENCH,
|
|
348
|
+
Locales.SPANISH,
|
|
349
|
+
// Add your other locales
|
|
350
|
+
],
|
|
351
|
+
defaultLocale: Locales.ENGLISH,
|
|
104
352
|
},
|
|
105
353
|
};
|
|
106
354
|
|
|
107
355
|
module.exports = config;
|
|
108
356
|
```
|
|
109
357
|
|
|
110
|
-
|
|
358
|
+
**If you want to also export JSON dictionaries for i18next**, add the `syncJSON` plugin:
|
|
111
359
|
|
|
112
|
-
|
|
360
|
+
```typescript fileName="intlayer.config.ts" codeFormat="typescript"
|
|
361
|
+
import { Locales, type IntlayerConfig } from "intlayer";
|
|
362
|
+
import { syncJSON } from "@intlayer/sync-json-plugin";
|
|
113
363
|
|
|
114
|
-
|
|
364
|
+
const config: IntlayerConfig = {
|
|
365
|
+
internationalization: {
|
|
366
|
+
locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
|
|
367
|
+
defaultLocale: Locales.ENGLISH,
|
|
368
|
+
},
|
|
369
|
+
plugins: [
|
|
370
|
+
syncJSON({
|
|
371
|
+
source: ({ key, locale }) => `./intl/messages/${locale}/${key}.json`,
|
|
372
|
+
}),
|
|
373
|
+
],
|
|
374
|
+
};
|
|
115
375
|
|
|
116
|
-
|
|
117
|
-
|
|
376
|
+
export default config;
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
```javascript fileName="intlayer.config.mjs" codeFormat="esm"
|
|
380
|
+
import { Locales } from "intlayer";
|
|
381
|
+
import { syncJSON } from "@intlayer/sync-json-plugin";
|
|
382
|
+
|
|
383
|
+
/** @type {import('intlayer').IntlayerConfig} */
|
|
384
|
+
const config = {
|
|
385
|
+
internationalization: {
|
|
386
|
+
locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
|
|
387
|
+
defaultLocale: Locales.ENGLISH,
|
|
388
|
+
},
|
|
389
|
+
plugins: [
|
|
390
|
+
syncJSON({
|
|
391
|
+
source: ({ key, locale }) => `./intl/messages/${locale}/${key}.json`,
|
|
392
|
+
}),
|
|
393
|
+
],
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
export default config;
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
```javascript fileName="intlayer.config.cjs" codeFormat="commonjs"
|
|
400
|
+
const { Locales } = require("intlayer");
|
|
401
|
+
const { syncJSON } = require("@intlayer/sync-json-plugin");
|
|
402
|
+
|
|
403
|
+
/** @type {import('intlayer').IntlayerConfig} */
|
|
404
|
+
const config = {
|
|
405
|
+
internationalization: {
|
|
406
|
+
locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
|
|
407
|
+
defaultLocale: Locales.ENGLISH,
|
|
408
|
+
},
|
|
409
|
+
plugins: [
|
|
410
|
+
syncJSON({
|
|
411
|
+
source: ({ key, locale }) => `./intl/messages/${locale}/${key}.json`,
|
|
412
|
+
}),
|
|
413
|
+
],
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
module.exports = config;
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
> The `syncJSON` plugin will automatically generate JSON files compatible with i18next whenever you build your Intlayer dictionaries.
|
|
420
|
+
|
|
421
|
+
> **Important Note**: The exportation of i18next dictionaries is currently in beta and does not ensure a 1:1 compatibility with all i18next features. It is recommended to use Intlayer natively for the best experience, using i18next export only for gradual migration scenarios.
|
|
422
|
+
|
|
423
|
+
For a complete list of available configuration parameters, refer to the [configuration documentation](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/configuration.md).
|
|
424
|
+
|
|
425
|
+
### Step 3: Integrate Intlayer in Your Next.js Configuration
|
|
426
|
+
|
|
427
|
+
Configure your Next.js setup to use Intlayer:
|
|
428
|
+
|
|
429
|
+
```typescript fileName="next.config.ts" codeFormat="typescript"
|
|
430
|
+
import type { NextConfig } from "next";
|
|
431
|
+
import { withIntlayer } from "next-intlayer/server";
|
|
432
|
+
|
|
433
|
+
const nextConfig: NextConfig = {
|
|
434
|
+
/* your config options here */
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
export default withIntlayer(nextConfig);
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
```typescript fileName="next.config.mjs" codeFormat="esm"
|
|
441
|
+
import { withIntlayer } from "next-intlayer/server";
|
|
442
|
+
|
|
443
|
+
/** @type {import('next').NextConfig} */
|
|
444
|
+
const nextConfig = {
|
|
445
|
+
/* your config options here */
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
export default withIntlayer(nextConfig);
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
```typescript fileName="next.config.cjs" codeFormat="commonjs"
|
|
452
|
+
const { withIntlayer } = require("next-intlayer/server");
|
|
453
|
+
|
|
454
|
+
/** @type {import('next').NextConfig} */
|
|
455
|
+
const nextConfig = {
|
|
456
|
+
/* your config options here */
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
module.exports = withIntlayer(nextConfig);
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
> The `withIntlayer()` plugin integrates Intlayer with Next.js, building content declaration files, monitoring them in development mode, and providing optimizations for Webpack or Turbopack. It's compatible with both Server and Client Components.
|
|
463
|
+
|
|
464
|
+
### Step 4: Define Dynamic Locale Routes
|
|
465
|
+
|
|
466
|
+
Set up your Next.js application to handle dynamic locale routing.
|
|
467
|
+
|
|
468
|
+
First, update your root layout to remove the `<html>` and `<body>` tags:
|
|
469
|
+
|
|
470
|
+
```tsx fileName="src/app/layout.tsx" codeFormat="typescript"
|
|
471
|
+
import type { PropsWithChildren, FC } from "react";
|
|
472
|
+
import "./globals.css";
|
|
473
|
+
|
|
474
|
+
const RootLayout: FC<PropsWithChildren> = ({ children }) => (
|
|
475
|
+
// You can still wrap the children with other providers
|
|
476
|
+
<>{children}</>
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
export default RootLayout;
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
```jsx fileName="src/app/layout.mjx" codeFormat="esm"
|
|
483
|
+
import "./globals.css";
|
|
484
|
+
|
|
485
|
+
const RootLayout = ({ children }) => (
|
|
486
|
+
// You can still wrap the children with other providers
|
|
487
|
+
<>{children}</>
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
export default RootLayout;
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
```jsx fileName="src/app/layout.csx" codeFormat="commonjs"
|
|
494
|
+
require("./globals.css");
|
|
495
|
+
|
|
496
|
+
const RootLayout = ({ children }) => (
|
|
497
|
+
// You can still wrap the children with other providers
|
|
498
|
+
<>{children}</>
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
module.exports = {
|
|
502
|
+
default: RootLayout,
|
|
503
|
+
};
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
> Keeping the `RootLayout` component empty allows you to set the [`lang`](https://developer.mozilla.org/fr/docs/Web/HTML/Global_attributes/lang) and [`dir`](https://developer.mozilla.org/fr/docs/Web/HTML/Global_attributes/dir) attributes on the `<html>` tag in the locale-specific layout.
|
|
507
|
+
|
|
508
|
+
Next, create a locale-specific layout in `[locale]` directory:
|
|
509
|
+
|
|
510
|
+
```tsx fileName="src/app/[locale]/layout.tsx" codeFormat="typescript"
|
|
511
|
+
import type { NextLayoutIntlayer } from "next-intlayer";
|
|
512
|
+
import { Inter } from "next/font/google";
|
|
513
|
+
import { getHTMLTextDir } from "intlayer";
|
|
514
|
+
|
|
515
|
+
const inter = Inter({ subsets: ["latin"] });
|
|
516
|
+
|
|
517
|
+
const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
|
|
518
|
+
const { locale } = await params;
|
|
519
|
+
return (
|
|
520
|
+
<html lang={locale} dir={getHTMLTextDir(locale)}>
|
|
521
|
+
<body className={inter.className}>{children}</body>
|
|
522
|
+
</html>
|
|
523
|
+
);
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
export default LocaleLayout;
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
```jsx fileName="src/app/[locale]/layout.mjx" codeFormat="esm"
|
|
530
|
+
import { Inter } from "next/font/google";
|
|
531
|
+
import { getHTMLTextDir } from "intlayer";
|
|
532
|
+
|
|
533
|
+
const inter = Inter({ subsets: ["latin"] });
|
|
534
|
+
|
|
535
|
+
const LocaleLayout = async ({ children, params }) => {
|
|
536
|
+
const { locale } = await params;
|
|
537
|
+
return (
|
|
538
|
+
<html lang={locale} dir={getHTMLTextDir(locale)}>
|
|
539
|
+
<body className={inter.className}>{children}</body>
|
|
540
|
+
</html>
|
|
541
|
+
);
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
export default LocaleLayout;
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
```jsx fileName="src/app/[locale]/layout.csx" codeFormat="commonjs"
|
|
548
|
+
const { Inter } = require("next/font/google");
|
|
549
|
+
const { getHTMLTextDir } = require("intlayer");
|
|
550
|
+
|
|
551
|
+
const inter = Inter({ subsets: ["latin"] });
|
|
552
|
+
|
|
553
|
+
const LocaleLayout = async ({ children, params }) => {
|
|
554
|
+
const { locale } = await params;
|
|
555
|
+
return (
|
|
556
|
+
<html lang={locale} dir={getHTMLTextDir(locale)}>
|
|
557
|
+
<body className={inter.className}>{children}</body>
|
|
558
|
+
</html>
|
|
559
|
+
);
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
module.exports = LocaleLayout;
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Then, add the `generateStaticParams` function to pre-generate pages for all locales:
|
|
566
|
+
|
|
567
|
+
```tsx fileName="src/app/[locale]/layout.tsx" codeFormat="typescript"
|
|
568
|
+
export { generateStaticParams } from "next-intlayer"; // Line to insert
|
|
569
|
+
|
|
570
|
+
const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
|
|
571
|
+
/* ... Rest of the code */
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
export default LocaleLayout;
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
```jsx fileName="src/app/[locale]/layout.mjx" codeFormat="esm"
|
|
578
|
+
export { generateStaticParams } from "next-intlayer"; // Line to insert
|
|
579
|
+
|
|
580
|
+
const LocaleLayout = async ({ children, params }) => {
|
|
581
|
+
/* ... Rest of the code */
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
export default LocaleLayout;
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
```jsx fileName="src/app/[locale]/layout.csx" codeFormat="commonjs"
|
|
588
|
+
const { generateStaticParams } = require("next-intlayer"); // Line to insert
|
|
589
|
+
|
|
590
|
+
const LocaleLayout = async ({ children, params }) => {
|
|
591
|
+
/* ... Rest of the code */
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
module.exports = { default: LocaleLayout, generateStaticParams };
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
> The `[locale]` path segment is used to define the locale. Example: `/en/about` refers to `en` and `/fr/about` refers to `fr`.
|
|
598
|
+
|
|
599
|
+
> The `generateStaticParams` function ensures your application pre-builds the necessary pages for all locales, reducing runtime computation and improving user experience.
|
|
600
|
+
|
|
601
|
+
### Step 5: Declare Your Content
|
|
602
|
+
|
|
603
|
+
Create and manage your content declarations. This is where Intlayer truly shines compared to i18next.
|
|
604
|
+
|
|
605
|
+
Create content files next to your components:
|
|
606
|
+
|
|
607
|
+
```tsx fileName="src/app/[locale]/page.content.ts" contentDeclarationFormat="typescript"
|
|
608
|
+
import { t, type Dictionary } from "intlayer";
|
|
609
|
+
|
|
610
|
+
const pageContent = {
|
|
611
|
+
key: "page",
|
|
612
|
+
content: {
|
|
613
|
+
getStarted: {
|
|
614
|
+
main: t({
|
|
615
|
+
en: "Get started by editing",
|
|
616
|
+
fr: "Commencez par éditer",
|
|
617
|
+
es: "Comience por editar",
|
|
618
|
+
}),
|
|
619
|
+
pageLink: "src/app/page.tsx",
|
|
620
|
+
},
|
|
621
|
+
docs: {
|
|
622
|
+
title: t({
|
|
623
|
+
en: "Documentation",
|
|
624
|
+
fr: "Documentation",
|
|
625
|
+
es: "Documentación",
|
|
626
|
+
}),
|
|
627
|
+
description: t({
|
|
628
|
+
en: "Find in-depth information about Next.js features and API.",
|
|
629
|
+
fr: "Trouvez des informations détaillées sur les fonctionnalités et l'API de Next.js.",
|
|
630
|
+
es: "Encuentre información detallada sobre las características y la API de Next.js.",
|
|
631
|
+
}),
|
|
632
|
+
},
|
|
633
|
+
},
|
|
634
|
+
} satisfies Dictionary;
|
|
635
|
+
|
|
636
|
+
export default pageContent;
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
```javascript fileName="src/app/[locale]/page.content.mjs" contentDeclarationFormat="esm"
|
|
640
|
+
import { t } from "intlayer";
|
|
641
|
+
|
|
642
|
+
/** @type {import('intlayer').Dictionary} */
|
|
643
|
+
const pageContent = {
|
|
644
|
+
key: "page",
|
|
645
|
+
content: {
|
|
646
|
+
getStarted: {
|
|
647
|
+
main: t({
|
|
648
|
+
en: "Get started by editing",
|
|
649
|
+
fr: "Commencez par éditer",
|
|
650
|
+
es: "Comience por editar",
|
|
651
|
+
}),
|
|
652
|
+
pageLink: "src/app/page.tsx",
|
|
653
|
+
},
|
|
654
|
+
docs: {
|
|
655
|
+
title: t({
|
|
656
|
+
en: "Documentation",
|
|
657
|
+
fr: "Documentation",
|
|
658
|
+
es: "Documentación",
|
|
659
|
+
}),
|
|
660
|
+
description: t({
|
|
661
|
+
en: "Find in-depth information about Next.js features and API.",
|
|
662
|
+
fr: "Trouvez des informations détaillées sur les fonctionnalités et l'API de Next.js.",
|
|
663
|
+
es: "Encuentre información detallada sobre las características y la API de Next.js.",
|
|
664
|
+
}),
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
export default pageContent;
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
```javascript fileName="src/app/[locale]/page.content.cjs" contentDeclarationFormat="commonjs"
|
|
673
|
+
const { t } = require("intlayer");
|
|
674
|
+
|
|
675
|
+
/** @type {import('intlayer').Dictionary} */
|
|
676
|
+
const pageContent = {
|
|
677
|
+
key: "page",
|
|
678
|
+
content: {
|
|
679
|
+
getStarted: {
|
|
680
|
+
main: t({
|
|
681
|
+
en: "Get started by editing",
|
|
682
|
+
fr: "Commencez par éditer",
|
|
683
|
+
es: "Comience por editar",
|
|
684
|
+
}),
|
|
685
|
+
pageLink: "src/app/page.tsx",
|
|
686
|
+
},
|
|
687
|
+
docs: {
|
|
688
|
+
title: t({
|
|
689
|
+
en: "Documentation",
|
|
690
|
+
fr: "Documentation",
|
|
691
|
+
es: "Documentación",
|
|
692
|
+
}),
|
|
693
|
+
description: t({
|
|
694
|
+
en: "Find in-depth information about Next.js features and API.",
|
|
695
|
+
fr: "Trouvez des informations détaillées sur les fonctionnalités et l'API de Next.js.",
|
|
696
|
+
es: "Encuentre información detallada sobre las características y la API de Next.js.",
|
|
697
|
+
}),
|
|
698
|
+
},
|
|
699
|
+
},
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
module.exports = pageContent;
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
```json fileName="src/app/[locale]/page.content.json" contentDeclarationFormat="json"
|
|
706
|
+
{
|
|
707
|
+
"$schema": "https://intlayer.org/schema.json",
|
|
708
|
+
"key": "page",
|
|
709
|
+
"content": {
|
|
710
|
+
"getStarted": {
|
|
711
|
+
"main": {
|
|
712
|
+
"nodeType": "translation",
|
|
713
|
+
"translation": {
|
|
714
|
+
"en": "Get started by editing",
|
|
715
|
+
"fr": "Commencez par éditer",
|
|
716
|
+
"es": "Comience por editar"
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
"pageLink": "src/app/page.tsx"
|
|
720
|
+
},
|
|
721
|
+
"docs": {
|
|
722
|
+
"title": {
|
|
723
|
+
"nodeType": "translation",
|
|
724
|
+
"translation": {
|
|
725
|
+
"en": "Documentation",
|
|
726
|
+
"fr": "Documentation",
|
|
727
|
+
"es": "Documentación"
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
"description": {
|
|
731
|
+
"nodeType": "translation",
|
|
732
|
+
"translation": {
|
|
733
|
+
"en": "Find in-depth information about Next.js features and API.",
|
|
734
|
+
"fr": "Trouvez des informations détaillées sur les fonctionnalités et l'API de Next.js.",
|
|
735
|
+
"es": "Encuentre información detallada sobre las características y la API de Next.js."
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
> **Intlayer vs. i18next**: With i18next, you would need to create separate JSON files in a `locales/` folder. With Intlayer, content lives right next to your component, making it easier to maintain and refactor.
|
|
744
|
+
|
|
745
|
+
> Content declarations can be defined anywhere in your application as long as they are included in the `contentDir` directory (by default, `./src`). And match the content declaration file extension (by default, `.content.{json,ts,tsx,js,jsx,mjs,mjx,cjs,cjx}`).
|
|
746
|
+
|
|
747
|
+
For more details, refer to the [content declaration documentation](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/content_file.md).
|
|
748
|
+
|
|
749
|
+
### Step 6: Utilize Content in Your Code
|
|
750
|
+
|
|
751
|
+
Access your content dictionaries throughout your application using the `useIntlayer` hook:
|
|
752
|
+
|
|
753
|
+
```tsx fileName="src/app/[locale]/page.tsx" codeFormat="typescript"
|
|
754
|
+
import type { FC } from "react";
|
|
755
|
+
import { ClientComponentExample } from "@components/ClientComponentExample";
|
|
756
|
+
import { ServerComponentExample } from "@components/ServerComponentExample";
|
|
757
|
+
import { type NextPageIntlayer, IntlayerClientProvider } from "next-intlayer";
|
|
758
|
+
import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
|
|
759
|
+
|
|
760
|
+
const PageContent: FC = () => {
|
|
761
|
+
const content = useIntlayer("page");
|
|
762
|
+
|
|
763
|
+
return (
|
|
764
|
+
<>
|
|
765
|
+
<h1>{content.getStarted.main}</h1>
|
|
766
|
+
<code>{content.getStarted.pageLink}</code>
|
|
767
|
+
|
|
768
|
+
<h2>{content.docs.title}</h2>
|
|
769
|
+
<p>{content.docs.description}</p>
|
|
770
|
+
</>
|
|
771
|
+
);
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
const Page: NextPageIntlayer = async ({ params }) => {
|
|
775
|
+
const { locale } = await params;
|
|
776
|
+
|
|
777
|
+
return (
|
|
778
|
+
<IntlayerServerProvider locale={locale}>
|
|
779
|
+
<PageContent />
|
|
780
|
+
<ServerComponentExample />
|
|
781
|
+
|
|
782
|
+
<IntlayerClientProvider locale={locale}>
|
|
783
|
+
<ClientComponentExample />
|
|
784
|
+
</IntlayerClientProvider>
|
|
785
|
+
</IntlayerServerProvider>
|
|
786
|
+
);
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
export default Page;
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
```jsx fileName="src/app/[locale]/page.mjx" codeFormat="esm"
|
|
793
|
+
import { ClientComponentExample } from "@components/ClientComponentExample";
|
|
794
|
+
import { ServerComponentExample } from "@components/ServerComponentExample";
|
|
795
|
+
import { IntlayerClientProvider } from "next-intlayer";
|
|
796
|
+
import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
|
|
797
|
+
|
|
798
|
+
const PageContent = () => {
|
|
799
|
+
const content = useIntlayer("page");
|
|
800
|
+
|
|
801
|
+
return (
|
|
802
|
+
<>
|
|
803
|
+
<h1>{content.getStarted.main}</h1>
|
|
804
|
+
<code>{content.getStarted.pageLink}</code>
|
|
805
|
+
|
|
806
|
+
<h2>{content.docs.title}</h2>
|
|
807
|
+
<p>{content.docs.description}</p>
|
|
808
|
+
</>
|
|
809
|
+
);
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
const Page = async ({ params }) => {
|
|
813
|
+
const { locale } = await params;
|
|
814
|
+
|
|
815
|
+
return (
|
|
816
|
+
<IntlayerServerProvider locale={locale}>
|
|
817
|
+
<PageContent />
|
|
818
|
+
<ServerComponentExample />
|
|
819
|
+
|
|
820
|
+
<IntlayerClientProvider locale={locale}>
|
|
821
|
+
<ClientComponentExample />
|
|
822
|
+
</IntlayerClientProvider>
|
|
823
|
+
</IntlayerServerProvider>
|
|
824
|
+
);
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
export default Page;
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
```jsx fileName="src/app/[locale]/page.csx" codeFormat="commonjs"
|
|
831
|
+
const {
|
|
832
|
+
ClientComponentExample,
|
|
833
|
+
} = require("@components/ClientComponentExample");
|
|
834
|
+
const {
|
|
835
|
+
ServerComponentExample,
|
|
836
|
+
} = require("@components/ServerComponentExample");
|
|
837
|
+
const { IntlayerClientProvider } = require("next-intlayer");
|
|
838
|
+
const { IntlayerServerProvider, useIntlayer } = require("next-intlayer/server");
|
|
839
|
+
|
|
840
|
+
const PageContent = () => {
|
|
841
|
+
const content = useIntlayer("page");
|
|
842
|
+
|
|
843
|
+
return (
|
|
844
|
+
<>
|
|
845
|
+
<h1>{content.getStarted.main}</h1>
|
|
846
|
+
<code>{content.getStarted.pageLink}</code>
|
|
847
|
+
|
|
848
|
+
<h2>{content.docs.title}</h2>
|
|
849
|
+
<p>{content.docs.description}</p>
|
|
850
|
+
</>
|
|
851
|
+
);
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
const Page = async ({ params }) => {
|
|
855
|
+
const { locale } = await params;
|
|
118
856
|
|
|
857
|
+
return (
|
|
858
|
+
<IntlayerServerProvider locale={locale}>
|
|
859
|
+
<PageContent />
|
|
860
|
+
<ServerComponentExample />
|
|
861
|
+
|
|
862
|
+
<IntlayerClientProvider locale={locale}>
|
|
863
|
+
<ClientComponentExample />
|
|
864
|
+
</IntlayerClientProvider>
|
|
865
|
+
</IntlayerServerProvider>
|
|
866
|
+
);
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
module.exports = Page;
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
**Key points:**
|
|
873
|
+
|
|
874
|
+
- **`IntlayerClientProvider`**: Provides the locale to client-side components. Can be placed in any parent component, but recommended in layouts for efficiency.
|
|
875
|
+
- **`IntlayerServerProvider`**: Provides the locale to server children. Cannot be set in the layout due to React's cache mechanism.
|
|
876
|
+
- **`useIntlayer("page")`**: Retrieves the content for the `page` dictionary key with full TypeScript support.
|
|
877
|
+
|
|
878
|
+
**Client Component Example:**
|
|
879
|
+
|
|
880
|
+
```tsx fileName="src/components/ClientComponentExample.tsx" codeFormat="typescript"
|
|
881
|
+
"use client";
|
|
882
|
+
|
|
883
|
+
import type { FC } from "react";
|
|
884
|
+
import { useIntlayer } from "next-intlayer";
|
|
885
|
+
|
|
886
|
+
export const ClientComponentExample: FC = () => {
|
|
887
|
+
const content = useIntlayer("client-component-example");
|
|
888
|
+
|
|
889
|
+
return (
|
|
890
|
+
<div>
|
|
891
|
+
<h2>{content.title}</h2>
|
|
892
|
+
<p>{content.description}</p>
|
|
893
|
+
</div>
|
|
894
|
+
);
|
|
895
|
+
};
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
```jsx fileName="src/components/ClientComponentExample.mjx" codeFormat="esm"
|
|
899
|
+
"use client";
|
|
900
|
+
|
|
901
|
+
import { useIntlayer } from "next-intlayer";
|
|
902
|
+
|
|
903
|
+
export const ClientComponentExample = () => {
|
|
904
|
+
const content = useIntlayer("client-component-example");
|
|
905
|
+
|
|
906
|
+
return (
|
|
907
|
+
<div>
|
|
908
|
+
<h2>{content.title}</h2>
|
|
909
|
+
<p>{content.description}</p>
|
|
910
|
+
</div>
|
|
911
|
+
);
|
|
912
|
+
};
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
```jsx fileName="src/components/ClientComponentExample.csx" codeFormat="commonjs"
|
|
916
|
+
"use client";
|
|
917
|
+
|
|
918
|
+
const { useIntlayer } = require("next-intlayer");
|
|
919
|
+
|
|
920
|
+
const ClientComponentExample = () => {
|
|
921
|
+
const content = useIntlayer("client-component-example");
|
|
922
|
+
|
|
923
|
+
return (
|
|
924
|
+
<div>
|
|
925
|
+
<h2>{content.title}</h2>
|
|
926
|
+
<p>{content.description}</p>
|
|
927
|
+
</div>
|
|
928
|
+
);
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
exports.ClientComponentExample = ClientComponentExample;
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
**Server Component Example:**
|
|
935
|
+
|
|
936
|
+
```tsx fileName="src/components/ServerComponentExample.tsx" codeFormat="typescript"
|
|
937
|
+
import type { FC } from "react";
|
|
938
|
+
import { useIntlayer } from "next-intlayer/server";
|
|
939
|
+
|
|
940
|
+
export const ServerComponentExample: FC = () => {
|
|
941
|
+
const content = useIntlayer("server-component-example");
|
|
942
|
+
|
|
943
|
+
return (
|
|
944
|
+
<div>
|
|
945
|
+
<h2>{content.title}</h2>
|
|
946
|
+
<p>{content.description}</p>
|
|
947
|
+
</div>
|
|
948
|
+
);
|
|
949
|
+
};
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
```jsx fileName="src/components/ServerComponentExample.mjx" codeFormat="esm"
|
|
953
|
+
import { useIntlayer } from "next-intlayer/server";
|
|
954
|
+
|
|
955
|
+
export const ServerComponentExample = () => {
|
|
956
|
+
const content = useIntlayer("server-component-example");
|
|
957
|
+
|
|
958
|
+
return (
|
|
959
|
+
<div>
|
|
960
|
+
<h2>{content.title}</h2>
|
|
961
|
+
<p>{content.description}</p>
|
|
962
|
+
</div>
|
|
963
|
+
);
|
|
964
|
+
};
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
```jsx fileName="src/components/ServerComponentExample.csx" codeFormat="commonjs"
|
|
968
|
+
const { useIntlayer } = require("next-intlayer/server");
|
|
969
|
+
|
|
970
|
+
const ServerComponentExample = () => {
|
|
971
|
+
const content = useIntlayer("server-component-example");
|
|
972
|
+
|
|
973
|
+
return (
|
|
974
|
+
<div>
|
|
975
|
+
<h2>{content.title}</h2>
|
|
976
|
+
<p>{content.description}</p>
|
|
977
|
+
</div>
|
|
978
|
+
);
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
exports.ServerComponentExample = ServerComponentExample;
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
> If you want to use your content in a `string` attribute, such as `alt`, `title`, `href`, `aria-label`, etc., you must call the value of the function:
|
|
985
|
+
>
|
|
986
|
+
> ```jsx
|
|
987
|
+
> <img src={content.image.src.value} alt={content.image.alt.value} />
|
|
988
|
+
> ```
|
|
989
|
+
|
|
990
|
+
> To learn more about the `useIntlayer` hook, refer to the [documentation](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/packages/next-intlayer/useIntlayer.md).
|
|
991
|
+
|
|
992
|
+
### (Optional) Step 7: Configure Proxy for Locale Detection
|
|
993
|
+
|
|
994
|
+
Set up proxy to automatically detect and redirect users to their preferred locale:
|
|
995
|
+
|
|
996
|
+
```typescript fileName="src/proxy.ts" codeFormat="typescript"
|
|
997
|
+
export { intlayerProxy as proxy } from "next-intlayer/proxy";
|
|
998
|
+
|
|
999
|
+
export const config = {
|
|
1000
|
+
matcher:
|
|
1001
|
+
"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
|
|
1002
|
+
};
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
```javascript fileName="src/proxy.mjs" codeFormat="esm"
|
|
1006
|
+
export { intlayerProxy as proxy } from "next-intlayer/proxy";
|
|
1007
|
+
|
|
1008
|
+
export const config = {
|
|
1009
|
+
matcher:
|
|
1010
|
+
"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
|
|
1011
|
+
};
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
```javascript fileName="src/proxy.cjs" codeFormat="commonjs"
|
|
1015
|
+
const { intlayerProxy } = require("next-intlayer/proxy");
|
|
1016
|
+
|
|
1017
|
+
const config = {
|
|
1018
|
+
matcher:
|
|
1019
|
+
"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
module.exports = { proxy: intlayerProxy, config };
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
> The `intlayerProxy` detects the user's preferred locale from browser headers and cookies, then redirects them to the appropriate URL. It also saves the preference in a cookie for future visits.
|
|
1026
|
+
|
|
1027
|
+
### (Optional) Step 8: Internationalization of Your Metadata
|
|
1028
|
+
|
|
1029
|
+
To internationalize metadata (page titles, descriptions, etc.), use the `generateMetadata` function with `getIntlayer`:
|
|
1030
|
+
|
|
1031
|
+
```typescript fileName="src/app/[locale]/metadata.content.ts" contentDeclarationFormat="typescript"
|
|
1032
|
+
import { type Dictionary, t } from "intlayer";
|
|
1033
|
+
import type { Metadata } from "next";
|
|
1034
|
+
|
|
1035
|
+
const metadataContent = {
|
|
1036
|
+
key: "page-metadata",
|
|
1037
|
+
content: {
|
|
1038
|
+
title: t({
|
|
1039
|
+
en: "My Website - Home",
|
|
1040
|
+
fr: "Mon Site Web - Accueil",
|
|
1041
|
+
es: "Mi Sitio Web - Inicio",
|
|
1042
|
+
}),
|
|
1043
|
+
description: t({
|
|
1044
|
+
en: "Welcome to my multilingual website built with Intlayer and Next.js",
|
|
1045
|
+
fr: "Bienvenue sur mon site web multilingue construit avec Intlayer et Next.js",
|
|
1046
|
+
es: "Bienvenido a mi sitio web multilingüe construido con Intlayer y Next.js",
|
|
1047
|
+
}),
|
|
1048
|
+
},
|
|
1049
|
+
} satisfies Dictionary<Metadata>;
|
|
1050
|
+
|
|
1051
|
+
export default metadataContent;
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
```javascript fileName="src/app/[locale]/metadata.content.mjs" contentDeclarationFormat="esm"
|
|
1055
|
+
import { t } from "intlayer";
|
|
1056
|
+
|
|
1057
|
+
/** @type {import('intlayer').Dictionary<import('next').Metadata>} */
|
|
1058
|
+
const metadataContent = {
|
|
1059
|
+
key: "page-metadata",
|
|
1060
|
+
content: {
|
|
1061
|
+
title: t({
|
|
1062
|
+
en: "My Website - Home",
|
|
1063
|
+
fr: "Mon Site Web - Accueil",
|
|
1064
|
+
es: "Mi Sitio Web - Inicio",
|
|
1065
|
+
}),
|
|
1066
|
+
description: t({
|
|
1067
|
+
en: "Welcome to my multilingual website built with Intlayer and Next.js",
|
|
1068
|
+
fr: "Bienvenue sur mon site web multilingue construit avec Intlayer et Next.js",
|
|
1069
|
+
es: "Bienvenido a mi sitio web multilingüe construido con Intlayer y Next.js",
|
|
1070
|
+
}),
|
|
1071
|
+
},
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
export default metadataContent;
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
```javascript fileName="src/app/[locale]/metadata.content.cjs" contentDeclarationFormat="commonjs"
|
|
1078
|
+
const { t } = require("intlayer");
|
|
1079
|
+
|
|
1080
|
+
/** @type {import('intlayer').Dictionary<import('next').Metadata>} */
|
|
1081
|
+
const metadataContent = {
|
|
1082
|
+
key: "page-metadata",
|
|
1083
|
+
content: {
|
|
1084
|
+
title: t({
|
|
1085
|
+
en: "My Website - Home",
|
|
1086
|
+
fr: "Mon Site Web - Accueil",
|
|
1087
|
+
es: "Mi Sitio Web - Inicio",
|
|
1088
|
+
}),
|
|
1089
|
+
description: t({
|
|
1090
|
+
en: "Welcome to my multilingual website built with Intlayer and Next.js",
|
|
1091
|
+
fr: "Bienvenue sur mon site web multilingue construit avec Intlayer et Next.js",
|
|
1092
|
+
es: "Bienvenido a mi sitio web multilingüe construido con Intlayer y Next.js",
|
|
1093
|
+
}),
|
|
1094
|
+
},
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
module.exports = metadataContent;
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
```json fileName="src/app/[locale]/metadata.content.json" contentDeclarationFormat="json"
|
|
1101
|
+
{
|
|
1102
|
+
"$schema": "https://intlayer.org/schema.json",
|
|
1103
|
+
"key": "page-metadata",
|
|
1104
|
+
"content": {
|
|
1105
|
+
"title": {
|
|
1106
|
+
"nodeType": "translation",
|
|
1107
|
+
"translation": {
|
|
1108
|
+
"en": "My Website - Home",
|
|
1109
|
+
"fr": "Mon Site Web - Accueil",
|
|
1110
|
+
"es": "Mi Sitio Web - Inicio"
|
|
1111
|
+
}
|
|
1112
|
+
},
|
|
1113
|
+
"description": {
|
|
1114
|
+
"nodeType": "translation",
|
|
1115
|
+
"translation": {
|
|
1116
|
+
"en": "Welcome to my multilingual website built with Intlayer and Next.js",
|
|
1117
|
+
"fr": "Bienvenue sur mon site web multilingue construit avec Intlayer et Next.js",
|
|
1118
|
+
"es": "Bienvenido a mi sitio web multilingüe construido con Intlayer y Next.js"
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
```
|
|
1124
|
+
|
|
1125
|
+
Then use it in your layout or page:
|
|
1126
|
+
|
|
1127
|
+
```typescript fileName="src/app/[locale]/layout.tsx or src/app/[locale]/page.tsx" codeFormat="typescript"
|
|
1128
|
+
import { getIntlayer, getMultilingualUrls } from "intlayer";
|
|
1129
|
+
import type { Metadata } from "next";
|
|
1130
|
+
import type { LocalPromiseParams } from "next-intlayer";
|
|
1131
|
+
|
|
1132
|
+
export const generateMetadata = async ({
|
|
1133
|
+
params,
|
|
1134
|
+
}: LocalPromiseParams): Promise<Metadata> => {
|
|
1135
|
+
const { locale } = await params;
|
|
1136
|
+
const metadata = getIntlayer("page-metadata", locale);
|
|
1137
|
+
|
|
1138
|
+
const multilingualUrls = getMultilingualUrls("/");
|
|
1139
|
+
const localizedUrl =
|
|
1140
|
+
multilingualUrls[locale as keyof typeof multilingualUrls];
|
|
1141
|
+
|
|
1142
|
+
return {
|
|
1143
|
+
...metadata,
|
|
1144
|
+
alternates: {
|
|
1145
|
+
canonical: localizedUrl,
|
|
1146
|
+
languages: { ...multilingualUrls, "x-default": "/" },
|
|
1147
|
+
},
|
|
1148
|
+
openGraph: {
|
|
1149
|
+
url: localizedUrl,
|
|
1150
|
+
},
|
|
1151
|
+
};
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1154
|
+
// ... Rest of your layout or page code
|
|
1155
|
+
```
|
|
1156
|
+
|
|
1157
|
+
```javascript fileName="src/app/[locale]/layout.mjs or src/app/[locale]/page.mjs" codeFormat="esm"
|
|
1158
|
+
import { getIntlayer, getMultilingualUrls } from "intlayer";
|
|
1159
|
+
|
|
1160
|
+
export const generateMetadata = async ({ params }) => {
|
|
1161
|
+
const { locale } = await params;
|
|
1162
|
+
const metadata = getIntlayer("page-metadata", locale);
|
|
1163
|
+
|
|
1164
|
+
const multilingualUrls = getMultilingualUrls("/");
|
|
1165
|
+
const localizedUrl = multilingualUrls[locale];
|
|
1166
|
+
|
|
1167
|
+
return {
|
|
1168
|
+
...metadata,
|
|
1169
|
+
alternates: {
|
|
1170
|
+
canonical: localizedUrl,
|
|
1171
|
+
languages: { ...multilingualUrls, "x-default": "/" },
|
|
1172
|
+
},
|
|
1173
|
+
openGraph: {
|
|
1174
|
+
url: localizedUrl,
|
|
1175
|
+
},
|
|
1176
|
+
};
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
// ... Rest of your layout or page code
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
```javascript fileName="src/app/[locale]/layout.cjs or src/app/[locale]/page.cjs" codeFormat="commonjs"
|
|
1183
|
+
const { getIntlayer, getMultilingualUrls } = require("intlayer");
|
|
1184
|
+
|
|
1185
|
+
const generateMetadata = async ({ params }) => {
|
|
1186
|
+
const { locale } = await params;
|
|
1187
|
+
const metadata = getIntlayer("page-metadata", locale);
|
|
1188
|
+
|
|
1189
|
+
const multilingualUrls = getMultilingualUrls("/");
|
|
1190
|
+
const localizedUrl = multilingualUrls[locale];
|
|
1191
|
+
|
|
1192
|
+
return {
|
|
1193
|
+
...metadata,
|
|
1194
|
+
alternates: {
|
|
1195
|
+
canonical: localizedUrl,
|
|
1196
|
+
languages: { ...multilingualUrls, "x-default": "/" },
|
|
1197
|
+
},
|
|
1198
|
+
openGraph: {
|
|
1199
|
+
url: localizedUrl,
|
|
1200
|
+
},
|
|
1201
|
+
};
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
module.exports = { generateMetadata };
|
|
1205
|
+
|
|
1206
|
+
// ... Rest of your layout or page code
|
|
1207
|
+
```
|
|
1208
|
+
|
|
1209
|
+
> Learn more about Next.js metadata optimization in the [official Next.js documentation](https://nextjs.org/docs/app/building-your-application/optimizing/metadata).
|
|
1210
|
+
|
|
1211
|
+
### (Optional) Step 9: Internationalization of sitemap.xml and robots.txt
|
|
1212
|
+
|
|
1213
|
+
Internationalize your `sitemap.xml` and `robots.txt` files:
|
|
1214
|
+
|
|
1215
|
+
```tsx fileName="src/app/sitemap.ts" codeFormat="typescript"
|
|
1216
|
+
import { getMultilingualUrls } from "intlayer";
|
|
1217
|
+
import type { MetadataRoute } from "next";
|
|
1218
|
+
|
|
1219
|
+
const sitemap = (): MetadataRoute.Sitemap => [
|
|
1220
|
+
{
|
|
1221
|
+
url: "https://example.com",
|
|
1222
|
+
alternates: {
|
|
1223
|
+
languages: { ...getMultilingualUrls("https://example.com") },
|
|
1224
|
+
},
|
|
1225
|
+
},
|
|
1226
|
+
{
|
|
1227
|
+
url: "https://example.com/about",
|
|
1228
|
+
alternates: {
|
|
1229
|
+
languages: { ...getMultilingualUrls("https://example.com/about") },
|
|
1230
|
+
},
|
|
1231
|
+
},
|
|
1232
|
+
{
|
|
1233
|
+
url: "https://example.com/contact",
|
|
1234
|
+
alternates: {
|
|
1235
|
+
languages: { ...getMultilingualUrls("https://example.com/contact") },
|
|
1236
|
+
},
|
|
1237
|
+
},
|
|
1238
|
+
];
|
|
1239
|
+
|
|
1240
|
+
export default sitemap;
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
```jsx fileName="src/app/sitemap.mjx" codeFormat="esm"
|
|
1244
|
+
import { getMultilingualUrls } from "intlayer";
|
|
1245
|
+
|
|
1246
|
+
const sitemap = () => [
|
|
1247
|
+
{
|
|
1248
|
+
url: "https://example.com",
|
|
1249
|
+
alternates: {
|
|
1250
|
+
languages: { ...getMultilingualUrls("https://example.com") },
|
|
1251
|
+
},
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
url: "https://example.com/about",
|
|
1255
|
+
alternates: {
|
|
1256
|
+
languages: { ...getMultilingualUrls("https://example.com/about") },
|
|
1257
|
+
},
|
|
1258
|
+
},
|
|
1259
|
+
{
|
|
1260
|
+
url: "https://example.com/contact",
|
|
1261
|
+
alternates: {
|
|
1262
|
+
languages: { ...getMultilingualUrls("https://example.com/contact") },
|
|
1263
|
+
},
|
|
1264
|
+
},
|
|
1265
|
+
];
|
|
1266
|
+
|
|
1267
|
+
export default sitemap;
|
|
1268
|
+
```
|
|
1269
|
+
|
|
1270
|
+
```jsx fileName="src/app/sitemap.csx" codeFormat="commonjs"
|
|
1271
|
+
const { getMultilingualUrls } = require("intlayer");
|
|
1272
|
+
|
|
1273
|
+
const sitemap = () => [
|
|
1274
|
+
{
|
|
1275
|
+
url: "https://example.com",
|
|
1276
|
+
alternates: {
|
|
1277
|
+
languages: { ...getMultilingualUrls("https://example.com") },
|
|
1278
|
+
},
|
|
1279
|
+
},
|
|
1280
|
+
{
|
|
1281
|
+
url: "https://example.com/about",
|
|
1282
|
+
alternates: {
|
|
1283
|
+
languages: { ...getMultilingualUrls("https://example.com/about") },
|
|
1284
|
+
},
|
|
1285
|
+
},
|
|
1286
|
+
{
|
|
1287
|
+
url: "https://example.com/contact",
|
|
1288
|
+
alternates: {
|
|
1289
|
+
languages: { ...getMultilingualUrls("https://example.com/contact") },
|
|
1290
|
+
},
|
|
1291
|
+
},
|
|
1292
|
+
];
|
|
1293
|
+
|
|
1294
|
+
module.exports = sitemap;
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
```tsx fileName="src/app/robots.ts" codeFormat="typescript"
|
|
1298
|
+
import type { MetadataRoute } from "next";
|
|
1299
|
+
import { getMultilingualUrls } from "intlayer";
|
|
1300
|
+
|
|
1301
|
+
const getAllMultilingualUrls = (urls: string[]) =>
|
|
1302
|
+
urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);
|
|
1303
|
+
|
|
1304
|
+
const robots = (): MetadataRoute.Robots => ({
|
|
1305
|
+
rules: {
|
|
1306
|
+
userAgent: "*",
|
|
1307
|
+
allow: ["/"],
|
|
1308
|
+
disallow: getAllMultilingualUrls(["/admin", "/private"]),
|
|
1309
|
+
},
|
|
1310
|
+
host: "https://example.com",
|
|
1311
|
+
sitemap: `https://example.com/sitemap.xml`,
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
export default robots;
|
|
1315
|
+
```
|
|
1316
|
+
|
|
1317
|
+
```jsx fileName="src/app/robots.mjx" codeFormat="esm"
|
|
1318
|
+
import { getMultilingualUrls } from "intlayer";
|
|
1319
|
+
|
|
1320
|
+
const getAllMultilingualUrls = (urls) =>
|
|
1321
|
+
urls.flatMap((url) => Object.values(getMultilingualUrls(url)));
|
|
1322
|
+
|
|
1323
|
+
const robots = () => ({
|
|
1324
|
+
rules: {
|
|
1325
|
+
userAgent: "*",
|
|
1326
|
+
allow: ["/"],
|
|
1327
|
+
disallow: getAllMultilingualUrls(["/admin", "/private"]),
|
|
1328
|
+
},
|
|
1329
|
+
host: "https://example.com",
|
|
1330
|
+
sitemap: `https://example.com/sitemap.xml`,
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
export default robots;
|
|
1334
|
+
```
|
|
1335
|
+
|
|
1336
|
+
```jsx fileName="src/app/robots.csx" codeFormat="commonjs"
|
|
1337
|
+
const { getMultilingualUrls } = require("intlayer");
|
|
1338
|
+
|
|
1339
|
+
const getAllMultilingualUrls = (urls) =>
|
|
1340
|
+
urls.flatMap((url) => Object.values(getMultilingualUrls(url)));
|
|
1341
|
+
|
|
1342
|
+
const robots = () => ({
|
|
1343
|
+
rules: {
|
|
1344
|
+
userAgent: "*",
|
|
1345
|
+
allow: ["/"],
|
|
1346
|
+
disallow: getAllMultilingualUrls(["/admin", "/private"]),
|
|
1347
|
+
},
|
|
1348
|
+
host: "https://example.com",
|
|
1349
|
+
sitemap: `https://example.com/sitemap.xml`,
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
module.exports = robots;
|
|
1353
|
+
```
|
|
1354
|
+
|
|
1355
|
+
> Learn more about sitemap optimization in the [Next.js sitemap documentation](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap) and about robots.txt in the [Next.js robots.txt documentation](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/robots).
|
|
1356
|
+
|
|
1357
|
+
### (Optional) Step 10: Change the Language of Your Content
|
|
1358
|
+
|
|
1359
|
+
To allow users to switch languages, use the `useLocale` hook and Next.js's `Link` component:
|
|
1360
|
+
|
|
1361
|
+
```tsx fileName="src/components/LocaleSwitcher.tsx" codeFormat="typescript"
|
|
1362
|
+
"use client";
|
|
1363
|
+
|
|
1364
|
+
import type { FC } from "react";
|
|
1365
|
+
import {
|
|
1366
|
+
Locales,
|
|
1367
|
+
getHTMLTextDir,
|
|
1368
|
+
getLocaleName,
|
|
1369
|
+
getLocalizedUrl,
|
|
1370
|
+
} from "intlayer";
|
|
1371
|
+
import { useLocale } from "next-intlayer";
|
|
1372
|
+
import Link from "next/link";
|
|
1373
|
+
|
|
1374
|
+
export const LocaleSwitcher: FC = () => {
|
|
1375
|
+
const { locale, pathWithoutLocale, availableLocales, setLocale } =
|
|
1376
|
+
useLocale();
|
|
1377
|
+
|
|
1378
|
+
return (
|
|
1379
|
+
<div>
|
|
1380
|
+
<button popoverTarget="localePopover">{getLocaleName(locale)}</button>
|
|
1381
|
+
<div id="localePopover" popover="auto">
|
|
1382
|
+
{availableLocales.map((localeItem) => (
|
|
1383
|
+
<Link
|
|
1384
|
+
href={getLocalizedUrl(pathWithoutLocale, localeItem)}
|
|
1385
|
+
key={localeItem}
|
|
1386
|
+
aria-current={locale === localeItem ? "page" : undefined}
|
|
1387
|
+
onClick={() => setLocale(localeItem)}
|
|
1388
|
+
replace // Ensures that "go back" browser button redirects to previous page
|
|
1389
|
+
>
|
|
1390
|
+
<span>
|
|
1391
|
+
{/* Locale - e.g. FR */}
|
|
1392
|
+
{localeItem}
|
|
1393
|
+
</span>
|
|
1394
|
+
<span>
|
|
1395
|
+
{/* Language in its own Locale - e.g. Français */}
|
|
1396
|
+
{getLocaleName(localeItem, locale)}
|
|
1397
|
+
</span>
|
|
1398
|
+
<span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
|
|
1399
|
+
{/* Language in current Locale - e.g. Francés with current locale set to Locales.SPANISH */}
|
|
1400
|
+
{getLocaleName(localeItem)}
|
|
1401
|
+
</span>
|
|
1402
|
+
<span dir="ltr" lang={Locales.ENGLISH}>
|
|
1403
|
+
{/* Language in English - e.g. French */}
|
|
1404
|
+
{getLocaleName(localeItem, Locales.ENGLISH)}
|
|
1405
|
+
</span>
|
|
1406
|
+
</Link>
|
|
1407
|
+
))}
|
|
1408
|
+
</div>
|
|
1409
|
+
</div>
|
|
1410
|
+
);
|
|
1411
|
+
};
|
|
1412
|
+
```
|
|
1413
|
+
|
|
1414
|
+
```jsx fileName="src/components/LocaleSwitcher.mjx" codeFormat="esm"
|
|
1415
|
+
"use client";
|
|
1416
|
+
|
|
1417
|
+
import {
|
|
1418
|
+
Locales,
|
|
1419
|
+
getHTMLTextDir,
|
|
1420
|
+
getLocaleName,
|
|
1421
|
+
getLocalizedUrl,
|
|
1422
|
+
} from "intlayer";
|
|
1423
|
+
import { useLocale } from "next-intlayer";
|
|
1424
|
+
import Link from "next/link";
|
|
1425
|
+
|
|
1426
|
+
export const LocaleSwitcher = () => {
|
|
1427
|
+
const { locale, pathWithoutLocale, availableLocales, setLocale } =
|
|
1428
|
+
useLocale();
|
|
1429
|
+
|
|
1430
|
+
return (
|
|
1431
|
+
<div>
|
|
1432
|
+
<button popoverTarget="localePopover">{getLocaleName(locale)}</button>
|
|
1433
|
+
<div id="localePopover" popover="auto">
|
|
1434
|
+
{availableLocales.map((localeItem) => (
|
|
1435
|
+
<Link
|
|
1436
|
+
href={getLocalizedUrl(pathWithoutLocale, localeItem)}
|
|
1437
|
+
key={localeItem}
|
|
1438
|
+
aria-current={locale === localeItem ? "page" : undefined}
|
|
1439
|
+
onClick={() => setLocale(localeItem)}
|
|
1440
|
+
replace
|
|
1441
|
+
>
|
|
1442
|
+
<span>{localeItem}</span>
|
|
1443
|
+
<span>{getLocaleName(localeItem, locale)}</span>
|
|
1444
|
+
<span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
|
|
1445
|
+
{getLocaleName(localeItem)}
|
|
1446
|
+
</span>
|
|
1447
|
+
<span dir="ltr" lang={Locales.ENGLISH}>
|
|
1448
|
+
{getLocaleName(localeItem, Locales.ENGLISH)}
|
|
1449
|
+
</span>
|
|
1450
|
+
</Link>
|
|
1451
|
+
))}
|
|
1452
|
+
</div>
|
|
1453
|
+
</div>
|
|
1454
|
+
);
|
|
1455
|
+
};
|
|
1456
|
+
```
|
|
1457
|
+
|
|
1458
|
+
```jsx fileName="src/components/LocaleSwitcher.csx" codeFormat="commonjs"
|
|
1459
|
+
"use client";
|
|
1460
|
+
|
|
1461
|
+
const {
|
|
1462
|
+
Locales,
|
|
1463
|
+
getHTMLTextDir,
|
|
1464
|
+
getLocaleName,
|
|
1465
|
+
getLocalizedUrl,
|
|
1466
|
+
} = require("intlayer");
|
|
1467
|
+
const { useLocale } = require("next-intlayer");
|
|
1468
|
+
const Link = require("next/link");
|
|
1469
|
+
|
|
1470
|
+
const LocaleSwitcher = () => {
|
|
1471
|
+
const { locale, pathWithoutLocale, availableLocales, setLocale } =
|
|
1472
|
+
useLocale();
|
|
1473
|
+
|
|
1474
|
+
return (
|
|
1475
|
+
<div>
|
|
1476
|
+
<button popoverTarget="localePopover">{getLocaleName(locale)}</button>
|
|
1477
|
+
<div id="localePopover" popover="auto">
|
|
1478
|
+
{availableLocales.map((localeItem) => (
|
|
1479
|
+
<Link
|
|
1480
|
+
href={getLocalizedUrl(pathWithoutLocale, localeItem)}
|
|
1481
|
+
key={localeItem}
|
|
1482
|
+
aria-current={locale === localeItem ? "page" : undefined}
|
|
1483
|
+
onClick={() => setLocale(localeItem)}
|
|
1484
|
+
replace
|
|
1485
|
+
>
|
|
1486
|
+
<span>{localeItem}</span>
|
|
1487
|
+
<span>{getLocaleName(localeItem, locale)}</span>
|
|
1488
|
+
<span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
|
|
1489
|
+
{getLocaleName(localeItem)}
|
|
1490
|
+
</span>
|
|
1491
|
+
<span dir="ltr" lang={Locales.ENGLISH}>
|
|
1492
|
+
{getLocaleName(localeItem, Locales.ENGLISH)}
|
|
1493
|
+
</span>
|
|
1494
|
+
</Link>
|
|
1495
|
+
))}
|
|
1496
|
+
</div>
|
|
1497
|
+
</div>
|
|
1498
|
+
);
|
|
1499
|
+
};
|
|
1500
|
+
|
|
1501
|
+
exports.LocaleSwitcher = LocaleSwitcher;
|
|
1502
|
+
```
|
|
1503
|
+
|
|
1504
|
+
> **Comparison with i18next**: With i18next, you would typically use the `useTranslation` hook and manually manage language switching. Intlayer's `useLocale` hook provides a more integrated experience with Next.js routing and automatic URL management.
|
|
1505
|
+
|
|
1506
|
+
> Documentation references:
|
|
1507
|
+
>
|
|
1508
|
+
> - [`useLocale` hook](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/packages/next-intlayer/useLocale.md)
|
|
1509
|
+
> - [`getLocaleName` hook](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/packages/intlayer/getLocaleName.md)
|
|
1510
|
+
> - [`getLocalizedUrl` hook](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/packages/intlayer/getLocalizedUrl.md)
|
|
1511
|
+
> - [`getHTMLTextDir` hook](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/packages/intlayer/getHTMLTextDir.md)
|
|
1512
|
+
|
|
1513
|
+
### (Optional) Step 11: Using i18next Alongside Intlayer
|
|
1514
|
+
|
|
1515
|
+
If you need to maintain i18next compatibility during a gradual migration, you can configure i18next to load dictionaries exported by Intlayer:
|
|
1516
|
+
|
|
1517
|
+
```typescript fileName="i18n/client.ts" codeFormat="typescript"
|
|
119
1518
|
import i18next from "i18next";
|
|
1519
|
+
import { initReactI18next } from "react-i18next";
|
|
120
1520
|
import resourcesToBackend from "i18next-resources-to-backend";
|
|
121
1521
|
|
|
122
1522
|
i18next
|
|
123
|
-
|
|
1523
|
+
.use(initReactI18next)
|
|
124
1524
|
.use(
|
|
125
1525
|
resourcesToBackend(
|
|
126
1526
|
(language: string, namespace: string) =>
|
|
127
|
-
import(`../
|
|
1527
|
+
import(`../intl/messages/${language}/${namespace}.json`)
|
|
128
1528
|
)
|
|
129
|
-
)
|
|
1529
|
+
)
|
|
1530
|
+
.init({
|
|
1531
|
+
fallbackLng: "en",
|
|
1532
|
+
debug: process.env.NODE_ENV === "development",
|
|
1533
|
+
interpolation: {
|
|
1534
|
+
escapeValue: false,
|
|
1535
|
+
},
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
export default i18next;
|
|
130
1539
|
```
|
|
131
1540
|
|
|
132
1541
|
```javascript fileName="i18n/client.mjs" codeFormat="esm"
|
|
133
|
-
// i18n/client.mjs
|
|
134
|
-
|
|
135
1542
|
import i18next from "i18next";
|
|
1543
|
+
import { initReactI18next } from "react-i18next";
|
|
136
1544
|
import resourcesToBackend from "i18next-resources-to-backend";
|
|
137
1545
|
|
|
138
1546
|
i18next
|
|
139
|
-
|
|
1547
|
+
.use(initReactI18next)
|
|
140
1548
|
.use(
|
|
141
1549
|
resourcesToBackend(
|
|
142
1550
|
(language, namespace) =>
|
|
143
|
-
import(`../
|
|
1551
|
+
import(`../intl/messages/${language}/${namespace}.json`)
|
|
144
1552
|
)
|
|
145
|
-
)
|
|
1553
|
+
)
|
|
1554
|
+
.init({
|
|
1555
|
+
fallbackLng: "en",
|
|
1556
|
+
debug: process.env.NODE_ENV === "development",
|
|
1557
|
+
interpolation: {
|
|
1558
|
+
escapeValue: false,
|
|
1559
|
+
},
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1562
|
+
export default i18next;
|
|
146
1563
|
```
|
|
147
1564
|
|
|
148
1565
|
```javascript fileName="i18n/client.cjs" codeFormat="commonjs"
|
|
149
|
-
// i18n/client.cjs
|
|
150
|
-
|
|
151
1566
|
const i18next = require("i18next");
|
|
1567
|
+
const { initReactI18next } = require("react-i18next");
|
|
152
1568
|
const resourcesToBackend = require("i18next-resources-to-backend");
|
|
153
1569
|
|
|
154
1570
|
i18next
|
|
155
|
-
|
|
1571
|
+
.use(initReactI18next)
|
|
156
1572
|
.use(
|
|
157
1573
|
resourcesToBackend(
|
|
158
1574
|
(language, namespace) =>
|
|
159
|
-
import(`../
|
|
1575
|
+
import(`../intl/messages/${language}/${namespace}.json`)
|
|
160
1576
|
)
|
|
1577
|
+
)
|
|
1578
|
+
.init({
|
|
1579
|
+
fallbackLng: "en",
|
|
1580
|
+
debug: process.env.NODE_ENV === "development",
|
|
1581
|
+
interpolation: {
|
|
1582
|
+
escapeValue: false,
|
|
1583
|
+
},
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
module.exports = i18next;
|
|
1587
|
+
```
|
|
1588
|
+
|
|
1589
|
+
**Usage in a component:**
|
|
1590
|
+
|
|
1591
|
+
```tsx fileName="src/components/LegacyComponent.tsx"
|
|
1592
|
+
"use client";
|
|
1593
|
+
|
|
1594
|
+
import { useTranslation } from "react-i18next";
|
|
1595
|
+
|
|
1596
|
+
export const LegacyComponent = () => {
|
|
1597
|
+
const { t } = useTranslation();
|
|
1598
|
+
|
|
1599
|
+
return (
|
|
1600
|
+
<div>
|
|
1601
|
+
<h2>{t("page:docs.title")}</h2>
|
|
1602
|
+
<p>{t("page:docs.description")}</p>
|
|
1603
|
+
</div>
|
|
161
1604
|
);
|
|
1605
|
+
};
|
|
162
1606
|
```
|
|
1607
|
+
|
|
1608
|
+
This approach allows you to:
|
|
1609
|
+
|
|
1610
|
+
- Use Intlayer's modern content declaration system
|
|
1611
|
+
- Gradually migrate components from i18next to Intlayer
|
|
1612
|
+
- Maintain compatibility with existing i18next code
|
|
1613
|
+
- Export dictionaries in both formats during the transition
|
|
1614
|
+
|
|
1615
|
+
---
|
|
1616
|
+
|
|
1617
|
+
## Migration Strategy: From i18next to Intlayer
|
|
1618
|
+
|
|
1619
|
+
If you're migrating an existing application from i18next to Intlayer, follow this strategy:
|
|
1620
|
+
|
|
1621
|
+
### Phase 1: Setup (Week 1)
|
|
1622
|
+
|
|
1623
|
+
1. **Install Intlayer** alongside your existing i18next setup
|
|
1624
|
+
2. **Configure Intlayer** with the `syncJSON` plugin to export dictionaries
|
|
1625
|
+
3. **Test** that both systems work in parallel
|
|
1626
|
+
|
|
1627
|
+
### Phase 2: Gradual Migration (Weeks 2-N)
|
|
1628
|
+
|
|
1629
|
+
1. **Create Intlayer content files** for new features
|
|
1630
|
+
2. **Migrate existing components** one at a time:
|
|
1631
|
+
- Create corresponding `.content.ts` files
|
|
1632
|
+
- Replace `useTranslation()` with `useIntlayer()`
|
|
1633
|
+
- Test thoroughly before moving to the next component
|
|
1634
|
+
3. **Focus on high-value components** first (frequently edited, complex translations)
|
|
1635
|
+
|
|
1636
|
+
### Phase 3: Cleanup (Final Week)
|
|
1637
|
+
|
|
1638
|
+
1. **Remove i18next dependencies** once all components are migrated
|
|
1639
|
+
2. **Delete old locale JSON files**
|
|
1640
|
+
3. **Remove the `syncJSON` plugin** from your Intlayer config
|
|
1641
|
+
4. **Update documentation** for your team
|
|
1642
|
+
|
|
1643
|
+
### Migration Checklist
|
|
1644
|
+
|
|
1645
|
+
- [ ] Intlayer installed and configured
|
|
1646
|
+
- [ ] `syncJSON` plugin configured for i18next compatibility
|
|
1647
|
+
- [ ] First component migrated and tested
|
|
1648
|
+
- [ ] Team trained on Intlayer patterns
|
|
1649
|
+
- [ ] 25% of components migrated
|
|
1650
|
+
- [ ] 50% of components migrated
|
|
1651
|
+
- [ ] 75% of components migrated
|
|
1652
|
+
- [ ] 100% of components migrated
|
|
1653
|
+
- [ ] i18next removed
|
|
1654
|
+
- [ ] Documentation updated
|
|
1655
|
+
|
|
1656
|
+
---
|
|
1657
|
+
|
|
1658
|
+
## Advanced Configuration
|
|
1659
|
+
|
|
1660
|
+
### TypeScript Configuration
|
|
1661
|
+
|
|
1662
|
+
Ensure your TypeScript configuration includes the auto-generated types:
|
|
1663
|
+
|
|
1664
|
+
```json5 fileName="tsconfig.json"
|
|
1665
|
+
{
|
|
1666
|
+
// ... Your existing TypeScript configurations
|
|
1667
|
+
"include": [
|
|
1668
|
+
// ... Your existing TypeScript configurations
|
|
1669
|
+
".intlayer/**/*.ts", // Include the auto-generated types
|
|
1670
|
+
],
|
|
1671
|
+
}
|
|
1672
|
+
```
|
|
1673
|
+
|
|
1674
|
+
### Git Configuration
|
|
1675
|
+
|
|
1676
|
+
Ignore the generated files:
|
|
1677
|
+
|
|
1678
|
+
```plaintext fileName=".gitignore"
|
|
1679
|
+
# Ignore the files generated by Intlayer
|
|
1680
|
+
.intlayer
|
|
1681
|
+
```
|
|
1682
|
+
|
|
1683
|
+
### VS Code Extension
|
|
1684
|
+
|
|
1685
|
+
For improved developer experience, install the official **Intlayer VS Code Extension**:
|
|
1686
|
+
|
|
1687
|
+
[Install from the VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=intlayer.intlayer-vs-code-extension)
|
|
1688
|
+
|
|
1689
|
+
This extension provides:
|
|
1690
|
+
|
|
1691
|
+
- **Autocompletion** for translation keys
|
|
1692
|
+
- **Real-time error detection** for missing translations
|
|
1693
|
+
- **Inline previews** of translated content
|
|
1694
|
+
- **Quick actions** for creating and updating translations
|
|
1695
|
+
|
|
1696
|
+
> **Comparison with i18next**: While i18next has some IDE extensions, Intlayer's extension provides deeper integration with TypeScript and better auto-completion thanks to co-located content declarations.
|
|
1697
|
+
|
|
1698
|
+
---
|
|
1699
|
+
|
|
1700
|
+
## Conclusion
|
|
1701
|
+
|
|
1702
|
+
By following this comprehensive guide, you now have a fully internationalized Next.js application using Intlayer, with optional i18next compatibility for gradual migration.
|
|
1703
|
+
|
|
1704
|
+
**Key Takeaways:**
|
|
1705
|
+
|
|
1706
|
+
1. **Intlayer** offers a modern, developer-friendly approach to i18n that addresses many limitations of traditional solutions like i18next
|
|
1707
|
+
2. **Co-located content** declarations improve maintainability and reduce errors
|
|
1708
|
+
3. **First-class TypeScript support** provides excellent IDE integration and catches errors at build time
|
|
1709
|
+
4. **Flexible migration** path allows gradual adoption without disrupting existing applications
|
|
1710
|
+
5. **Built-in Next.js support** makes it the ideal choice for modern React applications
|
|
1711
|
+
|
|
1712
|
+
Whether you're starting a new project or migrating from i18next, Intlayer provides the tools and flexibility you need for a scalable, maintainable internationalization solution.
|
|
1713
|
+
|
|
1714
|
+
---
|
|
1715
|
+
|
|
1716
|
+
## Further Resources
|
|
1717
|
+
|
|
1718
|
+
- [Intlayer Documentation](https://intlayer.org)
|
|
1719
|
+
- [Intlayer GitHub Repository](https://github.com/aymericzip/intlayer)
|
|
1720
|
+
- [Next.js Internationalization Guide](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_with_nextjs_14.md)
|
|
1721
|
+
- [Intlayer Visual Editor](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_visual_editor.md)
|
|
1722
|
+
- [Intlayer CMS](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_CMS.md)
|
|
1723
|
+
- [i18next Documentation](https://www.i18next.com/)
|
|
1724
|
+
- [react-i18next Documentation](https://react.i18next.com/)
|
|
1725
|
+
|
|
1726
|
+
<function_calls>
|
|
1727
|
+
<invoke name="read_file">
|
|
1728
|
+
<parameter name="target_file">docs/docs/en/intlayer_with_nextjs_16.md
|