@intlayer/docs 9.3.1 → 9.3.3

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.
Files changed (151) hide show
  1. package/blog/ar/nextjs-multilingual-seo-comparison.md +10 -10
  2. package/blog/de/nextjs-multilingual-seo-comparison.md +9 -9
  3. package/blog/en/nextjs-multilingual-seo-comparison.md +10 -10
  4. package/blog/en-GB/nextjs-multilingual-seo-comparison.md +10 -10
  5. package/blog/es/nextjs-multilingual-seo-comparison.md +10 -10
  6. package/blog/fr/nextjs-multilingual-seo-comparison.md +10 -10
  7. package/blog/hi/nextjs-multilingual-seo-comparison.md +10 -10
  8. package/blog/id/nextjs-multilingual-seo-comparison.md +10 -10
  9. package/blog/it/nextjs-multilingual-seo-comparison.md +10 -10
  10. package/blog/ja/nextjs-multilingual-seo-comparison.md +9 -9
  11. package/blog/ko/nextjs-multilingual-seo-comparison.md +9 -9
  12. package/blog/pl/nextjs-multilingual-seo-comparison.md +10 -10
  13. package/blog/pt/nextjs-multilingual-seo-comparison.md +9 -9
  14. package/blog/ru/nextjs-multilingual-seo-comparison.md +9 -9
  15. package/blog/tr/nextjs-multilingual-seo-comparison.md +9 -9
  16. package/blog/uk/nextjs-multilingual-seo-comparison.md +10 -10
  17. package/blog/vi/nextjs-multilingual-seo-comparison.md +10 -10
  18. package/blog/zh/nextjs-multilingual-seo-comparison.md +10 -10
  19. package/dist/cjs/generated/docs.entry.cjs +20 -0
  20. package/dist/cjs/generated/docs.entry.cjs.map +1 -1
  21. package/dist/esm/generated/docs.entry.mjs +20 -0
  22. package/dist/esm/generated/docs.entry.mjs.map +1 -1
  23. package/dist/types/generated/docs.entry.d.ts +1 -0
  24. package/dist/types/generated/docs.entry.d.ts.map +1 -1
  25. package/docs/ar/bundle_optimization.md +1 -1
  26. package/docs/ar/configuration.md +32 -9
  27. package/docs/ar/dictionary/content_file.md +0 -22
  28. package/docs/ar/dictionary/function_fetching.md +23 -0
  29. package/docs/ar/eslint.md +336 -0
  30. package/docs/ar/intlayer_with_react_router_v7_fs_routes.md +1 -1
  31. package/docs/bn/configuration.md +34 -9
  32. package/docs/bn/eslint.md +336 -0
  33. package/docs/cs/bundle_optimization.md +1 -1
  34. package/docs/cs/configuration.md +33 -9
  35. package/docs/cs/eslint.md +336 -0
  36. package/docs/de/bundle_optimization.md +1 -1
  37. package/docs/de/configuration.md +34 -9
  38. package/docs/de/dictionary/content_file.md +0 -22
  39. package/docs/de/dictionary/function_fetching.md +23 -0
  40. package/docs/de/eslint.md +336 -0
  41. package/docs/en/bundle_optimization.md +1 -1
  42. package/docs/en/configuration.md +34 -9
  43. package/docs/en/dictionary/content_file.md +0 -22
  44. package/docs/en/dictionary/function_fetching.md +23 -0
  45. package/docs/en/eslint.md +336 -0
  46. package/docs/en/packages/intlayer/getLocalizedPath.md +70 -19
  47. package/docs/en-GB/configuration.md +33 -9
  48. package/docs/en-GB/dictionary/content_file.md +0 -22
  49. package/docs/en-GB/dictionary/function_fetching.md +23 -0
  50. package/docs/en-GB/eslint.md +336 -0
  51. package/docs/en-GB/intlayer_with_create_react_app.md +32 -35
  52. package/docs/en-GB/intlayer_with_react_router_v7_fs_routes.md +1 -1
  53. package/docs/es/bundle_optimization.md +1 -1
  54. package/docs/es/configuration.md +35 -9
  55. package/docs/es/dictionary/content_file.md +0 -22
  56. package/docs/es/dictionary/function_fetching.md +23 -0
  57. package/docs/es/eslint.md +336 -0
  58. package/docs/fr/bundle_optimization.md +1 -1
  59. package/docs/fr/configuration.md +35 -9
  60. package/docs/fr/dictionary/content_file.md +0 -22
  61. package/docs/fr/dictionary/function_fetching.md +23 -0
  62. package/docs/fr/eslint.md +336 -0
  63. package/docs/hi/configuration.md +35 -9
  64. package/docs/hi/dictionary/content_file.md +0 -22
  65. package/docs/hi/dictionary/function_fetching.md +23 -0
  66. package/docs/hi/eslint.md +336 -0
  67. package/docs/hi/intlayer_with_react_router_v7_fs_routes.md +1 -1
  68. package/docs/hi/intlayer_with_vite+svelte.md +2 -2
  69. package/docs/id/configuration.md +35 -9
  70. package/docs/id/dictionary/content_file.md +0 -22
  71. package/docs/id/dictionary/function_fetching.md +23 -0
  72. package/docs/id/eslint.md +336 -0
  73. package/docs/it/bundle_optimization.md +1 -1
  74. package/docs/it/configuration.md +35 -9
  75. package/docs/it/dictionary/content_file.md +0 -22
  76. package/docs/it/dictionary/function_fetching.md +23 -0
  77. package/docs/it/eslint.md +336 -0
  78. package/docs/ja/configuration.md +30 -9
  79. package/docs/ja/dictionary/content_file.md +0 -22
  80. package/docs/ja/dictionary/function_fetching.md +23 -0
  81. package/docs/ja/eslint.md +336 -0
  82. package/docs/ja/intlayer_with_react_router_v7.md +1 -146
  83. package/docs/ja/intlayer_with_vite+react.md +5 -1
  84. package/docs/ko/configuration.md +31 -9
  85. package/docs/ko/dictionary/content_file.md +0 -22
  86. package/docs/ko/dictionary/function_fetching.md +23 -0
  87. package/docs/ko/eslint.md +336 -0
  88. package/docs/ko/intlayer_with_lynx+react.md +4 -0
  89. package/docs/ko/intlayer_with_react_router_v7_fs_routes.md +1 -1
  90. package/docs/ko/intlayer_with_storybook.md +5 -5
  91. package/docs/nl/configuration.md +33 -9
  92. package/docs/nl/eslint.md +336 -0
  93. package/docs/pl/bundle_optimization.md +1 -1
  94. package/docs/pl/configuration.md +34 -9
  95. package/docs/pl/dictionary/content_file.md +0 -22
  96. package/docs/pl/dictionary/function_fetching.md +23 -0
  97. package/docs/pl/eslint.md +336 -0
  98. package/docs/pl/intlayer_with_astro.md +1 -114
  99. package/docs/pl/migration_from_i18next_to_intlayer.md +4 -0
  100. package/docs/pl/migration_from_next-i18next_to_intlayer.md +8 -4
  101. package/docs/pl/migration_from_next-intl_to_intlayer.md +11 -5
  102. package/docs/pl/migration_from_nuxtjs_i18n_to_intlayer.md +8 -4
  103. package/docs/pl/migration_from_react-i18next_to_intlayer.md +8 -4
  104. package/docs/pl/migration_from_vue-i18n_to_intlayer.md +4 -0
  105. package/docs/pt/bundle_optimization.md +1 -1
  106. package/docs/pt/configuration.md +34 -9
  107. package/docs/pt/dictionary/content_file.md +0 -22
  108. package/docs/pt/dictionary/function_fetching.md +23 -0
  109. package/docs/pt/eslint.md +336 -0
  110. package/docs/pt/intlayer_with_astro.md +1 -114
  111. package/docs/ru/bundle_optimization.md +1 -1
  112. package/docs/ru/configuration.md +34 -9
  113. package/docs/ru/dictionary/content_file.md +0 -22
  114. package/docs/ru/dictionary/function_fetching.md +23 -0
  115. package/docs/ru/eslint.md +336 -0
  116. package/docs/tr/bundle_optimization.md +1 -1
  117. package/docs/tr/configuration.md +33 -9
  118. package/docs/tr/dictionary/content_file.md +0 -22
  119. package/docs/tr/dictionary/function_fetching.md +23 -0
  120. package/docs/tr/eslint.md +336 -0
  121. package/docs/uk/configuration.md +35 -9
  122. package/docs/uk/dictionary/content_file.md +0 -22
  123. package/docs/uk/dictionary/function_fetching.md +23 -0
  124. package/docs/uk/eslint.md +336 -0
  125. package/docs/uk/packages/angular-intlayer/exports.md +2 -2
  126. package/docs/ur/configuration.md +35 -9
  127. package/docs/ur/eslint.md +336 -0
  128. package/docs/vi/bundle_optimization.md +1 -1
  129. package/docs/vi/configuration.md +33 -9
  130. package/docs/vi/dictionary/content_file.md +0 -22
  131. package/docs/vi/dictionary/function_fetching.md +23 -0
  132. package/docs/vi/eslint.md +336 -0
  133. package/docs/zh/bundle_optimization.md +1 -1
  134. package/docs/zh/configuration.md +29 -9
  135. package/docs/zh/dictionary/content_file.md +0 -22
  136. package/docs/zh/dictionary/function_fetching.md +23 -0
  137. package/docs/zh/eslint.md +336 -0
  138. package/docs/zh/intlayer_with_create_react_app.md +4 -0
  139. package/docs/zh/intlayer_with_lynx+react.md +4 -0
  140. package/docs/zh/intlayer_with_nextjs_14.md +0 -2
  141. package/docs/zh/intlayer_with_nextjs_15.md +0 -2
  142. package/docs/zh/intlayer_with_nextjs_page_router.md +0 -2
  143. package/docs/zh/intlayer_with_nuxt.md +1 -1
  144. package/docs/zh/intlayer_with_react_router_v7.md +4 -0
  145. package/docs/zh/intlayer_with_react_router_v7_fs_routes.md +4 -0
  146. package/docs/zh/intlayer_with_solid_start.md +1 -1
  147. package/docs/zh/intlayer_with_vite+vue.md +0 -2
  148. package/docs/zh-TW/bundle_optimization.md +1 -1
  149. package/docs/zh-TW/eslint.md +336 -0
  150. package/package.json +7 -7
  151. package/src/generated/docs.entry.ts +20 -0
