@intlayer/docs 8.6.10 → 8.7.0-canary.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/blog/ar/i18n_using_next-i18next.md +1 -1
- package/blog/ar/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/de/i18n_using_next-i18next.md +1 -1
- package/blog/de/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/en/i18n_using_next-i18next.md +1 -1
- package/blog/en/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/en-GB/i18n_using_next-i18next.md +1 -1
- package/blog/en-GB/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/es/i18n_using_next-i18next.md +1 -1
- package/blog/es/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/fr/i18n_using_next-i18next.md +1 -1
- package/blog/fr/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/hi/i18n_using_next-i18next.md +1 -1
- package/blog/id/i18n_using_next-i18next.md +1 -1
- package/blog/id/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/it/i18n_using_next-i18next.md +1 -1
- package/blog/it/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/ja/i18n_using_next-i18next.md +1 -1
- package/blog/ja/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/ko/i18n_using_next-i18next.md +1 -1
- package/blog/ko/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/pl/i18n_using_next-i18next.md +1 -1
- package/blog/pl/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/pt/i18n_using_next-i18next.md +1 -1
- package/blog/pt/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/ru/i18n_using_next-i18next.md +1 -1
- package/blog/ru/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/blog/tr/i18n_using_next-i18next.md +1 -1
- package/blog/uk/i18n_using_next-i18next.md +1 -1
- package/blog/uk/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/vi/i18n_using_next-i18next.md +1 -1
- package/blog/vi/next-i18next_vs_next-intl_vs_intlayer.md +2 -2
- package/blog/zh/i18n_using_next-i18next.md +1 -1
- package/blog/zh/next-i18next_vs_next-intl_vs_intlayer.md +1 -1
- package/docs/ar/bundle_optimization.md +454 -0
- package/docs/ar/intlayer_with_next-i18next.md +1 -1
- package/docs/ar/intlayer_with_next-intl.md +1 -1
- package/docs/ar/intlayer_with_tanstack+solid.md +24 -5
- package/docs/ar/intlayer_with_tanstack.md +45 -68
- package/docs/bn/bundle_optimization.md +454 -0
- package/docs/cs/bundle_optimization.md +454 -0
- package/docs/de/bundle_optimization.md +454 -0
- package/docs/de/intlayer_with_next-i18next.md +1 -1
- package/docs/de/intlayer_with_next-intl.md +1 -1
- package/docs/de/intlayer_with_tanstack+solid.md +24 -5
- package/docs/de/intlayer_with_tanstack.md +45 -68
- package/docs/en/bundle_optimization.md +36 -8
- package/docs/en/intlayer_with_next-i18next.md +1 -1
- package/docs/en/intlayer_with_next-intl.md +1 -1
- package/docs/en/intlayer_with_tanstack+solid.md +24 -5
- package/docs/en/intlayer_with_tanstack.md +45 -68
- package/docs/en-GB/bundle_optimization.md +454 -0
- package/docs/en-GB/intlayer_with_next-i18next.md +1 -1
- package/docs/en-GB/intlayer_with_next-intl.md +1 -1
- package/docs/en-GB/intlayer_with_tanstack+solid.md +24 -5
- package/docs/en-GB/intlayer_with_tanstack.md +47 -70
- package/docs/es/bundle_optimization.md +454 -0
- package/docs/es/intlayer_with_next-i18next.md +1 -1
- package/docs/es/intlayer_with_next-intl.md +1 -1
- package/docs/es/intlayer_with_tanstack+solid.md +24 -5
- package/docs/es/intlayer_with_tanstack.md +45 -68
- package/docs/fr/bundle_optimization.md +454 -0
- package/docs/fr/intlayer_with_next-i18next.md +1 -1
- package/docs/fr/intlayer_with_next-intl.md +1 -1
- package/docs/fr/intlayer_with_tanstack+solid.md +24 -5
- package/docs/fr/intlayer_with_tanstack.md +45 -68
- package/docs/hi/bundle_optimization.md +454 -0
- package/docs/hi/intlayer_with_next-i18next.md +1 -1
- package/docs/hi/intlayer_with_next-intl.md +1 -1
- package/docs/hi/intlayer_with_tanstack+solid.md +24 -5
- package/docs/hi/intlayer_with_tanstack.md +45 -68
- package/docs/id/bundle_optimization.md +454 -0
- package/docs/id/intlayer_with_next-i18next.md +1 -1
- package/docs/id/intlayer_with_next-intl.md +1 -1
- package/docs/id/intlayer_with_tanstack+solid.md +24 -5
- package/docs/id/intlayer_with_tanstack.md +45 -68
- package/docs/it/bundle_optimization.md +454 -0
- package/docs/it/intlayer_with_next-i18next.md +1 -1
- package/docs/it/intlayer_with_next-intl.md +1 -1
- package/docs/it/intlayer_with_tanstack+solid.md +24 -5
- package/docs/it/intlayer_with_tanstack.md +45 -68
- package/docs/ja/bundle_optimization.md +454 -0
- package/docs/ja/intlayer_with_next-i18next.md +1 -1
- package/docs/ja/intlayer_with_next-intl.md +1 -1
- package/docs/ja/intlayer_with_tanstack+solid.md +24 -5
- package/docs/ja/intlayer_with_tanstack.md +45 -36
- package/docs/ko/bundle_optimization.md +454 -0
- package/docs/ko/intlayer_with_next-i18next.md +1 -1
- package/docs/ko/intlayer_with_next-intl.md +1 -1
- package/docs/ko/intlayer_with_tanstack+solid.md +24 -5
- package/docs/ko/intlayer_with_tanstack.md +45 -68
- package/docs/nl/bundle_optimization.md +454 -0
- package/docs/pl/bundle_optimization.md +454 -0
- package/docs/pl/intlayer_with_next-i18next.md +1 -1
- package/docs/pl/intlayer_with_next-intl.md +1 -1
- package/docs/pl/intlayer_with_tanstack+solid.md +24 -5
- package/docs/pl/intlayer_with_tanstack.md +45 -68
- package/docs/pt/bundle_optimization.md +454 -0
- package/docs/pt/intlayer_with_next-i18next.md +1 -1
- package/docs/pt/intlayer_with_next-intl.md +1 -1
- package/docs/pt/intlayer_with_tanstack+solid.md +24 -5
- package/docs/pt/intlayer_with_tanstack.md +45 -68
- package/docs/ru/bundle_optimization.md +454 -0
- package/docs/ru/intlayer_with_next-i18next.md +1 -1
- package/docs/ru/intlayer_with_next-intl.md +1 -1
- package/docs/ru/intlayer_with_tanstack+solid.md +24 -5
- package/docs/ru/intlayer_with_tanstack.md +45 -68
- package/docs/tr/bundle_optimization.md +454 -0
- package/docs/tr/intlayer_with_next-i18next.md +1 -1
- package/docs/tr/intlayer_with_next-intl.md +1 -1
- package/docs/tr/intlayer_with_tanstack+solid.md +24 -5
- package/docs/tr/intlayer_with_tanstack.md +45 -68
- package/docs/uk/bundle_optimization.md +454 -0
- package/docs/uk/intlayer_with_next-i18next.md +1 -1
- package/docs/uk/intlayer_with_next-intl.md +1 -1
- package/docs/uk/intlayer_with_tanstack+solid.md +24 -5
- package/docs/uk/intlayer_with_tanstack.md +45 -68
- package/docs/ur/bundle_optimization.md +454 -0
- package/docs/vi/bundle_optimization.md +454 -0
- package/docs/vi/intlayer_with_next-i18next.md +1 -1
- package/docs/vi/intlayer_with_next-intl.md +1 -1
- package/docs/vi/intlayer_with_tanstack+solid.md +24 -5
- package/docs/vi/intlayer_with_tanstack.md +45 -68
- package/docs/zh/bundle_optimization.md +454 -0
- package/docs/zh/intlayer_with_next-i18next.md +1 -1
- package/docs/zh/intlayer_with_next-intl.md +1 -1
- package/docs/zh/intlayer_with_tanstack+solid.md +24 -5
- package/docs/zh/intlayer_with_tanstack.md +45 -68
- package/package.json +7 -7
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2025-11-25
|
|
3
|
+
updatedAt: 2026-04-08
|
|
4
|
+
title: Optimising i18n Bundle Size & Performance
|
|
5
|
+
description: Reduce application bundle size by optimising internationalisation (i18n) content. Learn how to leverage tree shaking and lazy loading for dictionaries with Intlayer.
|
|
6
|
+
keywords:
|
|
7
|
+
- Bundle Optimisation
|
|
8
|
+
- Content Automation
|
|
9
|
+
- Dynamic Content
|
|
10
|
+
- Intlayer
|
|
11
|
+
- Next.js
|
|
12
|
+
- JavaScript
|
|
13
|
+
- React
|
|
14
|
+
slugs:
|
|
15
|
+
- doc
|
|
16
|
+
- concept
|
|
17
|
+
- bundle-optimization
|
|
18
|
+
history:
|
|
19
|
+
- version: 8.7.0
|
|
20
|
+
date: 2026-04-08
|
|
21
|
+
changes: "Add `minify` and `purge` options to the build configuration"
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# Optimising i18n Bundle Size & Performance
|
|
25
|
+
|
|
26
|
+
One of the most common challenges with traditional i18n solutions relying on JSON files is managing content size. If developers do not manually separate content into namespaces, users often end up downloading translations for every page and potentially every language just to view a single page.
|
|
27
|
+
|
|
28
|
+
For example, an application with 10 pages translated into 10 languages might result in a user downloading the content of 100 pages, even though they only need **one** (the current page in the current language). This leads to wasted bandwidth and slower load times.
|
|
29
|
+
|
|
30
|
+
**Intlayer solves this problem through build-time optimisation.** It analyses your code to detect which dictionaries are actually used per component and reinjects only the necessary content into your bundle.
|
|
31
|
+
|
|
32
|
+
## Table of Contents
|
|
33
|
+
|
|
34
|
+
<TOC />
|
|
35
|
+
|
|
36
|
+
## Scan your bundle
|
|
37
|
+
|
|
38
|
+
Analysing your bundle is the first step in identifying "heavy" JSON files and code-splitting opportunities. These tools generate a visual treemap of your application's compiled code, allowing you to see exactly which libraries are consuming the most space.
|
|
39
|
+
|
|
40
|
+
<Tabs>
|
|
41
|
+
<Tab value="vite">
|
|
42
|
+
|
|
43
|
+
### Vite / Rollup
|
|
44
|
+
|
|
45
|
+
Vite uses Rollup under the hood. The `rollup-plugin-visualizer` generates an interactive HTML file showing the size of every module in your graph.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install -D rollup-plugin-visualizer
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```typescript fileName="vite.config.ts"
|
|
52
|
+
import { defineConfig } from "vite";
|
|
53
|
+
import { visualizer } from "rollup-plugin-visualizer";
|
|
54
|
+
|
|
55
|
+
export default defineConfig({
|
|
56
|
+
plugins: [
|
|
57
|
+
visualizer({
|
|
58
|
+
open: true, // Automatically open the report in your browser
|
|
59
|
+
filename: "stats.html",
|
|
60
|
+
gzipSize: true,
|
|
61
|
+
brotliSize: true,
|
|
62
|
+
}),
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
</Tab>
|
|
68
|
+
<Tab value="nextjs (turbopack)">
|
|
69
|
+
|
|
70
|
+
### Next.js (Turbopack)
|
|
71
|
+
|
|
72
|
+
For projects using the App Router and Turbopack, Next.js provides a built-in experimental analyser that requires no extra dependencies.
|
|
73
|
+
|
|
74
|
+
```bash packageManager='npm'
|
|
75
|
+
npx next experimental-analyze
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```bash packageManager='yarn'
|
|
79
|
+
yarn next experimental-analyze
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```bash packageManager='pnpm'
|
|
83
|
+
pnpm next experimental-analyze
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```bash packageManager='bun'
|
|
87
|
+
bun next experimental-analyze
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
</Tab>
|
|
91
|
+
<Tab value="nextjs (Webpack)">
|
|
92
|
+
|
|
93
|
+
### Next.js (Webpack)
|
|
94
|
+
|
|
95
|
+
If you are using the default Webpack bundler in Next.js, use the official bundle analyser. Trigger it by setting an environment variable during your build.
|
|
96
|
+
|
|
97
|
+
```bash packageManager='npm'
|
|
98
|
+
npm install -D @next/bundle-analyzer
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```bash packageManager='yarn'
|
|
102
|
+
yarn add -D @next/bundle-analyzer
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```bash packageManager='pnpm'
|
|
106
|
+
pnpm add -D @next/bundle-analyzer
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```bash packageManager='bun'
|
|
110
|
+
bun add -d @next/bundle-analyzer
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```javascript fileName="next.config.js"
|
|
114
|
+
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
|
115
|
+
enabled: process.env.ANALYZE === "true",
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
module.exports = withBundleAnalyzer({
|
|
119
|
+
// Your Next.js config
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Usage:**
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
ANALYZE=true npm run build
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
</Tab>
|
|
130
|
+
<Tab value="Webpack (CRA / Angular / etc)">
|
|
131
|
+
|
|
132
|
+
### Standard Webpack
|
|
133
|
+
|
|
134
|
+
For Create React App (ejected), Angular, or custom Webpack setups, use the industry-standard `webpack-bundle-analyzer`.
|
|
135
|
+
|
|
136
|
+
```bash packageManager='npm'
|
|
137
|
+
npm install -D webpack-bundle-analyzer
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```bash packageManager='yarn'
|
|
141
|
+
yarn add -D webpack-bundle-analyzer
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```bash packageManager='pnpm'
|
|
145
|
+
pnpm add -D webpack-bundle-analyzer
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```bash packageManager='bun'
|
|
149
|
+
bun add -d webpack-bundle-analyzer
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```typescript fileName="webpack.config.ts
|
|
153
|
+
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
|
|
154
|
+
|
|
155
|
+
export default {
|
|
156
|
+
plugins: [
|
|
157
|
+
new BundleAnalyzerPlugin({
|
|
158
|
+
analyzerMode: "static",
|
|
159
|
+
reportFilename: "bundle-analyzer.html",
|
|
160
|
+
openAnalyzer: false,
|
|
161
|
+
}),
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
</Tab>
|
|
167
|
+
</Tabs>
|
|
168
|
+
|
|
169
|
+
## How It Works
|
|
170
|
+
|
|
171
|
+
Intlayer uses a **per-component approach**. Unlike global JSON files, your content is defined alongside or within your components. During the build process, Intlayer:
|
|
172
|
+
|
|
173
|
+
1. **Analyses** your code to find `useIntlayer` calls.
|
|
174
|
+
2. **Builds** the corresponding dictionary content.
|
|
175
|
+
3. **Replaces** the `useIntlayer` call with optimised code based on your configuration.
|
|
176
|
+
|
|
177
|
+
This ensures that:
|
|
178
|
+
|
|
179
|
+
- If a component is not imported, its content is not included in the bundle (Dead Code Elimination).
|
|
180
|
+
- If a component is lazy-loaded, its content is also lazy-loaded.
|
|
181
|
+
|
|
182
|
+
## Setup by Platform
|
|
183
|
+
|
|
184
|
+
<Tabs>
|
|
185
|
+
<Tab value="nextjs">
|
|
186
|
+
|
|
187
|
+
### Next.js
|
|
188
|
+
|
|
189
|
+
Next.js requires the `@intlayer/swc` plugin to handle the transformation, as Next.js uses SWC for builds.
|
|
190
|
+
|
|
191
|
+
> This plugin is not installed by default because SWC plugins are still experimental for Next.js. It may change in the future.
|
|
192
|
+
|
|
193
|
+
```bash packageManager="npm"
|
|
194
|
+
npm install -D @intlayer/swc
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```bash packageManager="yarn"
|
|
198
|
+
yarn add -D @intlayer/swc
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
```bash packageManager="pnpm"
|
|
202
|
+
pnpm add -D @intlayer/swc
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```bash packageManager="bun"
|
|
206
|
+
bun add -d @intlayer/swc
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Once Installed. Intlayer will automatically detect and use the plugin.
|
|
210
|
+
|
|
211
|
+
</Tab>
|
|
212
|
+
<Tab value="vite">
|
|
213
|
+
|
|
214
|
+
### Vite
|
|
215
|
+
|
|
216
|
+
Vite uses `@intlayer/babel` plugin which is included as dependency of `vite-intlayer`. The optimisation is enabled by default. Nothing else to do.
|
|
217
|
+
|
|
218
|
+
</Tab>
|
|
219
|
+
<Tab value="webpack">
|
|
220
|
+
|
|
221
|
+
### Webpack
|
|
222
|
+
|
|
223
|
+
To enable bundle optimisation with Intlayer on Webpack, you need to install and configure the appropriate Babel (`@intlayer/babel`) or SWC (`@intlayer/swc`) plugin.
|
|
224
|
+
|
|
225
|
+
```bash packageManager="npm"
|
|
226
|
+
npm install -D @intlayer/babel
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```bash packageManager="yarn"
|
|
230
|
+
yarn add -D @intlayer/babel
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```bash packageManager="pnpm"
|
|
234
|
+
pnpm add -D @intlayer/babel
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```bash packageManager="bun"
|
|
238
|
+
bun add -d @intlayer/babel
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
```typescript fileName="babel.config.js"
|
|
242
|
+
const {
|
|
243
|
+
getOptimizePluginOptions,
|
|
244
|
+
intlayerOptimizeBabelPlugin,
|
|
245
|
+
} = require("@intlayer/babel");
|
|
246
|
+
|
|
247
|
+
module.exports = {
|
|
248
|
+
plugins: [[intlayerOptimizeBabelPlugin, getOptimizePluginOptions()]],
|
|
249
|
+
};
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
</Tab>
|
|
253
|
+
</Tabs>
|
|
254
|
+
|
|
255
|
+
## Configuration
|
|
256
|
+
|
|
257
|
+
You can control how Intlayer optimises your bundle via the `build` property in your `intlayer.config.ts`.
|
|
258
|
+
|
|
259
|
+
```typescript fileName="intlayer.config.ts"
|
|
260
|
+
import { Locales, type IntlayerConfig } from "intlayer";
|
|
261
|
+
|
|
262
|
+
const config: IntlayerConfig = {
|
|
263
|
+
internationalization: {
|
|
264
|
+
locales: [Locales.ENGLISH, Locales.FRENCH],
|
|
265
|
+
defaultLocale: Locales.ENGLISH,
|
|
266
|
+
},
|
|
267
|
+
dictionary: {
|
|
268
|
+
importMode: "dynamic",
|
|
269
|
+
},
|
|
270
|
+
build: {
|
|
271
|
+
/**
|
|
272
|
+
* Minify the dictionaries to reduce the bundle size.
|
|
273
|
+
*/
|
|
274
|
+
minify: true;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Purge the unused keys in a dictionaries
|
|
278
|
+
*/
|
|
279
|
+
purge: true;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Indicates if the build should check TypeScript types
|
|
283
|
+
*/
|
|
284
|
+
checkTypes: false;
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export default config;
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
> Keeping the default option for `optimize` is recommended in the most majority of cases.
|
|
292
|
+
|
|
293
|
+
> See doc configuration for more details: [Configuration](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en-GB/configuration.md)
|
|
294
|
+
|
|
295
|
+
### Build Options
|
|
296
|
+
|
|
297
|
+
The following options are available under the `build` configuration object:
|
|
298
|
+
|
|
299
|
+
| Property | Type | Default | Description |
|
|
300
|
+
| :------------- | :-------- | :---------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
301
|
+
| **`optimize`** | `boolean` | `undefined` | Controls whether build optimisation is enabled. If `true`, Intlayer replaces dictionary calls with optimised injects. If `false`, optimisation is disabled. Ideally set to `true` in production. |
|
|
302
|
+
| **`minify`** | `boolean` | `false` | Whether to minify the dictionaries to reduce the bundle size. |
|
|
303
|
+
| **`purge`** | `boolean" | `false` | Whether to purge the unused keys in dictionaries. |
|
|
304
|
+
|
|
305
|
+
### Minification
|
|
306
|
+
|
|
307
|
+
Minifying dictionaries removes unnecessary whitespace, comments, and reduces the size of the JSON content. This is especially useful for large dictionaries.
|
|
308
|
+
|
|
309
|
+
```typescript fileName="intlayer.config.ts"
|
|
310
|
+
import type { IntlayerConfig } from "intlayer";
|
|
311
|
+
|
|
312
|
+
const config: IntlayerConfig = {
|
|
313
|
+
build: {
|
|
314
|
+
minify: true,
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
export default config;
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
> Note: Minification is ignored if `optimize` is disabled or if the Visual Editor is enabled (as the editor needs the full content to allow editing).
|
|
322
|
+
|
|
323
|
+
### Purging
|
|
324
|
+
|
|
325
|
+
Purging ensures that only the keys actually used in your code are included in the final dictionary bundle. This can significantly reduce the size of your bundle if you have large dictionaries with many keys that are not used in every part of your application.
|
|
326
|
+
|
|
327
|
+
```typescript fileName="intlayer.config.ts"
|
|
328
|
+
import type { IntlayerConfig } from "intlayer";
|
|
329
|
+
|
|
330
|
+
const config: IntlayerConfig = {
|
|
331
|
+
build: {
|
|
332
|
+
purge: true,
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
export default config;
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
> Note: Purging is ignored if `optimize` is disabled.
|
|
340
|
+
|
|
341
|
+
### Import Mode
|
|
342
|
+
|
|
343
|
+
For large applications, including several pages and locales, your JSON can represent a significant part of your bundle size. Intlayer allows you to control how dictionaries are loaded.
|
|
344
|
+
|
|
345
|
+
The import mode can be defined by default globally in your `intlayer.config.ts` file.
|
|
346
|
+
|
|
347
|
+
```typescript fileName="intlayer.config.ts"
|
|
348
|
+
import type { IntlayerConfig } from "intlayer";
|
|
349
|
+
|
|
350
|
+
const config: IntlayerConfig = {
|
|
351
|
+
build: {
|
|
352
|
+
minify: true,
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
export default config;
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
As well as for each dictionaries in your `.content.{{ts|tsx|js|jsx|mjs|cjs|json|jsonc|json5}}` files.
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
import { type Dictionary, t } from "intlayer";
|
|
363
|
+
|
|
364
|
+
const appContent: Dictionary = {
|
|
365
|
+
key: "app",
|
|
366
|
+
importMode: "dynamic", // Override the default import mode
|
|
367
|
+
content: {
|
|
368
|
+
// ...
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
export default appContent;
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
| Property | Type | Default | Description |
|
|
376
|
+
| :--------------- | :--------------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------------- |
|
|
377
|
+
| **`importMode`** | `'static'`, `'dynamic'`, `'fetch'` | `'static'` | **Deprecated**: Use `dictionary.importMode` instead. Determines how dictionaries are loaded (see details below). |
|
|
378
|
+
|
|
379
|
+
The `importMode` setting dictates how the dictionary content is injected into your component.
|
|
380
|
+
You can define it globally in the `intlayer.config.ts` file under the `dictionary` object, or you can overwrite it for a specific dictionary in its `.content.ts` file.
|
|
381
|
+
|
|
382
|
+
### 1. Static Mode (`default`)
|
|
383
|
+
|
|
384
|
+
In static mode, Intlayer replaces `useIntlayer` with `useDictionary` and injects the dictionary directly into the JavaScript bundle.
|
|
385
|
+
|
|
386
|
+
- **Pros:** Instant rendering (synchronous), zero extra network requests during hydration.
|
|
387
|
+
- **Cons:** The bundle includes translations for **all** available languages for that specific component.
|
|
388
|
+
- **Best for:** Single Page Applications (SPA).
|
|
389
|
+
|
|
390
|
+
**Transformed Code Example:**
|
|
391
|
+
|
|
392
|
+
```tsx
|
|
393
|
+
// Your code
|
|
394
|
+
const content = useIntlayer("my-key");
|
|
395
|
+
|
|
396
|
+
// Optimised code (Static)
|
|
397
|
+
const content = useDictionary({
|
|
398
|
+
key: "my-key",
|
|
399
|
+
content: {
|
|
400
|
+
nodeType: "translation",
|
|
401
|
+
translation: {
|
|
402
|
+
en: "My title",
|
|
403
|
+
fr: "Mon titre",
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### 2. Dynamic Mode
|
|
410
|
+
|
|
411
|
+
In dynamic mode, Intlayer replaces `useIntlayer` with `useDictionaryAsync`. This uses `import()` (Suspense-like mechanism) to lazy-load specifically the JSON for the current locale.
|
|
412
|
+
|
|
413
|
+
- **Pros:** **Locale-level tree shaking.** A user viewing the English version will _only_ download the English dictionary. The French dictionary is never loaded.
|
|
414
|
+
- **Cons:** Triggers a network request (asset fetch) per component during hydration.
|
|
415
|
+
- **Best for:** Large text blocks, articles, or applications supporting many languages where bundle size is critical.
|
|
416
|
+
|
|
417
|
+
**Transformed Code Example:**
|
|
418
|
+
|
|
419
|
+
```tsx
|
|
420
|
+
// Your code
|
|
421
|
+
const content = useIntlayer("my-key");
|
|
422
|
+
|
|
423
|
+
// Optimised code (Dynamic)
|
|
424
|
+
const content = useDictionaryAsync({
|
|
425
|
+
en: () =>
|
|
426
|
+
import(".intlayer/dynamic_dictionary/my-key/en.json").then(
|
|
427
|
+
(mod) => mod.default
|
|
428
|
+
),
|
|
429
|
+
fr: () =>
|
|
430
|
+
import(".intlayer/dynamic_dictionary/my-key/fr.json").then(
|
|
431
|
+
(mod) => mod.default
|
|
432
|
+
),
|
|
433
|
+
});
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
> When using `importMode: 'dynamic'`, if you have 100 components using `useIntlayer` on a single page, the browser will attempt 100 separate fetches. To avoid this "waterfall" of requests, group content into fewer `.content` files (e.g., one dictionary per page section) rather than one per atom component.
|
|
437
|
+
|
|
438
|
+
### 3. Fetch Mode
|
|
439
|
+
|
|
440
|
+
Behaves similarly to Dynamic mode but attempts to fetch dictionaries from the Intlayer Live Sync API first. If the API call fails or the content is not marked for live updates, it falls back to the dynamic import.
|
|
441
|
+
|
|
442
|
+
> See CMS documentation for more details: [CMS](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en-GB/intlayer_CMS.md)
|
|
443
|
+
|
|
444
|
+
> In fetch mode, purge and minification can't be used.
|
|
445
|
+
|
|
446
|
+
## Summary: Static vs Dynamic
|
|
447
|
+
|
|
448
|
+
| Feature | Static Mode | Dynamic Mode |
|
|
449
|
+
| :------------------- | :-------------------------------------------- | :----------------------------------- |
|
|
450
|
+
| **JS Bundle Size** | Larger (includes all langs for the component) | Smallest (only code, no content) |
|
|
451
|
+
| **Initial Load** | Instant (Content is in bundle) | Slight delay (Fetches JSON) |
|
|
452
|
+
| **Network Requests** | 0 extra requests | 1 request per dictionary |
|
|
453
|
+
| **Tree Shaking** | Component-level | Component-level + Locale-level |
|
|
454
|
+
| **Best Use Case** | UI Components, Small Apps | Pages with much text, Many Languages |
|
|
@@ -256,7 +256,7 @@ export default function LocaleLayout({
|
|
|
256
256
|
params: { locale: string };
|
|
257
257
|
}) {
|
|
258
258
|
const locale: Locale = (locales as readonly string[]).includes(params.locale)
|
|
259
|
-
?
|
|
259
|
+
? params.locale
|
|
260
260
|
: defaultLocale;
|
|
261
261
|
|
|
262
262
|
const dir = isRtl(locale) ? "rtl" : "ltr";
|
|
@@ -103,7 +103,7 @@ async function loadMessages(locale: string) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
export default getRequestConfig(async ({ locale }) => {
|
|
106
|
-
if (!locales.includes(locale
|
|
106
|
+
if (!locales.includes(locale)) notFound();
|
|
107
107
|
|
|
108
108
|
return {
|
|
109
109
|
messages: await loadMessages(locale),
|
|
@@ -193,9 +193,7 @@ const RootComponent: ParentComponent = (props) => {
|
|
|
193
193
|
</head>
|
|
194
194
|
<body>
|
|
195
195
|
<IntlayerProvider locale={locale}>
|
|
196
|
-
<Suspense>
|
|
197
|
-
{props.children}
|
|
198
|
-
</Suspense>
|
|
196
|
+
<Suspense>{props.children}</Suspense>
|
|
199
197
|
</IntlayerProvider>
|
|
200
198
|
<Scripts />
|
|
201
199
|
</body>
|
|
@@ -506,12 +504,33 @@ export const Route = createFileRoute("/{-$locale}/")({
|
|
|
506
504
|
component: RouteComponent,
|
|
507
505
|
head: ({ params }) => {
|
|
508
506
|
const { locale } = params;
|
|
509
|
-
const
|
|
507
|
+
const path = "/"; // The path for this route
|
|
508
|
+
|
|
509
|
+
const metaContent = getIntlayer("app", locale);
|
|
510
510
|
|
|
511
511
|
return {
|
|
512
|
+
links: [
|
|
513
|
+
// Canonical link: Points to the current localized page
|
|
514
|
+
{ rel: "canonical", href: getLocalizedUrl(path, locale) },
|
|
515
|
+
|
|
516
|
+
// Hreflang: Tell Google about all localized versions
|
|
517
|
+
...localeMap(({ locale: mapLocale }) => ({
|
|
518
|
+
rel: "alternate",
|
|
519
|
+
hrefLang: mapLocale,
|
|
520
|
+
href: getLocalizedUrl(path, mapLocale),
|
|
521
|
+
})),
|
|
522
|
+
|
|
523
|
+
// x-default: For users in unmatched languages
|
|
524
|
+
// Define the default fallback locale (usually your primary language)
|
|
525
|
+
{
|
|
526
|
+
rel: "alternate",
|
|
527
|
+
hrefLang: "x-default",
|
|
528
|
+
href: getLocalizedUrl(path, defaultLocale),
|
|
529
|
+
},
|
|
530
|
+
],
|
|
512
531
|
meta: [
|
|
513
532
|
{ title: metaContent.title },
|
|
514
|
-
{
|
|
533
|
+
{ name: "description", content: metaContent.meta.description },
|
|
515
534
|
],
|
|
516
535
|
};
|
|
517
536
|
},
|
|
@@ -224,9 +224,7 @@ function RootDocument({ children }: { children: ReactNode }) {
|
|
|
224
224
|
<HeadContent />
|
|
225
225
|
</head>
|
|
226
226
|
<body>
|
|
227
|
-
<IntlayerProvider locale={locale}>
|
|
228
|
-
{children}
|
|
229
|
-
</IntlayerProvider>
|
|
227
|
+
<IntlayerProvider locale={locale}>{children}</IntlayerProvider>
|
|
230
228
|
<Scripts />
|
|
231
229
|
</body>
|
|
232
230
|
</html>
|
|
@@ -325,31 +323,21 @@ import { getPrefix } from "intlayer";
|
|
|
325
323
|
|
|
326
324
|
export const LOCALE_ROUTE = "{-$locale}" as const;
|
|
327
325
|
|
|
328
|
-
|
|
329
|
-
export type RemoveLocaleParam<T> = T extends string
|
|
330
|
-
? RemoveLocaleFromString<T>
|
|
331
|
-
: T;
|
|
326
|
+
export type To = StripLocalePrefix<LinkComponentProps["to"]>;
|
|
332
327
|
|
|
333
|
-
export type
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
328
|
+
export type StripLocalePrefix<T extends string | undefined> = T extends
|
|
329
|
+
| `/${typeof LOCALE_ROUTE}/`
|
|
330
|
+
| `/${typeof LOCALE_ROUTE}`
|
|
331
|
+
? "/"
|
|
332
|
+
: T extends `/${typeof LOCALE_ROUTE}/${infer Rest}`
|
|
333
|
+
? `/${Rest}`
|
|
334
|
+
: T;
|
|
337
335
|
|
|
338
|
-
type
|
|
336
|
+
type LocalizedLinkProps = {
|
|
339
337
|
to?: To;
|
|
340
338
|
} & Omit<LinkComponentProps, "to">;
|
|
341
339
|
|
|
342
|
-
|
|
343
|
-
type RemoveAll<
|
|
344
|
-
S extends string,
|
|
345
|
-
Sub extends string,
|
|
346
|
-
> = S extends `${infer H}${Sub}${infer T}` ? RemoveAll<`${H}${T}`, Sub> : S;
|
|
347
|
-
|
|
348
|
-
type RemoveLocaleFromString<S extends string> = CollapseDoubleSlashes<
|
|
349
|
-
RemoveAll<S, typeof LOCALE_ROUTE>
|
|
350
|
-
>;
|
|
351
|
-
|
|
352
|
-
export const LocalisedLink: FC<LocalisedLinkProps> = (props) => {
|
|
340
|
+
export const LocalizedLink: FC<LocalizedLinkProps> = (props) => {
|
|
353
341
|
const { locale } = useLocale();
|
|
354
342
|
const { localePrefix } = getPrefix(locale);
|
|
355
343
|
|
|
@@ -377,26 +365,26 @@ Then we can create a `useLocalizedNavigate` hook for programmatic navigation:
|
|
|
377
365
|
import { useNavigate } from "@tanstack/react-router";
|
|
378
366
|
import { getPrefix } from "intlayer";
|
|
379
367
|
import { useLocale } from "react-intlayer";
|
|
380
|
-
import {
|
|
368
|
+
import type { StripLocalePrefix } from "@/components/localized-link";
|
|
381
369
|
import type { FileRouteTypes } from "@/routeTree.gen";
|
|
382
370
|
|
|
383
|
-
type
|
|
384
|
-
|
|
385
|
-
| `/${typeof LOCALE_ROUTE}/`
|
|
386
|
-
? "/"
|
|
387
|
-
: T extends `/${typeof LOCALE_ROUTE}/${infer Rest}`
|
|
388
|
-
? `/${Rest}`
|
|
389
|
-
: never;
|
|
371
|
+
type NavigateFn = ReturnType<typeof useNavigate>;
|
|
372
|
+
type BaseNavigateOptions = Parameters<NavigateFn>[0];
|
|
390
373
|
|
|
391
374
|
type LocalizedTo = StripLocalePrefix<FileRouteTypes["to"]>;
|
|
392
375
|
|
|
393
|
-
type
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
376
|
+
export type LocalizedNavigateOptions = Omit<
|
|
377
|
+
BaseNavigateOptions,
|
|
378
|
+
"to" | "params"
|
|
379
|
+
> & {
|
|
380
|
+
to: LocalizedTo;
|
|
381
|
+
params?: Omit<NonNullable<BaseNavigateOptions["params"]>, "locale">;
|
|
398
382
|
};
|
|
399
383
|
|
|
384
|
+
type LocalizedNavigate = (
|
|
385
|
+
options: LocalizedNavigateOptions
|
|
386
|
+
) => ReturnType<NavigateFn>;
|
|
387
|
+
|
|
400
388
|
export const useLocalizedNavigate = () => {
|
|
401
389
|
const navigate = useNavigate();
|
|
402
390
|
|
|
@@ -443,38 +431,6 @@ import { useLocalizedNavigate } from "@/hooks/useLocalizedNavigate";
|
|
|
443
431
|
|
|
444
432
|
export const Route = createFileRoute("/{-$locale}/")({
|
|
445
433
|
component: RouteComponent,
|
|
446
|
-
head: ({ params }) => {
|
|
447
|
-
const { locale } = params;
|
|
448
|
-
const path = "/"; // The path for this route
|
|
449
|
-
|
|
450
|
-
const metaContent = getIntlayer("app", locale);
|
|
451
|
-
|
|
452
|
-
return {
|
|
453
|
-
links: [
|
|
454
|
-
// Canonical link: Points to the current localized page
|
|
455
|
-
{ rel: "canonical", href: getLocalizedUrl(path, locale) },
|
|
456
|
-
|
|
457
|
-
// Hreflang: Tell Google about all localized versions
|
|
458
|
-
...localeMap(({ locale: mapLocale }) => ({
|
|
459
|
-
rel: "alternate",
|
|
460
|
-
hrefLang: mapLocale,
|
|
461
|
-
href: getLocalizedUrl(path, mapLocale),
|
|
462
|
-
})),
|
|
463
|
-
|
|
464
|
-
// x-default: For users in unmatched languages
|
|
465
|
-
// Define the default fallback locale (usually your primary language)
|
|
466
|
-
{
|
|
467
|
-
rel: "alternate",
|
|
468
|
-
hrefLang: "x-default",
|
|
469
|
-
href: getLocalizedUrl(path, defaultLocale),
|
|
470
|
-
},
|
|
471
|
-
],
|
|
472
|
-
meta: [
|
|
473
|
-
{ title: metaContent.title },
|
|
474
|
-
{ name: "description", content: metaContent.meta.description },
|
|
475
|
-
],
|
|
476
|
-
};
|
|
477
|
-
},
|
|
478
434
|
});
|
|
479
435
|
|
|
480
436
|
function RouteComponent() {
|
|
@@ -629,12 +585,33 @@ export const Route = createFileRoute("/{-$locale}/")({
|
|
|
629
585
|
component: RouteComponent,
|
|
630
586
|
head: ({ params }) => {
|
|
631
587
|
const { locale } = params;
|
|
632
|
-
const
|
|
588
|
+
const path = "/"; // The path for this route
|
|
589
|
+
|
|
590
|
+
const metaContent = getIntlayer("app", locale);
|
|
633
591
|
|
|
634
592
|
return {
|
|
593
|
+
links: [
|
|
594
|
+
// Canonical link: Points to the current localized page
|
|
595
|
+
{ rel: "canonical", href: getLocalizedUrl(path, locale) },
|
|
596
|
+
|
|
597
|
+
// Hreflang: Tell Google about all localized versions
|
|
598
|
+
...localeMap(({ locale: mapLocale }) => ({
|
|
599
|
+
rel: "alternate",
|
|
600
|
+
hrefLang: mapLocale,
|
|
601
|
+
href: getLocalizedUrl(path, mapLocale),
|
|
602
|
+
})),
|
|
603
|
+
|
|
604
|
+
// x-default: For users in unmatched languages
|
|
605
|
+
// Define the default fallback locale (usually your primary language)
|
|
606
|
+
{
|
|
607
|
+
rel: "alternate",
|
|
608
|
+
hrefLang: "x-default",
|
|
609
|
+
href: getLocalizedUrl(path, defaultLocale),
|
|
610
|
+
},
|
|
611
|
+
],
|
|
635
612
|
meta: [
|
|
636
613
|
{ title: metaContent.title },
|
|
637
|
-
{
|
|
614
|
+
{ name: "description", content: metaContent.meta.description },
|
|
638
615
|
],
|
|
639
616
|
};
|
|
640
617
|
},
|