@intlayer/docs 6.1.2 → 6.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
createdAt: 2025-08-23
|
|
3
|
-
updatedAt: 2025-
|
|
3
|
+
updatedAt: 2025-09-25
|
|
4
4
|
title: next-i18next vs next-intl vs Intlayer
|
|
5
5
|
description: Compare next-i18next with next-intl and Intlayer for the internationalization (i18n) of a Next.js app
|
|
6
6
|
keywords:
|
|
@@ -19,7 +19,10 @@ slugs:
|
|
|
19
19
|
|
|
20
20
|
# next-i18next VS next-intl VS intlayer | Next.js Internationalization (i18n)
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Let’s take a look into the similarities and differences between three i18n options for Next.js: next-i18next, next-intl, and Intlayer.
|
|
23
|
+
|
|
24
|
+
This is not a full tutorial. It’s a comparison to help you pick.
|
|
25
|
+
|
|
23
26
|
We focus on **Next.js 13+ App Router** (with **React Server Components**) and evaluate:
|
|
24
27
|
|
|
25
28
|
1. **Architecture & content organization**
|
|
@@ -32,9 +35,11 @@ We focus on **Next.js 13+ App Router** (with **React Server Components**) and ev
|
|
|
32
35
|
|
|
33
36
|
> **tl;dr**: All three can localize a Next.js app. If you want **component-scoped content**, **strict TypeScript types**, **build-time missing-key checks**, **tree-shaken dictionaries**, and **first-class App Router + SEO helpers**, **Intlayer** is the most complete, modern choice.
|
|
34
37
|
|
|
38
|
+
> One confusion often made by developers is to think that `next-intl` is the Next.js version of `react-intl`. It's not—`next-intl` is maintained by [Amann](https://github.com/amannn), while `react-intl` is maintained by [FormatJS](https://github.com/formatjs/formatjs).
|
|
39
|
+
|
|
35
40
|
---
|
|
36
41
|
|
|
37
|
-
##
|
|
42
|
+
## In short
|
|
38
43
|
|
|
39
44
|
- **next-intl** - Lightweight, straightforward message formatting with solid Next.js support. Centralized catalogs are common; DX is simple, but safety and large-scale maintenance remain mostly your responsibility.
|
|
40
45
|
- **next-i18next** - i18next in Next.js clothing. Mature ecosystem and features via plugins (e.g., ICU), but configuration can be verbose and catalogs tend to centralize as projects grow.
|
|
@@ -42,6 +47,17 @@ We focus on **Next.js 13+ App Router** (with **React Server Components**) and ev
|
|
|
42
47
|
|
|
43
48
|
---
|
|
44
49
|
|
|
50
|
+
| Library | GitHub Stars | Total Commits | Last Commit | First Version | NPM Version | NPM Downloads |
|
|
51
|
+
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
52
|
+
| `aymericzip/intlayer` | [](https://github.com/aymericzip/intlayer/stargazers) | [](https://github.com/aymericzip/intlayer/commits) | [](https://github.com/aymericzip/intlayer/commits) | April 2024 | [](https://www.npmjs.com/package/intlayer) | [](https://www.npmjs.com/package/intlayer) |
|
|
53
|
+
| `amannn/next-intl` | [](https://github.com/amannn/next-intl/stargazers) | [](https://github.com/amannn/next-intl/commits) | [](https://github.com/amannn/next-intl/commits) | Nov 2020 | [](https://www.npmjs.com/package/next-intl) | [](https://www.npmjs.com/package/next-intl) |
|
|
54
|
+
| `i18next/i18next` | [](https://github.com/i18next/i18next/stargazers) | [](https://github.com/i18next/i18next/commits) | [](https://github.com/i18next/i18next/commits) | Jan 2012 | [](https://www.npmjs.com/package/i18next) | [](https://www.npmjs.com/package/i18next) |
|
|
55
|
+
| `i18next/next-i18next` | [](https://github.com/i18next/next-i18next/stargazers) | [](https://github.com/i18next/next-i18next/commits) | [](https://github.com/i18next/next-i18next/commits) | Nov 2018 | [](https://www.npmjs.com/package/next-i18next) | [](https://www.npmjs.com/package/next-i18next) |
|
|
56
|
+
|
|
57
|
+
> Badges update automatically. Snapshots will vary over time.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
45
61
|
## Side-by-Side Feature Comparison (Next.js focused)
|
|
46
62
|
|
|
47
63
|
| Feature | `next-intlayer` (Intlayer) | `next-intl` | `next-i18next` |
|
|
@@ -62,15 +78,22 @@ We focus on **Next.js 13+ App Router** (with **React Server Components**) and ev
|
|
|
62
78
|
| **Ecosystem / Community** | ⚠️ Smaller but growing fast and reactive | ✅ Good | ✅ Good |
|
|
63
79
|
| **Server-side Rendering & Server Components** | ✅ Yes, streamlined for SSR / React Server Components | ⚠️ Supported at page level but need to pass t-functions on component tree for children server components | ⚠️ Supported at page level but need to pass t-functions on component tree for children server components |
|
|
64
80
|
| **Tree-shaking (load only used content)** | ✅ Yes, per-component at build time via Babel/SWC plugins | ⚠️ Partial | ⚠️ Partial |
|
|
65
|
-
| **Lazy loading** | ✅ Yes, per-locale / per-dictionary | ✅ Yes (per-route/per-locale), need
|
|
81
|
+
| **Lazy loading** | ✅ Yes, per-locale / per-dictionary | ✅ Yes (per-route/per-locale), need namespace management | ✅ Yes (per-route/per-locale), need namespace management |
|
|
66
82
|
| **Purge unused content** | ✅ Yes, per-dictionary at build time | ❌ No, can be managed manually with namespace management | ❌ No, can be managed manually with namespace management |
|
|
67
83
|
| **Management of Large Projects** | ✅ Encourages modular, suited for design-system | ✅ Modular with setup | ✅ Modular with setup |
|
|
84
|
+
| **Testing Missing Translations (CLI/CI)** | ✅ CLI: `npx intlayer content test` (CI-friendly audit) | ⚠️ Not built-in; docs suggest `npx @lingual/i18n-check` | ⚠️ Not built-in; rely on i18next tools / runtime `saveMissing` |
|
|
68
85
|
|
|
69
86
|
---
|
|
70
87
|
|
|
71
|
-
##
|
|
88
|
+
## Introduction
|
|
72
89
|
|
|
73
|
-
|
|
90
|
+
Next.js gives you built-in support for internationalized routing (e.g. locale segments). But that feature doesn’t do translations on its own. You still need a library to render localized content to your users.
|
|
91
|
+
|
|
92
|
+
Many i18n libraries exist, but in the Next.js world today, three are gaining traction: next-i18next, next-intl, and Intlayer.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Architecture & scalability
|
|
74
97
|
|
|
75
98
|
- **next-intl / next-i18next**: Default to **centralized catalogs** per locale (plus **namespaces** in i18next). Works fine early on, but often becomes a big shared surface area with rising coupling and key churn.
|
|
76
99
|
- **Intlayer**: Encourages **per-component** (or per-feature) dictionaries **co-located** with the code they serve. This lowers cognitive load, eases duplication/migration of UI pieces, and reduces cross-team conflicts. Unused content is naturally easier to spot and purge.
|
|
@@ -79,76 +102,1136 @@ We focus on **Next.js 13+ App Router** (with **React Server Components**) and ev
|
|
|
79
102
|
|
|
80
103
|
---
|
|
81
104
|
|
|
82
|
-
|
|
105
|
+
## Bundle sizes & dependencies
|
|
106
|
+
|
|
107
|
+
After building the application, the bundle is the JavaScript that the browser will load to render the page. Bundle size is therefore important for application performance.
|
|
108
|
+
|
|
109
|
+
Two components are important in the context of a multi-language application bundle:
|
|
110
|
+
|
|
111
|
+
- The application code
|
|
112
|
+
- The content loaded by the browser
|
|
113
|
+
|
|
114
|
+
## Application Code
|
|
115
|
+
|
|
116
|
+
The importance of application code is minimal in this case. All three solutions are tree-shakable, meaning that unused parts of the code are not included in the bundle.
|
|
117
|
+
|
|
118
|
+
Here's a comparison of the JavaScript bundle size loaded by the browser for a multi-language application with the three solutions.
|
|
119
|
+
|
|
120
|
+
If we don't need any formatter in the application, the list of exported functions after tree-shaking will be:
|
|
121
|
+
|
|
122
|
+
- **next-intlayer**: `useIntlayer`, `useLocale`, `NextIntlClientProvider`, (Bundle size is 180.6 kB -> 78.6 kB (gzip))
|
|
123
|
+
- **next-intl**: `useTranslations`, `useLocale`, `NextIntlClientProvider`, (Bundle size is 101.3 kB -> 31.4 kB (gzip))
|
|
124
|
+
- **next-i18next**: `useTranslation`, `useI18n`, `I18nextProvider`, (Bundle size is 80.7 kB -> 25.5 kB (gzip))
|
|
125
|
+
|
|
126
|
+
These functions are only wrappers around React context/state, so the total impact of the i18n library on bundle size is minimal.
|
|
127
|
+
|
|
128
|
+
> Intlayer is slightly bigger than `next-intl` and `next-i18next` because it includes more logic in the `useIntlayer` function. This is related to markdown and `intlayer-editor` integration.
|
|
129
|
+
|
|
130
|
+
## Content and Translations
|
|
131
|
+
|
|
132
|
+
This part is often ignored by developers, but let's consider the case of an application composed of 10 pages in 10 languages. Let's assume that each page integrates 100% unique content to simplify the calculation (in reality, much content is redundant between pages, e.g., page title, header, footer, etc.).
|
|
133
|
+
|
|
134
|
+
A user wanting to visit the `/fr/about` page will load the content of one page in a given language. Ignoring content optimization would mean loading 8,200% `((1 + (((10 pages - 1) × (10 languages - 1)))) × 100)` of the application content unnecessarily. Do you see the problem? Even if this content remains text, and while you probably prefer to think about optimizing your site's images, you're sending useless content across the globe and making users' computers process it for nothing.
|
|
135
|
+
|
|
136
|
+
Two important issues:
|
|
137
|
+
|
|
138
|
+
- **Splitting by route:**
|
|
139
|
+
|
|
140
|
+
> If I'm on the `/about` page, I don't want to load the content of the `/home` page
|
|
141
|
+
|
|
142
|
+
- **Splitting by locale:**
|
|
143
|
+
> If I'm on the `/fr/about` page, I don't want to load the content of the `/en/about` page
|
|
144
|
+
|
|
145
|
+
Again, all three solutions are aware of these issues and allow managing these optimizations. The difference between the three solutions is the DX (Developer Experience).
|
|
146
|
+
|
|
147
|
+
`next-intl` and `next-i18next` use a centralized approach to manage translations, allowing splitting JSON by locale and by sub-files. In `next-i18next`, we call the JSON files 'namespaces'; `next-intl` allows declaring messages. In `intlayer`, we call the JSON files 'dictionaries'.
|
|
148
|
+
|
|
149
|
+
- In the case of `next-intl`, like `next-i18next`, content is loaded at the page/layout level, then this content is loaded into a context provider. This means the developer must manually manage the JSON files that will be loaded for each page.
|
|
150
|
+
|
|
151
|
+
> In practice, this implies that developers often skip this optimization, preferring to load all content in the page's context provider for simplicity.
|
|
152
|
+
|
|
153
|
+
- In the case of `intlayer`, all content is loaded in the application. Then a plugin (`@intlayer/babel` / `@intlayer/swc`) takes care of optimizing the bundle by loading only the content used on the page. The developer therefore doesn't need to manually manage the dictionaries that will be loaded. This allows better optimization, better maintainability, and reduces development time.
|
|
154
|
+
|
|
155
|
+
As the application grows (especially when multiple developers work on the application), it's common to forget to remove content that's no longer used from JSON files.
|
|
156
|
+
|
|
157
|
+
> Note that all JSON is loaded in all cases (next-intl, next-i18next, intlayer).
|
|
158
|
+
|
|
159
|
+
This is why Intlayer's approach is more performant: if a component is no longer used, its dictionary is not loaded in the bundle.
|
|
160
|
+
|
|
161
|
+
How the library handles fallbacks is also important. Let's consider that the application is in English by default, and the user visits the `/fr/about` page. If translations are missing in French, we'll consider the English fallback.
|
|
162
|
+
|
|
163
|
+
In the case of `next-intl` and `next-i18next`, the library requires loading the JSON related to the current locale, but also to the fallback locale. Thus, considering that all content has been translated, each page will load 100% unnecessary content. **In comparison, `intlayer` processes the fallback at dictionary build time. Thus, each page will load only the content used.**
|
|
164
|
+
|
|
165
|
+
Here an example of the impact of bundle size optimization using `intlayer` in a vite + react application:
|
|
166
|
+
|
|
167
|
+
| Optimized bundle | Bundle not optimized |
|
|
168
|
+
| -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
169
|
+
|  |  |
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Developer Experience
|
|
174
|
+
|
|
175
|
+
This part makes a deep comparison between the three solutions. Rather than considering simple cases, as described in the 'getting started' documentation for each solution, we will consider a real use case, more similar to a real project.
|
|
176
|
+
|
|
177
|
+
### App structure
|
|
178
|
+
|
|
179
|
+
The app structure is important to ensure good maintainability for your codebase.
|
|
180
|
+
|
|
181
|
+
<Tab defaultTab="next-intl" group='techno'>
|
|
182
|
+
|
|
183
|
+
<TabItem label="next-i18next" value="next-i18next">
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
.
|
|
187
|
+
├── public
|
|
188
|
+
│ └── locales
|
|
189
|
+
│ ├── en
|
|
190
|
+
│ │ ├── home.json
|
|
191
|
+
│ │ └── navbar.json
|
|
192
|
+
│ ├── fr
|
|
193
|
+
│ │ ├── home.json
|
|
194
|
+
│ │ └── navbar.json
|
|
195
|
+
│ └── es
|
|
196
|
+
│ ├── home.json
|
|
197
|
+
│ └── navbar.json
|
|
198
|
+
├── next-i18next.config.js
|
|
199
|
+
└── src
|
|
200
|
+
├── middleware.ts
|
|
201
|
+
├── app
|
|
202
|
+
│ └── home.tsx
|
|
203
|
+
└── components
|
|
204
|
+
└── Navbar
|
|
205
|
+
└── index.tsx
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
</TabItem>
|
|
209
|
+
<TabItem label="next-intl" value="next-intl">
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
.
|
|
213
|
+
├── locales
|
|
214
|
+
│ ├── en
|
|
215
|
+
│ │ ├── home.json
|
|
216
|
+
│ │ └── navbar.json
|
|
217
|
+
│ ├── fr
|
|
218
|
+
│ │ ├── home.json
|
|
219
|
+
│ │ └── navbar.json
|
|
220
|
+
│ └── es
|
|
221
|
+
│ ├── home.json
|
|
222
|
+
│ └── navbar.json
|
|
223
|
+
├── i18n.ts
|
|
224
|
+
└── src
|
|
225
|
+
├── middleware.ts
|
|
226
|
+
├── app
|
|
227
|
+
│ └── home.tsx
|
|
228
|
+
└── components
|
|
229
|
+
└── Navbar
|
|
230
|
+
└── index.tsx
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
</TabItem>
|
|
234
|
+
<TabItem label="intlayer" value="intlayer">
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
.
|
|
238
|
+
├── intlayer.config.ts
|
|
239
|
+
└── src
|
|
240
|
+
├── middleware.ts
|
|
241
|
+
├── app
|
|
242
|
+
│ └── home
|
|
243
|
+
│ └── index.tsx
|
|
244
|
+
│ └── index.content.ts
|
|
245
|
+
└── components
|
|
246
|
+
└── Navbar
|
|
247
|
+
├── index.tsx
|
|
248
|
+
└── index.content.ts
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
</TabItem>
|
|
252
|
+
</Tab>
|
|
253
|
+
|
|
254
|
+
#### Comparison
|
|
255
|
+
|
|
256
|
+
##### Configuration
|
|
257
|
+
|
|
258
|
+
Intlayer uses a centralized configuration file to set up your locale, middleware, build, etc.
|
|
259
|
+
|
|
260
|
+
##### Content declaration
|
|
261
|
+
|
|
262
|
+
The centralized type of architecture slows down the development process and makes the codebase more complex to maintain for several reasons:
|
|
263
|
+
|
|
264
|
+
1. **For any new component created, you should:**
|
|
265
|
+
- Create the new resource/namespace in the `locales` folder
|
|
266
|
+
- Remember to import the new namespace in your page
|
|
267
|
+
- Translate your content (often done manually by copy/paste from AI providers)
|
|
268
|
+
|
|
269
|
+
2. **For any change made on your components, you should:**
|
|
270
|
+
- Search for the related resource/namespace (far from the component)
|
|
271
|
+
- Translate your content
|
|
272
|
+
- Ensure your content is up to date for any locale
|
|
273
|
+
- Verify your namespace doesn't include unused keys/values
|
|
274
|
+
- Ensure the structure of your JSON files is the same for all locales
|
|
275
|
+
|
|
276
|
+
On professional projects using these solutions, localization platforms are often used to help manage the translation of your content. However, this can quickly become costly for large projects.
|
|
277
|
+
|
|
278
|
+
To solve this problem, Intlayer adopts an approach that scopes your content per-component and keeps your content close to your component, as we often do with CSS (`styled-components`), types, documentation (`storybook`), or unit tests (`jest`).
|
|
279
|
+
|
|
280
|
+
This approach allows you to:
|
|
281
|
+
|
|
282
|
+
1. **Increase the speed of development**
|
|
283
|
+
- `.content.{{ts|js|json}}` files can be created using a VSCode extension
|
|
284
|
+
- Autocompletion AI tools in your IDE (such as GitHub Copilot) can help you declare your content, reducing copy/paste
|
|
285
|
+
|
|
286
|
+
2. **Clean your codebase**
|
|
287
|
+
- Reduce the complexity
|
|
288
|
+
- Increase the maintainability
|
|
289
|
+
|
|
290
|
+
3. **Duplicate your components and their related content more easily (Example: login/register components, etc.)**
|
|
291
|
+
- By limiting the risk of impacting other components' content
|
|
292
|
+
- By copy/pasting your content from one application to another without external dependencies
|
|
293
|
+
|
|
294
|
+
4. **Avoid polluting your codebase with unused keys/values for unused components**
|
|
295
|
+
- If you don't use a component, Intlayer will not import its related content
|
|
296
|
+
- If you delete a component, you'll more easily remember to remove its related content as it will be present in the same folder
|
|
297
|
+
|
|
298
|
+
5. **Reduce reasoning cost for AI agents to declare your multilingual content**
|
|
299
|
+
- The AI agent won't have to scan your entire codebase to know where to implement your content
|
|
300
|
+
- Translations can easily be done by autocompletion AI tools in your IDE (such as GitHub Copilot)
|
|
301
|
+
|
|
302
|
+
6. **Optimize loading performance**
|
|
303
|
+
- If a component is lazy-loaded, its related content will be loaded at the same time
|
|
304
|
+
|
|
305
|
+
#### Setup and Loading Content
|
|
306
|
+
|
|
307
|
+
As mentioned previously, you must optimize how each JSON file is imported into your code.
|
|
308
|
+
How the library handles content loading is important.
|
|
309
|
+
|
|
310
|
+
<Tab defaultTab="next-intl" group='techno'>
|
|
311
|
+
<TabItem label="next-i18next" value="next-i18next">
|
|
312
|
+
|
|
313
|
+
```tsx fileName="next-i18next.config.js"
|
|
314
|
+
module.exports = {
|
|
315
|
+
i18n: {
|
|
316
|
+
locales: ["en", "fr", "es"],
|
|
317
|
+
defaultLocale: "en",
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
```tsx fileName="src/app/_app.tsx"
|
|
323
|
+
import { appWithTranslation } from "next-i18next";
|
|
324
|
+
|
|
325
|
+
const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />;
|
|
326
|
+
|
|
327
|
+
export default appWithTranslation(MyApp);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
```tsx fileName="src/app/[locale]/about/page.tsx"
|
|
331
|
+
import type { GetStaticProps } from "next";
|
|
332
|
+
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
|
333
|
+
import { useTranslation } from "next-i18next";
|
|
334
|
+
import { I18nextProvider, initReactI18next } from "react-i18next";
|
|
335
|
+
import { createInstance } from "i18next";
|
|
336
|
+
import { ClientComponent, ServerComponent } from "@components";
|
|
337
|
+
|
|
338
|
+
export default function HomePage() {
|
|
339
|
+
// Déclarez explicitement le namespace utilisé par ce composant
|
|
340
|
+
const resources = await loadMessagesFor(locale); // your loader (JSON, etc.)
|
|
341
|
+
|
|
342
|
+
const i18n = createInstance();
|
|
343
|
+
i18n.use(initReactI18next).init({
|
|
344
|
+
lng: locale,
|
|
345
|
+
fallbackLng: "en",
|
|
346
|
+
resources,
|
|
347
|
+
ns: ["common", "about"],
|
|
348
|
+
defaultNS: "common",
|
|
349
|
+
interpolation: { escapeValue: false },
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const { t } = useTranslation("about");
|
|
353
|
+
|
|
354
|
+
return (
|
|
355
|
+
<I18nextProvider i18n={i18n}>
|
|
356
|
+
<main>
|
|
357
|
+
<h1>{t("title")}</h1>
|
|
358
|
+
<ClientComponent />
|
|
359
|
+
<ServerComponent />
|
|
360
|
+
</main>
|
|
361
|
+
</I18nextProvider>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export const getStaticProps: GetStaticProps = async ({ locale }) => {
|
|
366
|
+
// Ne préchargez que les namespaces nécessaires à CETTE page
|
|
367
|
+
return {
|
|
368
|
+
props: {
|
|
369
|
+
...(await serverSideTranslations(locale ?? "en", ["common", "about"])),
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
};
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
</TabItem>
|
|
376
|
+
<TabItem label="next-intl" value="next-intl">
|
|
377
|
+
|
|
378
|
+
```tsx fileName="i18n.ts"
|
|
379
|
+
import { getRequestConfig } from "next-intl/server";
|
|
380
|
+
import { notFound } from "next/navigation";
|
|
381
|
+
|
|
382
|
+
// Can be imported from a shared config
|
|
383
|
+
const locales = ["en", "fr", "es"];
|
|
384
|
+
|
|
385
|
+
export default getRequestConfig(async ({ locale }) => {
|
|
386
|
+
// Validate that the incoming `locale` parameter is valid
|
|
387
|
+
if (!locales.includes(locale as any)) notFound();
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
messages: (await import(`../messages/${locale}.json`)).default,
|
|
391
|
+
};
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
```tsx fileName="src/app/[locale]/about/layout.tsx"
|
|
396
|
+
import { NextIntlClientProvider } from "next-intl";
|
|
397
|
+
import { getMessages } from "next-intl/server";
|
|
398
|
+
import pick from "lodash/pick";
|
|
399
|
+
|
|
400
|
+
export default async function LocaleLayout({
|
|
401
|
+
children,
|
|
402
|
+
params,
|
|
403
|
+
}: {
|
|
404
|
+
children: React.ReactNode;
|
|
405
|
+
params: { locale: string };
|
|
406
|
+
}) {
|
|
407
|
+
const { locale } = params;
|
|
408
|
+
// Les messages sont chargés côté serveur via src/i18n/request.ts
|
|
409
|
+
// (voir docs next-intl). Ici on ne pousse au client qu'un sous-ensemble
|
|
410
|
+
// utile aux composants client (optimisation du payload).
|
|
411
|
+
const messages = await getMessages();
|
|
412
|
+
const clientMessages = pick(messages, ["common", "about"]);
|
|
413
|
+
|
|
414
|
+
return (
|
|
415
|
+
<html lang={locale}>
|
|
416
|
+
<body>
|
|
417
|
+
<NextIntlClientProvider locale={locale} messages={clientMessages}>
|
|
418
|
+
{children}
|
|
419
|
+
</NextIntlClientProvider>
|
|
420
|
+
</body>
|
|
421
|
+
</html>
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
```tsx fileName="src/app/[locale]/about/page.tsx"
|
|
427
|
+
import { getTranslations } from "next-intl/server";
|
|
428
|
+
import { ClientComponent, ServerComponent } from "@components";
|
|
429
|
+
|
|
430
|
+
export default async function LandingPage({
|
|
431
|
+
params,
|
|
432
|
+
}: {
|
|
433
|
+
params: { locale: string };
|
|
434
|
+
}) {
|
|
435
|
+
// Chargement strictement côté serveur (pas hydraté au client)
|
|
436
|
+
const t = await getTranslations("about");
|
|
437
|
+
|
|
438
|
+
return (
|
|
439
|
+
<main>
|
|
440
|
+
<h1>{t("title")}</h1>
|
|
441
|
+
<ClientComponent />
|
|
442
|
+
<ServerComponent />
|
|
443
|
+
</main>
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
</TabItem>
|
|
449
|
+
<TabItem label="intlayer" value="intlayer">
|
|
450
|
+
|
|
451
|
+
```tsx fileName="intlayer.config.ts"
|
|
452
|
+
export default {
|
|
453
|
+
internationalization: {
|
|
454
|
+
locales: ["en", "fr", "es"],
|
|
455
|
+
defaultLocale: "en",
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
```tsx fileName="src/app/[locale]/layout.tsx"
|
|
461
|
+
import { getHTMLTextDir } from "intlayer";
|
|
462
|
+
import {
|
|
463
|
+
IntlayerClientProvider,
|
|
464
|
+
generateStaticParams,
|
|
465
|
+
type NextLayoutIntlayer,
|
|
466
|
+
} from "next-intlayer";
|
|
467
|
+
|
|
468
|
+
export const dynamic = "force-static";
|
|
469
|
+
|
|
470
|
+
const LandingLayout: NextLayoutIntlayer = async ({ children, params }) => {
|
|
471
|
+
const { locale } = await params;
|
|
472
|
+
|
|
473
|
+
return (
|
|
474
|
+
<html lang={locale} dir={getHTMLTextDir(locale)}>
|
|
475
|
+
<IntlayerClientProvider locale={locale}>
|
|
476
|
+
{children}
|
|
477
|
+
</IntlayerClientProvider>
|
|
478
|
+
</html>
|
|
479
|
+
);
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
export default LandingLayout;
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
```tsx fileName="src/app/[locale]/about/page.tsx"
|
|
486
|
+
import { PageContent } from "@components/PageContent";
|
|
487
|
+
import type { NextPageIntlayer } from "next-intlayer";
|
|
488
|
+
import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
|
|
489
|
+
import { ClientComponent, ServerComponent } from "@components";
|
|
490
|
+
|
|
491
|
+
const LandingPage: NextPageIntlayer = async ({ params }) => {
|
|
492
|
+
const { locale } = await params;
|
|
493
|
+
const { title } = useIntlayer("about", locale);
|
|
494
|
+
|
|
495
|
+
return (
|
|
496
|
+
<IntlayerServerProvider locale={locale}>
|
|
497
|
+
<main>
|
|
498
|
+
<h1>{title}</h1>
|
|
499
|
+
<ClientComponent />
|
|
500
|
+
<ServerComponent />
|
|
501
|
+
</main>
|
|
502
|
+
</IntlayerServerProvider>
|
|
503
|
+
);
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
export default LandingPage;
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
</TabItem>
|
|
510
|
+
</Tab>
|
|
511
|
+
|
|
512
|
+
#### Comparison
|
|
513
|
+
|
|
514
|
+
In comparison to other solutions, Intlayer uses plugins to optimize the import of content at build time.
|
|
515
|
+
|
|
516
|
+
This means that the client context can be stored in the root layout, to reuse only one instance of the provider for all pages.
|
|
517
|
+
|
|
518
|
+
In comparison, the other solutions require rebuilding a custom provider for each page.
|
|
519
|
+
|
|
520
|
+
Intlayer also provides a Provider for your server components. We'll see why later.
|
|
521
|
+
|
|
522
|
+
### Usage in a client component
|
|
523
|
+
|
|
524
|
+
Let's take an example of a client component rendering a counter.
|
|
525
|
+
|
|
526
|
+
<Tab defaultTab="next-intl" group='techno'>
|
|
527
|
+
<TabItem label="next-i18next" value="next-i18next">
|
|
528
|
+
|
|
529
|
+
**Translations (must be real JSON in `public/locales/...`)**
|
|
530
|
+
|
|
531
|
+
```json fileName="public/locales/en/about.json"
|
|
532
|
+
{
|
|
533
|
+
"counter": {
|
|
534
|
+
"label": "Counter",
|
|
535
|
+
"increment": "Increment"
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
```json fileName="public/locales/fr/about.json"
|
|
541
|
+
{
|
|
542
|
+
"counter": {
|
|
543
|
+
"label": "Compteur",
|
|
544
|
+
"increment": "Incrémenter"
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
**Client component**
|
|
550
|
+
|
|
551
|
+
```tsx fileName="src/components/ClientComponentExample.tsx"
|
|
552
|
+
"use client";
|
|
553
|
+
|
|
554
|
+
import React, { useMemo, useState } from "react";
|
|
555
|
+
import { useTranslation } from "next-i18next";
|
|
556
|
+
|
|
557
|
+
export default function ClientComponentExample() {
|
|
558
|
+
const { t, i18n } = useTranslation("about");
|
|
559
|
+
const [count, setCount] = useState(0);
|
|
560
|
+
|
|
561
|
+
// next-i18next doesn't expose useNumber; use Intl.NumberFormat
|
|
562
|
+
const numberFormat = new Intl.NumberFormat(i18n.language);
|
|
563
|
+
|
|
564
|
+
return (
|
|
565
|
+
<div>
|
|
566
|
+
<p>{numberFormat.format(count)}</p>
|
|
567
|
+
<button
|
|
568
|
+
aria-label={t("counter.label")}
|
|
569
|
+
onClick={() => setCount((count) => count + 1)}
|
|
570
|
+
>
|
|
571
|
+
{t("counter.increment")}
|
|
572
|
+
</button>
|
|
573
|
+
</div>
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
> Don't forget to add "about" namespace on the page serverSideTranslations
|
|
579
|
+
> We take here the version of react 19.x.x, but for lower versions, you will need to use useMemo to store the instance of the formatter as it's a heavy function
|
|
580
|
+
|
|
581
|
+
</TabItem>
|
|
582
|
+
<TabItem label="next-intl" value="next-intl">
|
|
583
|
+
|
|
584
|
+
**Translations (shape reused; load them into next-intl messages as you prefer)**
|
|
585
|
+
|
|
586
|
+
```json fileName="locales/en/about.json"
|
|
587
|
+
{
|
|
588
|
+
"counter": {
|
|
589
|
+
"label": "Counter",
|
|
590
|
+
"increment": "Increment"
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
```json fileName="locales/fr/about.json"
|
|
596
|
+
{
|
|
597
|
+
"counter": {
|
|
598
|
+
"label": "Compteur",
|
|
599
|
+
"increment": "Incrémenter"
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
**Client component**
|
|
605
|
+
|
|
606
|
+
```tsx fileName="src/components/ClientComponentExample.tsx"
|
|
607
|
+
"use client";
|
|
608
|
+
|
|
609
|
+
import React, { useState } from "react";
|
|
610
|
+
import { useTranslations, useFormatter } from "next-intl";
|
|
611
|
+
|
|
612
|
+
export default function ClientComponentExample() {
|
|
613
|
+
// Scope directly to the nested object
|
|
614
|
+
const t = useTranslations("about.counter");
|
|
615
|
+
const format = useFormatter();
|
|
616
|
+
const [count, setCount] = useState(0);
|
|
617
|
+
|
|
618
|
+
return (
|
|
619
|
+
<div>
|
|
620
|
+
<p>{format.number(count)}</p>
|
|
621
|
+
<button
|
|
622
|
+
aria-label={t("label")}
|
|
623
|
+
onClick={() => setCount((count) => count + 1)}
|
|
624
|
+
>
|
|
625
|
+
{t("increment")}
|
|
626
|
+
</button>
|
|
627
|
+
</div>
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
> Don't forget to add "about" message on the page client message
|
|
633
|
+
|
|
634
|
+
</TabItem>
|
|
635
|
+
<TabItem label="intlayer" value="intlayer">
|
|
636
|
+
|
|
637
|
+
**Content**
|
|
83
638
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
639
|
+
```ts fileName="src/components/ClientComponentExample/index.content.ts"
|
|
640
|
+
import { t, type Dictionary } from "intlayer";
|
|
641
|
+
|
|
642
|
+
const counterContent = {
|
|
643
|
+
key: "counter",
|
|
644
|
+
content: {
|
|
645
|
+
label: t({ en: "Counter", fr: "Compteur" }),
|
|
646
|
+
increment: t({ en: "Increment", fr: "Incrémenter" }),
|
|
647
|
+
},
|
|
648
|
+
} satisfies Dictionary;
|
|
649
|
+
|
|
650
|
+
export default counterContent;
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
**Client component**
|
|
654
|
+
|
|
655
|
+
```tsx fileName="src/components/ClientComponentExample/index.tsx"
|
|
656
|
+
"use client";
|
|
657
|
+
|
|
658
|
+
import React, { useState } from "react";
|
|
659
|
+
import { useNumber, useIntlayer } from "next-intlayer";
|
|
660
|
+
|
|
661
|
+
export default function ClientComponentExample() {
|
|
662
|
+
const [count, setCount] = useState(0);
|
|
663
|
+
const { label, increment } = useIntlayer("counter"); // returns strings
|
|
664
|
+
const { number } = useNumber();
|
|
665
|
+
|
|
666
|
+
return (
|
|
667
|
+
<div>
|
|
668
|
+
<p>{number(count)}</p>
|
|
669
|
+
<button aria-label={label} onClick={() => setCount((count) => count + 1)}>
|
|
670
|
+
{increment}
|
|
671
|
+
</button>
|
|
672
|
+
</div>
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
</TabItem>
|
|
678
|
+
</Tab>
|
|
679
|
+
|
|
680
|
+
#### Comparison
|
|
681
|
+
|
|
682
|
+
- **Number formatting**
|
|
683
|
+
- **next-i18next**: no `useNumber`; use `Intl.NumberFormat` (or i18next-icu).
|
|
684
|
+
- **next-intl**: `useFormatter().number(value)`.
|
|
685
|
+
- **Intlayer**: `useNumber()` built-in.
|
|
686
|
+
|
|
687
|
+
- **Keys**
|
|
688
|
+
- Keep a nested structure (`about.counter.label`) and scope your hook accordingly (`useTranslation("about")` + `t("counter.label")` or `useTranslations("about.counter")` + `t("label")`).
|
|
689
|
+
|
|
690
|
+
- **File locations**
|
|
691
|
+
- **next-i18next** expects JSON in `public/locales/{lng}/{ns}.json`.
|
|
692
|
+
- **next-intl** is flexible; load messages however you configure.
|
|
693
|
+
- **Intlayer** stores content in TS/JS dictionaries and resolves by key.
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
### Usage in a server component
|
|
698
|
+
|
|
699
|
+
We will take the case of a UI component. This component is a server component, and should be able to be inserted as a child of a client component. (page (server component) -> client component -> server component). As this component can be inserted as a child of a client component, it cannot be async.
|
|
700
|
+
|
|
701
|
+
<Tab defaultTab="next-intl" group='techno'>
|
|
702
|
+
<TabItem label="next-i18next" value="next-i18next">
|
|
703
|
+
|
|
704
|
+
```tsx fileName="src/pages/about.tsx"
|
|
705
|
+
import React from "react";
|
|
706
|
+
import type { GetStaticProps } from "next";
|
|
707
|
+
import { useTranslation } from "next-i18next";
|
|
708
|
+
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
|
709
|
+
|
|
710
|
+
type ServerComponentProps = {
|
|
711
|
+
count: number;
|
|
712
|
+
t: (key: string) => string;
|
|
713
|
+
format: (value: number) => string;
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
export default function ServerComponent({
|
|
717
|
+
t,
|
|
718
|
+
format,
|
|
719
|
+
count,
|
|
720
|
+
}: ServerComponentProps) {
|
|
721
|
+
return (
|
|
722
|
+
<div>
|
|
723
|
+
<p>{format(count)}</p>
|
|
724
|
+
<button aria-label={t("counter.label")}>{t("counter.increment")}</button>
|
|
725
|
+
</div>
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
> As the server component cannot be async, you need to pass the translations and formatter function as props.
|
|
731
|
+
>
|
|
732
|
+
> - `const { t, i18n } = useTranslation("about");`
|
|
733
|
+
> - `const formatted = new Intl.NumberFormat(i18n.language).format(initialCount);`
|
|
734
|
+
|
|
735
|
+
</TabItem>
|
|
736
|
+
<TabItem label="next-intl" value="next-intl">
|
|
737
|
+
|
|
738
|
+
```tsx fileName="src/components/ServerComponent.tsx"
|
|
739
|
+
import { getTranslations, getFormatter } from "next-intl/server";
|
|
740
|
+
|
|
741
|
+
export default async function ServerComponent({
|
|
742
|
+
t,
|
|
743
|
+
format,
|
|
744
|
+
count,
|
|
745
|
+
}: {
|
|
746
|
+
t: (key: string) => string;
|
|
747
|
+
format: (value: number) => string;
|
|
748
|
+
count: number;
|
|
749
|
+
}) {
|
|
750
|
+
return (
|
|
751
|
+
<div>
|
|
752
|
+
<p>{format.number(count)}</p>
|
|
753
|
+
<button aria-label={t("label")}>{t("increment")}</button>
|
|
754
|
+
</div>
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
> As the server component cannot be async, you need to pass the translations and formatter function as props.
|
|
760
|
+
>
|
|
761
|
+
> - `const t = await getTranslations("about.counter");`
|
|
762
|
+
> - `const format = await getFormatter();`
|
|
763
|
+
|
|
764
|
+
</TabItem>
|
|
765
|
+
<TabItem label="intlayer" value="intlayer">
|
|
766
|
+
|
|
767
|
+
```tsx fileName="src/components/ServerComponent.tsx"
|
|
768
|
+
import { useIntlayer, useNumber } from "next-intlayer/server";
|
|
769
|
+
|
|
770
|
+
const ServerComponent = ({ count }: { count: number }) => {
|
|
771
|
+
const { label, increment } = useIntlayer("counter");
|
|
772
|
+
const { number } = useNumber();
|
|
773
|
+
|
|
774
|
+
return (
|
|
775
|
+
<div>
|
|
776
|
+
<p>{number(count)}</p>
|
|
777
|
+
<button aria-label={label}>{increment}</button>
|
|
778
|
+
</div>
|
|
779
|
+
);
|
|
780
|
+
};
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
</TabItem>
|
|
784
|
+
</Tab>
|
|
785
|
+
|
|
786
|
+
> Intlayer exposes **server-safe** hooks via `next-intlayer/server`. To work, `useIntlayer` and `useNumber` use hooks-like syntax, similar to the client hooks, but depend under the hood on the server context (`IntlayerServerProvider`).
|
|
787
|
+
|
|
788
|
+
### Metadata / Sitemap / Robots
|
|
789
|
+
|
|
790
|
+
Translating content is great. But people usually forget that the main goal of internationalization is to make your website more visible to the world. I18n is an incredible lever to improve your website visibility.
|
|
791
|
+
|
|
792
|
+
Here's a list of good practices regarding multilingual SEO.
|
|
793
|
+
|
|
794
|
+
- set hreflang meta tags in the `<head>` tag
|
|
795
|
+
> It helps search engines to understand what languages are available on the page
|
|
796
|
+
- list all pages translations in the sitemap.xml using `http://www.w3.org/1999/xhtml` XML schema
|
|
797
|
+
>
|
|
798
|
+
- do not forget to exclude prefixed pages from the robots.txt (e.g. `/dashboard`, and `/fr/dashboard`, `/es/dashboard`)
|
|
799
|
+
>
|
|
800
|
+
- use custom Link component to redirect to the most localized page (e.g. in french `<a href="/fr/about">A propos</a>` )
|
|
801
|
+
>
|
|
802
|
+
|
|
803
|
+
Developers often forget to properly reference their pages across locales.
|
|
804
|
+
|
|
805
|
+
<Tab defaultTab="next-intl" group='techno'>
|
|
806
|
+
|
|
807
|
+
<TabItem label="next-i18next" value="next-i18next">
|
|
808
|
+
|
|
809
|
+
```ts fileName="i18n.config.ts"
|
|
810
|
+
export const locales = ["en", "fr"] as const;
|
|
811
|
+
export type Locale = (typeof locales)[number];
|
|
812
|
+
export const defaultLocale: Locale = "en";
|
|
813
|
+
|
|
814
|
+
export function localizedPath(locale: string, path: string) {
|
|
815
|
+
return locale === defaultLocale ? path : "/" + locale + path;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const ORIGIN = "https://example.com";
|
|
819
|
+
export function abs(locale: string, path: string) {
|
|
820
|
+
return ORIGIN + localizedPath(locale, path);
|
|
821
|
+
}
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
```tsx fileName="src/app/[locale]/about/layout.tsx"
|
|
825
|
+
import type { Metadata } from "next";
|
|
826
|
+
import { locales, defaultLocale, localizedPath } from "@/i18n.config";
|
|
827
|
+
|
|
828
|
+
export async function generateMetadata({
|
|
829
|
+
params,
|
|
830
|
+
}: {
|
|
831
|
+
params: { locale: string };
|
|
832
|
+
}): Promise<Metadata> {
|
|
833
|
+
const { locale } = params;
|
|
834
|
+
|
|
835
|
+
// Dynamically import the correct JSON file
|
|
836
|
+
const messages = (
|
|
837
|
+
await import("@/../public/locales/" + locale + "/about.json")
|
|
838
|
+
).default;
|
|
839
|
+
|
|
840
|
+
const languages = Object.fromEntries(
|
|
841
|
+
locales.map((locale) => [locale, localizedPath(locale, "/about")])
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
return {
|
|
845
|
+
title: messages.title,
|
|
846
|
+
description: messages.description,
|
|
847
|
+
alternates: {
|
|
848
|
+
canonical: localizedPath(locale, "/about"),
|
|
849
|
+
languages: { ...languages, "x-default": "/about" },
|
|
850
|
+
},
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
export default async function AboutPage() {
|
|
855
|
+
return <h1>About</h1>;
|
|
856
|
+
}
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
```ts fileName="src/app/sitemap.ts"
|
|
860
|
+
import type { MetadataRoute } from "next";
|
|
861
|
+
import { locales, defaultLocale, abs } from "@/i18n.config";
|
|
862
|
+
|
|
863
|
+
export default function sitemap(): MetadataRoute.Sitemap {
|
|
864
|
+
const languages = Object.fromEntries(
|
|
865
|
+
locales.map((locale) => [locale, abs(locale, "/about")])
|
|
866
|
+
);
|
|
867
|
+
return [
|
|
868
|
+
{
|
|
869
|
+
url: abs(defaultLocale, "/about"),
|
|
870
|
+
lastModified: new Date(),
|
|
871
|
+
changeFrequency: "monthly",
|
|
872
|
+
priority: 0.7,
|
|
873
|
+
alternates: { languages },
|
|
874
|
+
},
|
|
875
|
+
];
|
|
876
|
+
}
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
```ts fileName="src/app/robots.ts"
|
|
880
|
+
import type { MetadataRoute } from "next";
|
|
881
|
+
import { locales, defaultLocale, localizedPath } from "@/i18n.config";
|
|
882
|
+
|
|
883
|
+
const ORIGIN = "https://example.com";
|
|
884
|
+
|
|
885
|
+
const expandAllLocales = (path: string) => [
|
|
886
|
+
localizedPath(defaultLocale, path),
|
|
887
|
+
...locales
|
|
888
|
+
.filter((locale) => locale !== defaultLocale)
|
|
889
|
+
.map((locale) => localizedPath(locale, path)),
|
|
890
|
+
];
|
|
891
|
+
|
|
892
|
+
export default function robots(): MetadataRoute.Robots {
|
|
893
|
+
const disallow = [
|
|
894
|
+
...expandAllLocales("/dashboard"),
|
|
895
|
+
...expandAllLocales("/admin"),
|
|
896
|
+
];
|
|
897
|
+
|
|
898
|
+
return {
|
|
899
|
+
rules: { userAgent: "*", allow: ["/"], disallow },
|
|
900
|
+
host: ORIGIN,
|
|
901
|
+
sitemap: ORIGIN + "/sitemap.xml",
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
</TabItem>
|
|
907
|
+
<TabItem label="next-intl" value="next-intl">
|
|
908
|
+
|
|
909
|
+
```tsx fileName="src/app/[locale]/about/layout.tsx"
|
|
910
|
+
import type { Metadata } from "next";
|
|
911
|
+
import { locales, defaultLocale } from "@/i18n";
|
|
912
|
+
import { getTranslations, unstable_setRequestLocale } from "next-intl/server";
|
|
913
|
+
|
|
914
|
+
function localizedPath(locale: string, path: string) {
|
|
915
|
+
return locale === defaultLocale ? path : "/" + locale + path;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
export async function generateMetadata({
|
|
919
|
+
params,
|
|
920
|
+
}: {
|
|
921
|
+
params: { locale: string };
|
|
922
|
+
}): Promise<Metadata> {
|
|
923
|
+
const { locale } = params;
|
|
924
|
+
const t = await getTranslations({ locale, namespace: "about" });
|
|
925
|
+
|
|
926
|
+
const url = "/about";
|
|
927
|
+
const languages = Object.fromEntries(
|
|
928
|
+
locales.map((locale) => [locale, localizedPath(locale, url)])
|
|
929
|
+
);
|
|
930
|
+
|
|
931
|
+
return {
|
|
932
|
+
title: t("title"),
|
|
933
|
+
description: t("description"),
|
|
934
|
+
alternates: {
|
|
935
|
+
canonical: localizedPath(locale, url),
|
|
936
|
+
languages: { ...languages, "x-default": url },
|
|
937
|
+
},
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// ... Rest of the page code
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
```tsx fileName="src/app/sitemap.ts"
|
|
945
|
+
import type { MetadataRoute } from "next";
|
|
946
|
+
import { locales, defaultLocale } from "@/i18n";
|
|
947
|
+
|
|
948
|
+
const origin = "https://example.com";
|
|
949
|
+
|
|
950
|
+
const formatterLocalizedPath = (locale: string, path: string) =>
|
|
951
|
+
locale === defaultLocale ? origin + path : origin + "/" + locale + path;
|
|
952
|
+
|
|
953
|
+
export default function sitemap(): MetadataRoute.Sitemap {
|
|
954
|
+
const aboutLanguages = Object.fromEntries(
|
|
955
|
+
locales.map((l) => [l, formatterLocalizedPath(l, "/about")])
|
|
956
|
+
);
|
|
957
|
+
|
|
958
|
+
return [
|
|
959
|
+
{
|
|
960
|
+
url: formatterLocalizedPath(defaultLocale, "/about"),
|
|
961
|
+
lastModified: new Date(),
|
|
962
|
+
changeFrequency: "monthly",
|
|
963
|
+
priority: 0.7,
|
|
964
|
+
alternates: { languages: aboutLanguages },
|
|
965
|
+
},
|
|
966
|
+
];
|
|
967
|
+
}
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
```tsx fileName="src/app/robots.ts"
|
|
971
|
+
import type { MetadataRoute } from "next";
|
|
972
|
+
import { locales, defaultLocale } from "@/i18n";
|
|
973
|
+
|
|
974
|
+
const origin = "https://example.com";
|
|
975
|
+
const withAllLocales = (path: string) => [
|
|
976
|
+
path,
|
|
977
|
+
...locales
|
|
978
|
+
.filter((locale) => locale !== defaultLocale)
|
|
979
|
+
.map((locale) => "/" + locale + path),
|
|
980
|
+
];
|
|
981
|
+
|
|
982
|
+
export default function robots(): MetadataRoute.Robots {
|
|
983
|
+
const disallow = [
|
|
984
|
+
...withAllLocales("/dashboard"),
|
|
985
|
+
...withAllLocales("/admin"),
|
|
986
|
+
];
|
|
987
|
+
|
|
988
|
+
return {
|
|
989
|
+
rules: { userAgent: "*", allow: ["/"], disallow },
|
|
990
|
+
host: origin,
|
|
991
|
+
sitemap: origin + "/sitemap.xml",
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
</TabItem>
|
|
997
|
+
<TabItem label="intlayer" value="intlayer">
|
|
998
|
+
|
|
999
|
+
```typescript fileName="src/app/[locale]/about/layout.tsx"
|
|
1000
|
+
import { getIntlayer, getMultilingualUrls } from "intlayer";
|
|
1001
|
+
import type { Metadata } from "next";
|
|
1002
|
+
import type { LocalPromiseParams } from "next-intlayer";
|
|
1003
|
+
|
|
1004
|
+
export const generateMetadata = async ({
|
|
1005
|
+
params,
|
|
1006
|
+
}: LocalPromiseParams): Promise<Metadata> => {
|
|
1007
|
+
const { locale } = await params;
|
|
1008
|
+
|
|
1009
|
+
const metadata = getIntlayer("page-metadata", locale);
|
|
1010
|
+
|
|
1011
|
+
const multilingualUrls = getMultilingualUrls("/about");
|
|
1012
|
+
|
|
1013
|
+
return {
|
|
1014
|
+
...metadata,
|
|
1015
|
+
alternates: {
|
|
1016
|
+
canonical: multilingualUrls[locale as keyof typeof multilingualUrls],
|
|
1017
|
+
languages: { ...multilingualUrls, "x-default": "/about" },
|
|
1018
|
+
},
|
|
1019
|
+
};
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
// ... Rest of the page code
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
```tsx fileName="src/app/sitemap.ts"
|
|
1026
|
+
import { getMultilingualUrls } from "intlayer";
|
|
1027
|
+
import type { MetadataRoute } from "next";
|
|
1028
|
+
|
|
1029
|
+
const sitemap = (): MetadataRoute.Sitemap => [
|
|
1030
|
+
{
|
|
1031
|
+
url: "https://example.com/about",
|
|
1032
|
+
alternates: {
|
|
1033
|
+
languages: { ...getMultilingualUrls("https://example.com/about") },
|
|
1034
|
+
},
|
|
1035
|
+
},
|
|
1036
|
+
];
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
```tsx fileName="src/app/robots.ts"
|
|
1040
|
+
import { getMultilingualUrls } from "intlayer";
|
|
1041
|
+
import type { MetadataRoute } from "next";
|
|
1042
|
+
|
|
1043
|
+
const getAllMultilingualUrls = (urls: string[]) =>
|
|
1044
|
+
urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);
|
|
1045
|
+
|
|
1046
|
+
const robots = (): MetadataRoute.Robots => ({
|
|
1047
|
+
rules: {
|
|
1048
|
+
userAgent: "*",
|
|
1049
|
+
allow: ["/"],
|
|
1050
|
+
disallow: getAllMultilingualUrls(["/dashboard"]),
|
|
1051
|
+
},
|
|
1052
|
+
host: "https://example.com",
|
|
1053
|
+
sitemap: "https://example.com/sitemap.xml",
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
export default robots;
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
</TabItem>
|
|
1060
|
+
</Tab>
|
|
1061
|
+
|
|
1062
|
+
> Intlayer provides a `getMultilingualUrls` function to generate multilingual URLs for your sitemap.
|
|
1063
|
+
|
|
1064
|
+
---
|
|
1065
|
+
|
|
1066
|
+
## TypeScript & safety
|
|
1067
|
+
|
|
1068
|
+
<Columns>
|
|
1069
|
+
<Column>
|
|
1070
|
+
|
|
1071
|
+
**next-intl**
|
|
1072
|
+
|
|
1073
|
+
- Solid TypeScript support, but **keys aren’t strictly typed by default**; you’ll maintain safety patterns manually.
|
|
1074
|
+
|
|
1075
|
+
</Column>
|
|
1076
|
+
<Column>
|
|
1077
|
+
|
|
1078
|
+
**next-i18next**
|
|
1079
|
+
|
|
1080
|
+
- Base typings for hooks; **strict key typing requires extra tooling/config**.
|
|
1081
|
+
|
|
1082
|
+
</Column>
|
|
1083
|
+
<Column>
|
|
1084
|
+
|
|
1085
|
+
**intlayer**
|
|
1086
|
+
|
|
1087
|
+
- **Generates strict types** from your content. **IDE autocompletion** and **compile-time errors** catch typos and missing keys before deploy.
|
|
1088
|
+
|
|
1089
|
+
</Column>
|
|
1090
|
+
<Columns>
|
|
87
1091
|
|
|
88
1092
|
**Why it matters:** Strong typing shifts failures **left** (CI/build) instead of **right** (runtime).
|
|
89
1093
|
|
|
90
1094
|
---
|
|
91
1095
|
|
|
92
|
-
|
|
1096
|
+
## Missing translation handling
|
|
1097
|
+
|
|
1098
|
+
**next-intl**
|
|
1099
|
+
|
|
1100
|
+
- Relies on **runtime fallbacks** (e.g., show the key or default locale). Build doesn’t fail.
|
|
1101
|
+
|
|
1102
|
+
**next-i18next**
|
|
1103
|
+
|
|
1104
|
+
- Relies on **runtime fallbacks** (e.g., show the key or default locale). Build doesn’t fail.
|
|
1105
|
+
|
|
1106
|
+
**intlayer**
|
|
93
1107
|
|
|
94
|
-
- **
|
|
95
|
-
- **Intlayer**: **Build-time detection** with **warnings/errors** for missing locales or keys.
|
|
1108
|
+
- **Build-time detection** with **warnings/errors** for missing locales or keys.
|
|
96
1109
|
|
|
97
1110
|
**Why it matters:** Catching gaps during build prevents “mystery strings” in production and aligns with strict release gates.
|
|
98
1111
|
|
|
99
1112
|
---
|
|
100
1113
|
|
|
101
|
-
|
|
1114
|
+
## Routing, middleware & URL strategy
|
|
102
1115
|
|
|
103
|
-
|
|
104
|
-
|
|
1116
|
+
<Columns>
|
|
1117
|
+
<Column>
|
|
1118
|
+
|
|
1119
|
+
**next-intl**
|
|
1120
|
+
|
|
1121
|
+
- Works with **Next.js localized routing** on the App Router.
|
|
1122
|
+
|
|
1123
|
+
</Column>
|
|
1124
|
+
<Column>
|
|
1125
|
+
|
|
1126
|
+
**next-i18next**
|
|
1127
|
+
|
|
1128
|
+
- Works with **Next.js localized routing** on the App Router.
|
|
1129
|
+
|
|
1130
|
+
</Column>
|
|
1131
|
+
<Column>
|
|
1132
|
+
|
|
1133
|
+
**intlayer**
|
|
1134
|
+
|
|
1135
|
+
- All of the above, plus **i18n middleware** (locale detection via headers/cookies) and **helpers** to generate localized URLs and `<link rel="alternate" hreflang="…">` tags.
|
|
1136
|
+
|
|
1137
|
+
</Column>
|
|
1138
|
+
</Columns>
|
|
105
1139
|
|
|
106
1140
|
**Why it matters:** Fewer custom glue layers; **consistent UX** and **clean SEO** across locales.
|
|
107
1141
|
|
|
108
1142
|
---
|
|
109
1143
|
|
|
110
|
-
|
|
1144
|
+
## Server Components (RSC) alignment
|
|
1145
|
+
|
|
1146
|
+
<Columns>
|
|
1147
|
+
<Column>
|
|
1148
|
+
|
|
1149
|
+
**next-intl**
|
|
1150
|
+
|
|
1151
|
+
- Supports Next.js 13+. Often requires passing t-functions/formatters through component trees in hybrid setups.
|
|
1152
|
+
|
|
1153
|
+
</Column>
|
|
1154
|
+
<Column>
|
|
1155
|
+
|
|
1156
|
+
**next-i18next**
|
|
1157
|
+
|
|
1158
|
+
- Supports Next.js 13+. Similar constraints with passing translation utilities across boundaries.
|
|
1159
|
+
|
|
1160
|
+
</Column>
|
|
1161
|
+
<Column>
|
|
1162
|
+
|
|
1163
|
+
**intlayer**
|
|
111
1164
|
|
|
112
|
-
-
|
|
113
|
-
|
|
1165
|
+
- Supports Next.js 13+ and smooths the **server/client boundary** with a consistent API and RSC-oriented providers, avoiding shuttling formatters or t-functions.
|
|
1166
|
+
|
|
1167
|
+
</Column>
|
|
1168
|
+
</Columns>
|
|
114
1169
|
|
|
115
1170
|
**Why it matters:** Cleaner mental model and fewer edge cases in hybrid trees.
|
|
116
1171
|
|
|
117
1172
|
---
|
|
118
1173
|
|
|
119
|
-
|
|
1174
|
+
## DX, tooling & maintenance
|
|
120
1175
|
|
|
121
|
-
|
|
122
|
-
|
|
1176
|
+
<Columns>
|
|
1177
|
+
<Column>
|
|
123
1178
|
|
|
124
|
-
**
|
|
1179
|
+
**next-intl**
|
|
125
1180
|
|
|
126
|
-
|
|
1181
|
+
- Commonly paired with external localization platforms and editorial workflows.
|
|
1182
|
+
|
|
1183
|
+
</Column>
|
|
1184
|
+
<Column>
|
|
1185
|
+
|
|
1186
|
+
**next-i18next**
|
|
127
1187
|
|
|
128
|
-
|
|
1188
|
+
- Commonly paired with external localization platforms and editorial workflows.
|
|
129
1189
|
|
|
130
|
-
|
|
131
|
-
|
|
1190
|
+
</Column>
|
|
1191
|
+
<Column>
|
|
1192
|
+
|
|
1193
|
+
**intlayer**
|
|
1194
|
+
|
|
1195
|
+
- Ships a **free Visual Editor** and **optional CMS** (Git-friendly or externalized), plus a **VSCode extension** and **AI-assisted translations** using your own provider keys.
|
|
1196
|
+
|
|
1197
|
+
</Column>
|
|
1198
|
+
</Columns>
|
|
132
1199
|
|
|
133
1200
|
**Why it matters:** Lowers ops cost and shortens the loop between developers and content authors.
|
|
134
1201
|
|
|
135
|
-
|
|
1202
|
+
## And the winner is…
|
|
136
1203
|
|
|
137
|
-
|
|
1204
|
+
It’s not simple. Each option has trade-offs. Here’s how I see it:
|
|
138
1205
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
- **Choose Intlayer** if you value **component-scoped content**, **strict TypeScript**, **build-time guarantees**, **tree-shaking**, and **batteries-included** routing/SEO/editor tooling - especially for **Next.js App Router**, design-systems and **large, modular codebases**.
|
|
1206
|
+
<Columns>
|
|
1207
|
+
<Column>
|
|
142
1208
|
|
|
143
|
-
|
|
1209
|
+
**next-intl**
|
|
1210
|
+
|
|
1211
|
+
- simplest, lightweight, fewer decisions forced on you. If you want a **minimal** solution, you’re comfortable with centralized catalogs, and your app is **small to mid-size**.
|
|
1212
|
+
|
|
1213
|
+
</Column>
|
|
1214
|
+
<Column>
|
|
1215
|
+
|
|
1216
|
+
**next-i18next**
|
|
1217
|
+
|
|
1218
|
+
- mature, full of features, lots of community plugins, but higher setup cost. If you need **i18next’s plugin ecosystem** (e.g., advanced ICU rules via plugins) and your team already knows i18next, accepting **more configuration** for flexibility.
|
|
1219
|
+
|
|
1220
|
+
</Column>
|
|
1221
|
+
<Column>
|
|
1222
|
+
|
|
1223
|
+
**Intlayer**
|
|
1224
|
+
|
|
1225
|
+
- built for modern Next.js, with modular content, type safety, tooling, and less boilerplate. If you value **component-scoped content**, **strict TypeScript**, **build-time guarantees**, **tree-shaking**, and **batteries-included** routing/SEO/editor tooling - especially for **Next.js App Router**, design-systems and **large, modular codebases**.
|
|
144
1226
|
|
|
145
|
-
|
|
1227
|
+
</Column>
|
|
1228
|
+
</Columns>
|
|
146
1229
|
|
|
147
|
-
|
|
1230
|
+
If you prefer minimal setup and accept some manual wiring, next-intl is a good pick. If you need all the features and don't mind complexity, next-i18next works. But if you want a modern, scalable, modular solution with built tools, Intlayer aims to give you that out of the box.
|
|
148
1231
|
|
|
149
|
-
|
|
1232
|
+
> **Alternative for enterprise teams**: If you need a well-proven solution that works perfectly with established localization platforms like **Crowdin**, **Phrase**, or other professional translation management systems, consider **next-intl** or **next-i18next** for their mature ecosystem and proven integrations.
|
|
150
1233
|
|
|
151
|
-
|
|
1234
|
+
> **Future roadmap**: Intlayer also plans to develop plugins that work on top of **i18next** and **next-intl** solutions. This will give you the advantages of Intlayer for automation, syntax, and content management while keeping the security and stability provided by these established solutions in your application code.
|
|
152
1235
|
|
|
153
1236
|
---
|
|
154
1237
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
createdAt: 2024-08-14
|
|
3
|
-
updatedAt: 2025-
|
|
3
|
+
updatedAt: 2025-09-27
|
|
4
4
|
title: Interest of Intlayer
|
|
5
5
|
description: Discover the benefits and advantages of using Intlayer in your projects. Understand why Intlayer stands out among other frameworks.
|
|
6
6
|
keywords:
|
|
@@ -219,7 +219,7 @@ This approach allows you to:
|
|
|
219
219
|
## Additional features of Intlayer
|
|
220
220
|
|
|
221
221
|
| Feature | Description |
|
|
222
|
-
| ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
222
|
+
| ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ---------------------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------- |
|
|
223
223
|
|  | **Cross-Frameworks Support**<br><br>Intlayer is compatible with all major frameworks and libraries, including Next.js, React, Vite, Vue.js, Nuxt, Preact, Express, and more. |
|
|
224
224
|
|  | **JavaScript-Powered Content Management**<br><br>Harness the flexibility of JavaScript to define and manage your content efficiently. <br><br> - [Content declaration](https://intlayer.org/doc/concept/content) |
|
|
225
225
|
|  | **Per-Locale Content Declaration File**<br><br>Speed up your development by declaring your content once, before auto generation.<br><br> - [Per-Locale Content Declaration File](https://intlayer.org/doc/concept/per-locale-file) |
|
|
@@ -237,6 +237,7 @@ This approach allows you to:
|
|
|
237
237
|
|  | **MCP Server Integration**<br><br>Provides an MCP (Model Context Protocol) server for IDE automation, enabling seamless content management and i18n workflows directly within your development environment. <br><br> - [MCP Server](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/mcp_server.md) |
|
|
238
238
|
|  | **VSCode Extension**<br><br>Intlayer provides a VSCode extension to help you manage your content and translations, building your dictionaries, translating your content, and more. <br><br> - [VSCode Extension](https://intlayer.org/doc/vs-code-extension) |
|
|
239
239
|
|  | **Interoperability**<br><br>Allows interoperability with react-i18next, next-i18next, next-intl, and react-intl. <br><br> - [Intlayer and react-intl](https://intlayer.org/blog/intlayer-with-react-intl) <br> - [Intlayer and next-intl](https://intlayer.org/blog/intlayer-with-next-intl) <br> - [Intlayer and next-i18next](https://intlayer.org/blog/intlayer-with-next-i18next) |
|
|
240
|
+
| Testing Missing Translations (CLI/CI) | ✅ CLI: npx intlayer content test (CI-friendly audit) | ⚠️ Not built-in; use i18next ecosystem tools (official i18next-cli, translation-check) | ✅ @formatjs/cli supports --missing-keys | ✅ lingui compile --strict fails on missing (also failOnMissing in plugins) | ⚠️ Not built-in; docs suggest npx @lingual/i18n-check | ⚠️ Not built-in; rely on i18next tools / runtime saveMissing | ⚠️ No official CLI; use vue-i18n-extract |
|
|
240
241
|
|
|
241
242
|
## Comparison of Intlayer with other solutions
|
|
242
243
|
|
package/docs/en/intlayer_cli.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
createdAt: 2024-08-11
|
|
3
|
-
updatedAt: 2025-09-
|
|
3
|
+
updatedAt: 2025-09-26
|
|
4
4
|
title: CLI
|
|
5
5
|
description: Discover how to use the Intlayer CLI to manage your multilingual website. Follow the steps in this online documentation to set up your project in a few minutes.
|
|
6
6
|
keywords:
|
|
@@ -61,6 +61,15 @@ To see how to configure available locales, or other parameters, refer to the [co
|
|
|
61
61
|
|
|
62
62
|
## Run intlayer commands
|
|
63
63
|
|
|
64
|
+
### Check CLI version
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx intlayer --version
|
|
68
|
+
npx intlayer version
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Both commands print the installed Intlayer CLI version.
|
|
72
|
+
|
|
64
73
|
### Build dictionaries
|
|
65
74
|
|
|
66
75
|
To build your dictionaries, you can run the commands:
|
|
@@ -716,6 +725,7 @@ npx clear-npx-cache
|
|
|
716
725
|
|
|
717
726
|
| Version | Date | Changes |
|
|
718
727
|
| ------- | ---------- | ----------------------------------------------- |
|
|
728
|
+
| 6.1.2 | 2025-09-26 | Add version command |
|
|
719
729
|
| 6.1.0 | 2025-09-26 | Set verbose option to default to true using CLI |
|
|
720
730
|
| 6.1.0 | 2025-09-23 | Add watch command and with option |
|
|
721
731
|
| 6.0.1 | 2025-09-23 | Add editor command |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intlayer/docs",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Intlayer documentation",
|
|
6
6
|
"keywords": [
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"legal"
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@intlayer/config": "6.1.
|
|
52
|
-
"@intlayer/core": "6.1.
|
|
51
|
+
"@intlayer/config": "6.1.4",
|
|
52
|
+
"@intlayer/core": "6.1.4"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "^24.5.2",
|
|
@@ -63,17 +63,17 @@
|
|
|
63
63
|
"tsx": "^4.20.5",
|
|
64
64
|
"typescript": "^5.9.2",
|
|
65
65
|
"vitest": "^3.2.4",
|
|
66
|
-
"@intlayer/
|
|
67
|
-
"@intlayer/
|
|
68
|
-
"@utils/
|
|
66
|
+
"@intlayer/cli": "6.1.4",
|
|
67
|
+
"@intlayer/api": "6.1.4",
|
|
68
|
+
"@utils/tsup-config": "1.0.4",
|
|
69
69
|
"@utils/ts-config-types": "1.0.4",
|
|
70
|
-
"@utils/
|
|
70
|
+
"@utils/ts-config": "1.0.4"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@intlayer/api": "6.1.
|
|
74
|
-
"@intlayer/
|
|
75
|
-
"@intlayer/
|
|
76
|
-
"@intlayer/
|
|
73
|
+
"@intlayer/api": "6.1.4",
|
|
74
|
+
"@intlayer/config": "6.1.4",
|
|
75
|
+
"@intlayer/cli": "6.1.4",
|
|
76
|
+
"@intlayer/core": "6.1.4"
|
|
77
77
|
},
|
|
78
78
|
"engines": {
|
|
79
79
|
"node": ">=14.18"
|