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