@@ -0,0 +1,336 @@
1
+ ---
2
+ createdAt: 2026-08-12
3
+ updatedAt: 2026-08-13
4
+ title: ESLint Plugin | Lint rules for Intlayer
5
+ description: Catch hardcoded strings, dynamic calls the Intlayer compiler cannot optimize, and unused dictionary content, with eslint-plugin-intlayer. Works with ESLint and oxlint, across React, Vue, Svelte, Angular and Astro.
6
+ keywords:
7
+ - Intlayer
8
+ - ESLint
9
+ - oxlint
10
+ - Linting
11
+ - i18n
12
+ - Internationalization
13
+ - no-raw-text
14
+ - Hardcoded strings
15
+ - Unused translations
16
+ - Dead content
17
+ - React
18
+ - Vue
19
+ - Svelte
20
+ - Angular
21
+ slugs:
22
+ - doc
23
+ - eslint
24
+ history:
25
+ - version: 9.3.1
26
+ date: 2026-08-12
27
+ changes: "Init history"
28
+ author: aymericzip
29
+ ---
30
+
31
+ # ESLint x OXLint Plugin
32
+
33
+ `eslint-plugin-intlayer` catches the kinds of i18n mistake TypeScript cannot:
34
+
35
+ 1. **Hardcoded text** that never made it into a dictionary.
36
+ 2. **Dynamic calls** that type-check and run, but that the Intlayer compiler cannot optimize.
37
+ 3. **Dead content** — dictionaries and fields nothing in the project reads (opt-in).
38
+
39
+ Unknown dictionary keys, unknown field paths and missing locales are already compile errors, so the plugin does not repeat them.
40
+
41
+ ## Installation
42
+
43
+ ```bash packageManager="npm"
44
+ npm install --save-dev eslint-plugin-intlayer
45
+ ```
46
+
47
+ ```bash packageManager="pnpm"
48
+ pnpm add --save-dev eslint-plugin-intlayer
49
+ ```
50
+
51
+ ```bash packageManager="yarn"
52
+ yarn add --dev eslint-plugin-intlayer
53
+ ```
54
+
55
+ Requires ESLint 9 or later (flat config). ESLint 10 is supported.
56
+
57
+ ## Usage
58
+
59
+ The plugin runs in both ESLint and [oxlint](https://oxc.rs) — the same rules, the same options.
60
+
61
+ <Tabs defaultTab="eslint">
62
+ <Tab label="ESLint" value="eslint">
63
+
64
+ ```javascript fileName="eslint.config.mjs"
65
+ import intlayer from "eslint-plugin-intlayer";
66
+
67
+ export default [...intlayer.configs.recommended];
68
+ ```
69
+
70
+ Or spread a config and set the severities yourself:
71
+
72
+ ```javascript fileName="eslint.config.mjs"
73
+ import intlayer from "eslint-plugin-intlayer";
74
+
75
+ export default [
76
+ ...intlayer.configs.recommended,
77
+ {
78
+ rules: {
79
+ "intlayer/no-raw-text": "warn",
80
+ "intlayer/static-dictionary-key": "error",
81
+ "intlayer/no-dynamic-field-access": "error",
82
+ "intlayer/enforce-adapter-import": "warn",
83
+ "intlayer/no-unused-content": "warn",
84
+ },
85
+ },
86
+ ];
87
+ ```
88
+
89
+ </Tab>
90
+ <Tab label="oxlint" value="oxlint">
91
+
92
+ ```json fileName=".oxlintrc.json"
93
+ {
94
+ "jsPlugins": ["eslint-plugin-intlayer"],
95
+ "rules": {
96
+ "intlayer/no-raw-text": "warn",
97
+ "intlayer/static-dictionary-key": "error",
98
+ "intlayer/no-dynamic-field-access": "error",
99
+ "intlayer/enforce-adapter-import": "warn"
100
+ }
101
+ }
102
+ ```
103
+
104
+ Two caveats: oxlint's JS plugin support is still alpha, and oxlint does not support custom parsers — so `.vue`, `.svelte`, `.astro` and Angular templates are not linted there. Run oxlint over your JS/TS/JSX files and keep ESLint for the rest.
105
+
106
+ `no-unused-content` is left out above on purpose: it needs the working directory and the linted file path from the rule context, which the alpha JS plugin bridge does not guarantee. Run it under ESLint.
107
+
108
+ </Tab>
109
+ </Tabs>
110
+
111
+ ### Configs
112
+
113
+ | Config | `no-raw-text` | `static-dictionary-key` | `no-dynamic-field-access` | `enforce-adapter-import` | `no-unused-content` |
114
+ | --------------- | -------------------------- | ----------------------- | ------------------------- | ------------------------ | ------------------- |
115
+ | `recommended` | warn | error | error | off | off |
116
+ | `strict` | error (+ non-JSX literals) | error | error | error | off |
117
+ | `contract-only` | off | error | error | off | off |
118
+
119
+ `recommended` keeps `no-raw-text` at `warn` on purpose: pointing it at an existing codebase surfaces every untranslated string at once, which should not break your build on day one.
120
+
121
+ `enforce-adapter-import` is off by default — enable it explicitly if you want it.
122
+
123
+ `no-unused-content` is off in every config, `strict` included. It is the one rule that reads your Intlayer configuration and walks your source files from disk, so turning it on should be a deliberate choice rather than something a preset does for you.
124
+
125
+ ## Rules
126
+
127
+ ### `no-raw-text`
128
+
129
+ Reports user-facing text that is not declared in a dictionary. It uses the same detection as `intlayer extract`, so brand names, CSS classes and technical identifiers are ignored.
130
+
131
+ ```jsx
132
+ // ✗ Reported
133
+ <h1>Welcome to our documentation</h1>
134
+ <input placeholder="Enter your email address" />
135
+
136
+ // ✓ Fine
137
+ const { title } = useIntlayer("home");
138
+ <h1>{title}</h1>
139
+ ```
140
+
141
+ Content declaration files (`*.content.ts`, …) are skipped.
142
+
143
+ To fix a whole file at once, run `npx intlayer extract` and let the compiler move the strings into a dictionary for you.
144
+
145
+ **Options**
146
+
147
+ ```javascript fileName="eslint.config.mjs"
148
+ {
149
+ "intlayer/no-raw-text": [
150
+ "warn",
151
+ {
152
+ // Attributes whose value is user-facing text.
153
+ // Default: title, placeholder, alt, aria-label, label
154
+ attributes: ["title", "placeholder", "alt", "aria-label", "label"],
155
+
156
+ // Elements whose content is never user-facing text.
157
+ // Default: code, pre, script, style
158
+ ignoreElements: ["code", "pre", "script", "style"],
159
+
160
+ // Regular expressions for text to never report.
161
+ ignorePatterns: ["^Powered by"],
162
+
163
+ // Also report string literals outside markup. Default: false
164
+ includeStringLiterals: false,
165
+ },
166
+ ],
167
+ }
168
+ ```
169
+
170
+ ### `static-dictionary-key`
171
+
172
+ Requires the dictionary key to be a string literal.
173
+
174
+ The compiler can only pre-load a dictionary when it can read the key directly at the call site. With a computed key it silently skips the optimization and bundles every dictionary instead.
175
+
176
+ ```typescript
177
+ // ✗ Reported
178
+ useIntlayer(dictionaryKey);
179
+ useIntlayer(`home-${suffix}`);
180
+ getTranslations({ namespace: page });
181
+
182
+ // ✗ A variable is still not a literal
183
+ const key = "home";
184
+ useIntlayer(key);
185
+
186
+ // ✓ Fine
187
+ useIntlayer("home");
188
+ getTranslations({ namespace: "home" });
189
+ ```
190
+
191
+ This applies to `useIntlayer`, `getIntlayer` and every compat adapter (`useTranslation`, `useTranslations`, `formatMessage`, `<FormattedMessage id>`, `<Trans i18nKey>`, …).
192
+
193
+ ### `no-dynamic-field-access`
194
+
195
+ Requires the field you read from a dictionary to be statically known.
196
+
197
+ The compiler removes fields it does not see used. A computed access is invisible to it, so the read can return `undefined` at runtime.
198
+
199
+ ```typescript
200
+ // ✗ Reported
201
+ const content = useIntlayer("home");
202
+ content[fieldName];
203
+
204
+ const t = useTranslations("home");
205
+ t(messageKey);
206
+
207
+ // ✓ Fine
208
+ content.title;
209
+ content["title"];
210
+ content.items[0];
211
+ t("hero.title");
212
+ ```
213
+
214
+ ### `enforce-adapter-import`
215
+
216
+ Prefers the `@intlayer/*` compat adapter over the original package. The original only resolves to Intlayer when the bundler alias is configured; the adapter always does. Autofixable with `--fix`.
217
+
218
+ ```typescript
219
+ // ✗ Reported
220
+ import { useTranslation } from "react-i18next";
221
+ import { getTranslations } from "next-intl/server";
222
+
223
+ // ✓ Fine
224
+ import { useTranslation } from "@intlayer/react-i18next";
225
+ import { getTranslations } from "@intlayer/next-intl/server";
226
+ ```
227
+
228
+ ### `no-unused-content`
229
+
230
+ **Off by default.** Reports content nothing in your project reads, plus dictionary keys declared in more than one place.
231
+
232
+ ```typescript fileName="src/home.content.ts"
233
+ export default {
234
+ key: "home", // ✗ Reported when no caller anywhere asks for "home"
235
+ content: {
236
+ title: t({ en: "Title" }),
237
+
238
+ // ✗ Reported when nothing reads `hero`
239
+ hero: {
240
+ subtitle: t({ en: "Subtitle" }),
241
+ },
242
+ },
243
+ };
244
+ ```
245
+
246
+ Unlike the other rules, this one cannot answer from the file in front of it — a field is unused only relative to the whole project. On the first content declaration of a lint run it loads your Intlayer configuration, globs the source files that configuration declares (`build.traversePattern`, `compiler.transformPattern`) and runs the same usage analyser that powers `@intlayer/lsp` and the "unused" strikethrough in the VS Code extension. The result is cached for `cacheTtl` milliseconds, so the scan happens once per run rather than once per file.
247
+
248
+ **Options**
249
+
250
+ ```javascript fileName="eslint.config.mjs"
251
+ {
252
+ "intlayer/no-unused-content": [
253
+ "warn",
254
+ {
255
+ // Report dictionary keys nothing references. Default: true
256
+ reportUnusedDictionaries: true,
257
+
258
+ // Report content fields nothing reads. Default: true
259
+ reportUnusedFields: true,
260
+
261
+ // Report keys declared in more than one place. Default: true
262
+ reportDuplicateKeys: true,
263
+
264
+ // Regular expressions for field paths to never report.
265
+ ignoreFields: ["^meta"],
266
+
267
+ // Project root the scan starts from. Default: ESLint's working directory
268
+ baseDir: process.cwd(),
269
+
270
+ // How long one project scan is reused, in ms. Default: 30000
271
+ cacheTtl: 30000,
272
+ },
273
+ ],
274
+ }
275
+ ```
276
+
277
+ Lower `cacheTtl` when you lint from a long-lived editor server and want your edits reflected sooner; set `baseDir` when a single lint run spans several Intlayer projects in a monorepo.
278
+
279
+ > **It errs towards silence.** A false positive here deletes a translation, so nothing is reported when the dictionary is consumed in a way the analysis cannot follow: the content object passed on as a whole, a translator function bound from it (`const t = useTranslations("home")`), a declaration reached through a direct import (`useDictionary(myDictionary)`), a `nest()` from another dictionary, or a field list made non-exhaustive by a spread. Single-file components (`.vue`, `.svelte`, `.astro`) count as using every field of the dictionaries they mention, because their script blocks are not parsed here.
280
+
281
+ `reportDuplicateKeys` reads the unmerged dictionaries the build writes under `.intlayer/`, so it stays quiet until the project has been built at least once. Two declarations sharing a key are merged, which is a legitimate pattern — the report exists because a field defined on both sides silently keeps only one of the two values.
282
+
283
+ The analyser is loaded from `@intlayer/lsp`, which ships as ESM. The rule therefore needs a Node version that can `require()` an ES module — Node 20.19+ or 22.12+. On anything older it reports nothing rather than failing the lint run.
284
+
285
+ ## Frameworks
286
+
287
+ Every rule works across all Intlayer integrations, including inside Vue, Svelte and Angular templates. You only need to tell ESLint which parser reads each file type.
288
+
289
+ | Framework | Files | Parser |
290
+ | ------------------------- | ----------------- | --------------------------------- |
291
+ | React, Preact, Solid, Lit | `.jsx` `.tsx` | `typescript-eslint` |
292
+ | Next.js | `.jsx` `.tsx` | `typescript-eslint` |
293
+ | Vue, Nuxt | `.vue` | `vue-eslint-parser` |
294
+ | Svelte, SvelteKit | `.svelte` | `svelte-eslint-parser` |
295
+ | Angular | `.ts` | `typescript-eslint` |
296
+ | Angular templates | `.component.html` | `@angular-eslint/template-parser` |
297
+ | Astro | `.astro` | `astro-eslint-parser` |
298
+
299
+ ```javascript fileName="eslint.config.mjs"
300
+ import intlayer from "eslint-plugin-intlayer";
301
+ import tseslint from "typescript-eslint";
302
+ import vueParser from "vue-eslint-parser";
303
+ import svelteParser from "svelte-eslint-parser";
304
+ import angularTemplateParser from "@angular-eslint/template-parser";
305
+
306
+ export default [
307
+ ...intlayer.configs.recommended,
308
+
309
+ {
310
+ files: ["**/*.{ts,tsx,jsx}"],
311
+ languageOptions: { parser: tseslint.parser },
312
+ },
313
+ {
314
+ files: ["**/*.vue"],
315
+ languageOptions: {
316
+ parser: vueParser,
317
+ parserOptions: { parser: tseslint.parser },
318
+ },
319
+ },
320
+ {
321
+ files: ["**/*.svelte"],
322
+ languageOptions: {
323
+ parser: svelteParser,
324
+ parserOptions: { parser: tseslint.parser },
325
+ },
326
+ },
327
+ {
328
+ files: ["**/*.component.html"],
329
+ languageOptions: { parser: angularTemplateParser },
330
+ },
331
+ ];
332
+ ```
333
+
334
+ Install only the parsers your project needs.
335
+
336
+ > **Known limitation.** In Vue and Angular templates, an expression such as `{{ content[key] }}` is not checked by `no-dynamic-field-access`. Dynamic reads written in the script block are caught normally.
@@ -19,6 +19,9 @@ slugs:
19
19
  - intlayer
20
20
  - getLocalizedPath
21
21
  history:
22
+ - version: 8.0.0
23
+ date: 2026-08-19
24
+ changes: "Apply the locale prefix, and narrow the return type from the declared rewrite rules"
22
25
  - version: 8.0.0
23
26
  date: 2026-01-22
24
27
  changes: "Implement custom URL rewrites"
@@ -29,13 +32,17 @@ author: aymericzip
29
32
 
30
33
  ## Description
31
34
 
32
- The `getLocalizedPath` function resolves a canonical path (internal application path) into its localized equivalent based on the provided locale and rewrite rules. It is particularly useful for generating SEO-friendly URLs that vary by language.
35
+ The `getLocalizedPath` function localizes a canonical path (internal application path): it resolves the custom rewrite rules, then applies the locale prefix of your routing mode. It is particularly useful for generating SEO-friendly URLs that vary by language.
36
+
37
+ It is the relative counterpart of [`getLocalizedUrl`](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/packages/intlayer/getLocalizedUrl.md) — for a relative input both return the same value. Unlike `getLocalizedUrl`, it never returns an absolute URL: the `domains` configuration is ignored, so a locale served from its own domain still yields a path. An absolute input is accepted, but its origin is dropped — only its path, query string and hash are kept.
33
38
 
34
39
  **Key Features:**
35
40
 
36
41
  - Supports dynamic route parameters using the `[param]` syntax.
37
42
  - Resolves paths according to custom rewrite rules defined in your configuration.
43
+ - Applies the locale prefix of the configured routing mode (`prefix-no-default`, `prefix-all`, …).
38
44
  - Automatically handles fallback to the canonical path if no rewrite rule is found for the specified locale.
45
+ - Narrows its return type: a literal path resolves to the localized string literal at compile time.
39
46
 
40
47
  ---
41
48
 
@@ -43,9 +50,14 @@ The `getLocalizedPath` function resolves a canonical path (internal application
43
50
 
44
51
  ```typescript
45
52
  getLocalizedPath(
46
- canonicalPath: string, // Required
47
- locale: Locales, // Required
48
- rewriteRules?: RoutingConfig['rewrite'] // Optional
53
+ canonicalPath: string, // Required
54
+ locale?: Locales, // Optional
55
+ options?: { // Optional
56
+ locales?: Locales[];
57
+ defaultLocale?: Locales;
58
+ mode?: 'prefix-no-default' | 'prefix-all' | 'no-prefix' | 'search-params';
59
+ rewrite?: RoutingConfig['rewrite'];
60
+ }
49
61
  ): string
50
62
  ```
51
63
 
@@ -60,17 +72,21 @@ getLocalizedPath(
60
72
  - **Type**: `string`
61
73
  - **Required**: Yes
62
74
 
63
- - `locale: Locales`
75
+ ### Optional Parameters
76
+
77
+ - `locale?: Locales`
64
78
  - **Description**: The target locale for which the path should be localized.
65
79
  - **Type**: `Locales`
66
- - **Required**: Yes
80
+ - **Default**: The default locale of your project's configuration.
67
81
 
68
- ### Optional Parameters
82
+ - `options?: object`
83
+ - **Description**: Routing overrides. Every entry defaults to your project's configuration.
84
+ - **Type**: `object`
69
85
 
70
- - `rewriteRules?: RoutingConfig['rewrite']`
71
- - **Description**: An object defining custom rewrite rules. If not provided, it defaults to the `routing.rewrite` property from your project's configuration.
72
- - **Type**: `RoutingConfig['rewrite']`
73
- - **Default**: `configuration.routing.rewrite`
86
+ - `options.locales?: Locales[]` — supported locales. **Default**: `configuration.internationalization.locales`
87
+ - `options.defaultLocale?: Locales` the default locale. **Default**: `configuration.internationalization.defaultLocale`
88
+ - `options.mode?: 'prefix-no-default' | 'prefix-all' | 'no-prefix' | 'search-params'` — how the locale appears in the path. **Default**: `configuration.routing.mode`
89
+ - `options.rewrite?: RoutingConfig['rewrite']` — custom rewrite rules. **Default**: `configuration.routing.rewrite`
74
90
 
75
91
  ---
76
92
 
@@ -79,6 +95,28 @@ getLocalizedPath(
79
95
  - **Type**: `string`
80
96
  - **Description**: The localized path for the specified locale.
81
97
 
98
+ The type is narrowed from the rewrite rules declared in your configuration, so the editor shows the resolved path rather than a bare `string`:
99
+
100
+ ```typescript codeFormat="typescript"
101
+ // Configuration: mode 'prefix-no-default', defaultLocale 'en',
102
+ // { '/about': { fr: '/a-propos' }, '/product/[id]': { fr: '/produit/[id]' } }
103
+ const about = getLocalizedPath("/about", Locales.FRENCH);
104
+ // ^? '/fr/a-propos'
105
+ const product = getLocalizedPath("/product/123", Locales.FRENCH);
106
+ // ^? '/fr/produit/123'
107
+ const contact = getLocalizedPath("/contact", Locales.FRENCH);
108
+ // ^? '/fr/contact' (no rewrite rule matches, only the prefix is applied)
109
+ const home = getLocalizedPath("/", Locales.FRENCH);
110
+ // ^? '/fr'
111
+ ```
112
+
113
+ The same narrowing flows into [`getLocalizedUrl`](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/packages/intlayer/getLocalizedUrl.md), which applies the rewrite rules before prefixing the locale.
114
+
115
+ Two cases stay widened to `string`, because they cannot be resolved at compile time:
116
+
117
+ - a path that is not a string literal (e.g. one built from a variable);
118
+ - a path matched by a rule using a multi-segment or optional parameter (`[...slug]`, `[[...slug]]`, `:param?`).
119
+
82
120
  ---
83
121
 
84
122
  ## Example Usage
@@ -90,12 +128,13 @@ If you have configured custom rewrites in your `intlayer.config.ts`:
90
128
  ```typescript codeFormat="typescript"
91
129
  import { getLocalizedPath, Locales } from "intlayer";
92
130
 
93
- // Configuration: { '/about': { en: '/about', fr: '/a-propos' } }
131
+ // Configuration: mode 'prefix-no-default', defaultLocale 'en',
132
+ // { '/about': { en: '/about', fr: '/a-propos' } }
94
133
  getLocalizedPath("/about", Locales.FRENCH);
95
- // Output: "/a-propos"
134
+ // Output: "/fr/a-propos"
96
135
 
97
136
  getLocalizedPath("/about", Locales.ENGLISH);
98
- // Output: "/about"
137
+ // Output: "/about" (default locale, no prefix)
99
138
  ```
100
139
 
101
140
  ### Usage with Dynamic Routes
@@ -105,7 +144,7 @@ import { getLocalizedPath, Locales } from "intlayer";
105
144
 
106
145
  // Configuration: { '/product/[id]': { en: '/product/[id]', fr: '/produit/[id]' } }
107
146
  getLocalizedPath("/product/123", Locales.FRENCH);
108
- // Output: "/produit/123"
147
+ // Output: "/fr/produit/123"
109
148
  ```
110
149
 
111
150
  ### Manual Rewrite Rules
@@ -122,13 +161,25 @@ const manualRules = {
122
161
  },
123
162
  };
124
163
 
125
- getLocalizedPath("/contact", Locales.FRENCH, manualRules);
126
- // Output: "/contactez-nous"
164
+ getLocalizedPath("/contact", Locales.FRENCH, { rewrite: manualRules });
165
+ // Output: "/fr/contactez-nous"
166
+ ```
167
+
168
+ ### Omitting the Locale
169
+
170
+ When no locale is given, the path is localized for the configured default locale:
171
+
172
+ ```typescript codeFormat="typescript"
173
+ import { getLocalizedPath } from "intlayer";
174
+
175
+ // Configuration: defaultLocale = Locales.ENGLISH, { '/about': { en: '/about', fr: '/a-propos' } }
176
+ getLocalizedPath("/about");
177
+ // Output: "/about"
127
178
  ```
128
179
 
129
180
  ---
130
181
 
131
182
  ## Related Functions
132
183
 
133
- - [`getCanonicalPath`](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/packages/intlayer/getCanonicalPath.md): Resolves a localized path back to its internal canonical path.
134
- - [`getLocalizedUrl`](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/packages/intlayer/getLocalizedUrl.md): Generates a fully localized URL (including protocol, host, and locale prefix).
184
+ - [`getCanonicalPath`](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/packages/intlayer/getCanonicalPath.md): Resolves a localized path back to its internal canonical path. Note that it undoes the rewrite rules only — strip the locale prefix with [`getPathWithoutLocale`](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/packages/intlayer/getPathWithoutLocale.md) first.
185
+ - [`getLocalizedUrl`](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/packages/intlayer/getLocalizedUrl.md): Same localization, but able to return an absolute URL (protocol, host, domain routing).
@@ -479,6 +479,28 @@ const config: IntlayerConfig = {
479
479
  */
480
480
  purge: true,
481
481
 
482
+ /**
483
+ * Group the per-locale dictionary chunks by the code-split boundary that uses
484
+ * them, so a lazily loaded page fetches its content in one request.
485
+ * Default: true
486
+ *
487
+ * Note:
488
+ * - Only applies to dictionaries using `importMode: 'dynamic'`.
489
+ */
490
+ chunkGrouping: true,
491
+
492
+ /**
493
+ * Load a dictionary together with the chunk that uses it, instead of fetching
494
+ * it once that chunk renders. Readers render synchronously instead of
495
+ * suspending, so navigating no longer flashes a loading state.
496
+ * Default: true
497
+ *
498
+ * Note:
499
+ * - Only the resolved locale is awaited, so a page still downloads only the
500
+ * language it renders.
501
+ */
502
+ dictionariesPreload: true,
503
+
482
504
  /**
483
505
  * Output format for generated dictionary files.
484
506
  * Default: ['cjs', 'esm']
@@ -1060,15 +1082,17 @@ Build options apply to the `@intlayer/babel` and `@intlayer/swc` plugins.
1060
1082
 
1061
1083
  > When optimized, Intlayer will replace dictionary calls to optimize chunking, so the final bundle only imports dictionaries that are actually used.
1062
1084
 
1063
- | Field | Description | Type | Default | Example | Note |
1064
- | ----------------- | ------------------------------------------------------------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1065
- | `mode` | Controls the mode of the build. | `'auto'` &#124; <br/> `'manual'` | `'auto'` | `'manual'` | • `'auto'`: build enabled automatically when the application is built.<br/>• `'manual'`: only runs when the build command is executed.<br/>• Can be used to disable dictionary builds (e.g. to avoid running in Node.js environments). |
1066
- | `optimize` | Controls whether the build should be optimized. | `boolean` | `undefined` | `process.env.NODE_ENV === 'production'` | • If unset, optimization is triggered on framework build (Vite/Next.js).<br/>• `true` forces optimization including dev mode.<br/>• `false` disables it.<br/>• When enabled, replaces dictionary calls to optimize chunking - only used dictionaries are imported.<br/>• Relies on `@intlayer/babel` and `@intlayer/swc` plugins.<br/>• Keys must be declared statically. |
1067
- | `minify` | Defines if the dictionaries should be minified to reduce the bundle size. | `boolean` | `false` | | • Defines if the bundle should be minified.<br/>• Default: `false` in production.<br/>• This option will be ignored if `optimize` is disabled.<br/>• This option will be ignored if `editor.enabled` is true. |
1068
- | `prune` | Defines if the unused keys in dictionaries should be purged. | `boolean` | `true` | | • Defines if the bundle should be pruned.<br/>• Default: `true` in production.<br/>• This option will be ignored if `optimize` is disabled. |
1069
- | `checkTypes` | Indicates if the build should check TypeScript types and log errors. | `boolean` | `false` | | Can slow down the build. |
1070
- | `outputFormat` | Controls the output format of the dictionaries. | `('esm' &#124; 'cjs')[]` | `['esm', 'cjs']` | `['cjs']` | |
1071
- | `traversePattern` | Patterns defining which files to traverse during optimization. | `string[]` | `['**/*.{tsx,ts,js,mjs,cjs,jsx,vue,svelte,svte}', '!**/node_modules/**', '!**/dist/**', '!**/.intlayer/**', '!**/*.config.*', '!**/*.test.*', '!**/*.spec.*', '!**/*.stories.*']` | `['src/**/*.{ts,tsx}', '../ui-library/**/*.{ts,tsx}', '!**/node_modules/**']` | Limit optimization to relevant files to improve build performance.<br/>• Ignored if `optimize` is disabled.<br/>• Uses glob pattern. |
1085
+ | Field | Description | Type | Default | Example | Note |
1086
+ | --------------------- | ------------------------------------------------------------------------------------------------------------------------ | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1087
+ | `mode` | Controls the mode of the build. | `'auto'` &#124; <br/> `'manual'` | `'auto'` | `'manual'` | • `'auto'`: build enabled automatically when the application is built.<br/>• `'manual'`: only runs when the build command is executed.<br/>• Can be used to disable dictionary builds (e.g. to avoid running in Node.js environments). |
1088
+ | `optimize` | Controls whether the build should be optimized. | `boolean` | `undefined` | `process.env.NODE_ENV === 'production'` | • If unset, optimization is triggered on framework build (Vite/Next.js).<br/>• `true` forces optimization including dev mode.<br/>• `false` disables it.<br/>• When enabled, replaces dictionary calls to optimize chunking - only used dictionaries are imported.<br/>• Relies on `@intlayer/babel` and `@intlayer/swc` plugins.<br/>• Keys must be declared statically. |
1089
+ | `minify` | Defines if the dictionaries should be minified to reduce the bundle size. | `boolean` | `false` | | • Defines if the bundle should be minified.<br/>• Default: `false` in production.<br/>• This option will be ignored if `optimize` is disabled.<br/>• This option will be ignored if `editor.enabled` is true. |
1090
+ | `prune` | Defines if the unused keys in dictionaries should be purged. | `boolean` | `true` | | • Defines if the bundle should be pruned.<br/>• Default: `true` in production.<br/>• This option will be ignored if `optimize` is disabled. |
1091
+ | `checkTypes` | Indicates if the build should check TypeScript types and log errors. | `boolean` | `false` | | Can slow down the build. |
1092
+ | `chunkGrouping` | Whether to group the per-locale dictionary chunks by the code-split boundary that uses them. | `boolean` | `true` | | • Without grouping, a page assembled from many components issues one request per dictionary.<br/>• Dictionaries reached from several boundaries move to a shared chunk, so no page ships another page's content.<br/>• Only applies to dictionaries using `importMode: 'dynamic'`.<br/>• Only applies to the client build, and only when bundling (not in dev). |
1093
+ | `dictionariesPreload` | Whether a dictionary should load together with the chunk that uses it, instead of being fetched once that chunk renders. | `boolean` | `true` | | The generated entry point awaits the browsing locale at the top level, so a lazily loaded route is not considered loaded until its content is there.<br/>• Readers render synchronously instead of suspending, so navigating no longer flashes a loading state.<br/>• Only the resolved locale is awaited, so a page still downloads only the language it renders.<br/>• Only applies to dictionaries using `importMode: 'dynamic'`, on the client build.<br/>• Requires a bundler supporting top-level await (Vite, esbuild). |
1094
+ | `outputFormat` | Controls the output format of the dictionaries. | `('esm' &#124; 'cjs')[]` | `['esm', 'cjs']` | `['cjs']` | |
1095
+ | `traversePattern` | Patterns defining which files to traverse during optimization. | `string[]` | `['**/*.{tsx,ts,js,mjs,cjs,jsx,vue,svelte,svte}', '!**/node_modules/**', '!**/dist/**', '!**/.intlayer/**', '!**/*.config.*', '!**/*.test.*', '!**/*.spec.*', '!**/*.stories.*']` | `['src/**/*.{ts,tsx}', '../ui-library/**/*.{ts,tsx}', '!**/node_modules/**']` | • Limit optimization to relevant files to improve build performance.<br/>• Ignored if `optimize` is disabled.<br/>• Uses glob pattern. |
1072
1096
 
1073
1097
  ---
1074
1098
 
@@ -532,28 +532,6 @@ Used in conjunction with Variants, this field defines named content alternatives
532
532
 
533
533
  > See [Variants](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en-GB/dynamic_dictionaries/variants.md) for more information.
534
534
 
535
- #### `meta` (`Record<string, string | number | boolean>`)
536
-
537
- Used in conjunction with Dynamic Records, this field allows declaring CMS-managed records or arbitrary data fetched at runtime by an opaque ID. The dictionary identity is defined by the arbitrary set of key-value pairs declared in this `meta` field.
538
-
539
- **Example:**
540
-
541
- ```typescript
542
- {
543
- key: "product-copy",
544
- meta: {
545
- id: "prod_abc",
546
- userId: "user_123"
547
- },
548
- content: {
549
- name: "Widget Pro",
550
- description: "The best widget."
551
- }
552
- }
553
- ```
554
-
555
- > See [Dynamic Records](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en-GB/dynamic_dictionaries/dynamic_content.md) for more information.
556
-
557
535
  ### CMS Properties
558
536
 
559
537
  ##### `version` (string)
@@ -85,6 +85,29 @@ No way to fetch content from a JSON file, use a .ts or .js file instead
85
85
 
86
86
  In this case, the `fakeFetch` function mimics a delay to simulate server response time. Intlayer executes the asynchronous function and uses the result as the content for the `text` key.
87
87
 
88
+ ## Fetching Remote Content
89
+
90
+ You can also assign a promise directly to a content field. Intlayer awaits it while building the dictionaries and inlines the resolved value:
91
+
92
+ ```typescript fileName="**/*.content.ts" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
93
+ import type { Dictionary } from "intlayer";
94
+
95
+ const remoteContent = {
96
+ key: "remote_content",
97
+ content: {
98
+ externalContent: fetch("https://example.com").then((res) => res.json()),
99
+ },
100
+ } satisfies Dictionary;
101
+
102
+ export default remoteContent;
103
+ ```
104
+
105
+ ```plaintext fileName="**/*.content.json" contentDeclarationFormat="json"
106
+ No way to fetch content from a JSON file, use a .ts or .js file instead
107
+ ```
108
+
109
+ > The request runs at build time, so the fetched data is a snapshot embedded in the dictionary. Rebuild your dictionaries to refresh it.
110
+
88
111
  ## Using Function-Based Content in React Components
89
112
 
90
113
  To use function-based content in a React component, you need to import `useIntlayer` from `react-intlayer` and call it with the content ID to retrieve the content. Here's an example: