@intlayer/docs 9.0.0-canary.7 → 9.0.0-canary.9
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/dist/cjs/generated/docs.entry.cjs +0 -20
- package/dist/cjs/generated/docs.entry.cjs.map +1 -1
- package/dist/esm/generated/docs.entry.mjs +0 -20
- package/dist/esm/generated/docs.entry.mjs.map +1 -1
- package/dist/types/generated/docs.entry.d.ts +0 -1
- package/dist/types/generated/docs.entry.d.ts.map +1 -1
- package/docs/ar/dynamic_dictionaries/index.md +24 -18
- package/docs/ar/dynamic_dictionaries/variants.md +280 -22
- package/docs/ar/plugins/sync-json.md +0 -1
- package/docs/ar/releases/v9.md +35 -12
- package/docs/de/dynamic_dictionaries/index.md +22 -22
- package/docs/de/dynamic_dictionaries/variants.md +272 -14
- package/docs/de/plugins/sync-json.md +0 -1
- package/docs/de/releases/v9.md +35 -12
- package/docs/en/dynamic_dictionaries/index.md +20 -14
- package/docs/en/dynamic_dictionaries/variants.md +266 -8
- package/docs/en/plugins/sync-json.md +0 -1
- package/docs/en/releases/v9.md +35 -12
- package/docs/en-GB/dynamic_dictionaries/index.md +21 -15
- package/docs/en-GB/dynamic_dictionaries/variants.md +268 -10
- package/docs/en-GB/plugins/sync-json.md +0 -1
- package/docs/en-GB/releases/v9.md +35 -12
- package/docs/es/dynamic_dictionaries/index.md +25 -19
- package/docs/es/dynamic_dictionaries/variants.md +275 -17
- package/docs/es/plugins/sync-json.md +0 -1
- package/docs/es/releases/v9.md +35 -12
- package/docs/fr/dynamic_dictionaries/index.md +26 -20
- package/docs/fr/dynamic_dictionaries/variants.md +273 -15
- package/docs/fr/plugins/sync-json.md +0 -1
- package/docs/fr/releases/v9.md +35 -12
- package/docs/hi/dynamic_dictionaries/index.md +28 -22
- package/docs/hi/dynamic_dictionaries/variants.md +282 -24
- package/docs/hi/plugins/sync-json.md +0 -1
- package/docs/hi/releases/v9.md +35 -12
- package/docs/id/dynamic_dictionaries/index.md +25 -19
- package/docs/id/dynamic_dictionaries/variants.md +274 -16
- package/docs/id/plugins/sync-json.md +0 -1
- package/docs/id/releases/v9.md +35 -12
- package/docs/it/dynamic_dictionaries/index.md +26 -20
- package/docs/it/dynamic_dictionaries/variants.md +273 -15
- package/docs/it/plugins/sync-json.md +0 -1
- package/docs/it/releases/v9.md +35 -12
- package/docs/ja/dynamic_dictionaries/index.md +27 -21
- package/docs/ja/dynamic_dictionaries/variants.md +276 -18
- package/docs/ja/plugins/sync-json.md +0 -1
- package/docs/ja/releases/v9.md +35 -12
- package/docs/ko/dynamic_dictionaries/index.md +23 -17
- package/docs/ko/dynamic_dictionaries/variants.md +273 -15
- package/docs/ko/plugins/sync-json.md +0 -1
- package/docs/ko/releases/v9.md +35 -12
- package/docs/pl/dynamic_dictionaries/index.md +26 -20
- package/docs/pl/dynamic_dictionaries/variants.md +275 -17
- package/docs/pl/plugins/sync-json.md +0 -1
- package/docs/pl/releases/v9.md +35 -12
- package/docs/pt/dynamic_dictionaries/index.md +25 -19
- package/docs/pt/dynamic_dictionaries/variants.md +276 -18
- package/docs/pt/plugins/sync-json.md +0 -1
- package/docs/pt/releases/v9.md +35 -12
- package/docs/ru/dynamic_dictionaries/index.md +25 -19
- package/docs/ru/dynamic_dictionaries/variants.md +277 -19
- package/docs/ru/plugins/sync-json.md +0 -1
- package/docs/ru/releases/v9.md +35 -12
- package/docs/tr/dynamic_dictionaries/index.md +27 -21
- package/docs/tr/dynamic_dictionaries/variants.md +274 -16
- package/docs/tr/plugins/sync-json.md +0 -1
- package/docs/tr/releases/v9.md +35 -12
- package/docs/uk/dynamic_dictionaries/index.md +25 -19
- package/docs/uk/dynamic_dictionaries/variants.md +277 -19
- package/docs/uk/plugins/sync-json.md +0 -1
- package/docs/uk/releases/v9.md +35 -12
- package/docs/vi/dynamic_dictionaries/index.md +28 -22
- package/docs/vi/dynamic_dictionaries/variants.md +278 -20
- package/docs/vi/plugins/sync-json.md +0 -1
- package/docs/vi/releases/v9.md +35 -12
- package/docs/zh/dynamic_dictionaries/index.md +24 -18
- package/docs/zh/dynamic_dictionaries/variants.md +274 -16
- package/docs/zh/plugins/sync-json.md +0 -1
- package/docs/zh/releases/v9.md +35 -12
- package/package.json +6 -6
- package/src/generated/docs.entry.ts +0 -20
- package/docs/ar/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/de/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/en/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/en-GB/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/es/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/fr/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/hi/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/id/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/it/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/ja/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/ko/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/pl/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/pt/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/ru/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/tr/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/uk/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/vi/dynamic_dictionaries/dynamic_content.md +0 -271
- package/docs/zh/dynamic_dictionaries/dynamic_content.md +0 -271
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
createdAt: 2026-06-12
|
|
3
|
-
updatedAt: 2026-06-
|
|
3
|
+
updatedAt: 2026-06-26
|
|
4
4
|
title: Variants
|
|
5
|
-
description: Use the variant metadata field in Intlayer content files to declare named content alternatives — A/B tests, seasonal banners, feature-flagged copy — and switch between them at runtime without code changes.
|
|
5
|
+
description: Use the variant metadata field in Intlayer content files to declare named or structured content alternatives — A/B tests, seasonal banners, feature-flagged copy, CMS records, user-specific content — and switch between them at runtime without code changes.
|
|
6
6
|
keywords:
|
|
7
7
|
- Variants
|
|
8
8
|
- A/B Testing
|
|
9
9
|
- Feature Flags
|
|
10
10
|
- Dynamic Content
|
|
11
|
+
- Dynamic Records
|
|
12
|
+
- CMS
|
|
11
13
|
- Intlayer
|
|
12
14
|
- Internationalization
|
|
13
15
|
slugs:
|
|
@@ -18,14 +20,24 @@ history:
|
|
|
18
20
|
- version: 9.0.0
|
|
19
21
|
date: 2026-06-12
|
|
20
22
|
changes: "Release of the variants feature"
|
|
23
|
+
- version: 9.1.0
|
|
24
|
+
date: 2026-06-26
|
|
25
|
+
changes: "`variant` now accepts a string or an object — the former `meta` / dynamic records are declared as object variants"
|
|
21
26
|
author: aymericzip
|
|
22
27
|
---
|
|
23
28
|
|
|
24
29
|
# Variants
|
|
25
30
|
|
|
26
|
-
A **variant** is a set of content files that share the same dictionary `key` but each carry a different `variant`
|
|
31
|
+
A **variant** is a set of content files that share the same dictionary `key` but each carry a different `variant` value. Intlayer serves the appropriate file based on the selector passed to `useIntlayer`.
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
The `variant` value can take **two forms**:
|
|
34
|
+
|
|
35
|
+
- **A string** — a single named alternative (A/B tests, seasonal banners, feature flags).
|
|
36
|
+
- **An object** — a structured discriminator addressed by a set of fields (CMS records, user-specific copy, any content keyed by an opaque ID). The whole object is the identity: the selector must provide an **equal** object to resolve the entry.
|
|
37
|
+
|
|
38
|
+
> The object form replaces the former `meta` field. Anywhere you previously wrote `meta: { id, … }`, write `variant: { id, … }`, and select it with `{ variant: { id, … } }`.
|
|
39
|
+
|
|
40
|
+
## Named (string) variants
|
|
29
41
|
|
|
30
42
|
Each file represents one named alternative. Omitting `variant` (or setting it to `"default"`) marks it as the fallback.
|
|
31
43
|
|
|
@@ -65,9 +77,9 @@ const dictionary = {
|
|
|
65
77
|
export default dictionary;
|
|
66
78
|
```
|
|
67
79
|
|
|
68
|
-
|
|
80
|
+
### Consuming named variants
|
|
69
81
|
|
|
70
|
-
|
|
82
|
+
#### Default variant
|
|
71
83
|
|
|
72
84
|
<Tabs group="framework">
|
|
73
85
|
<Tab label="React" value="react">
|
|
@@ -209,7 +221,7 @@ export default dictionary;
|
|
|
209
221
|
</Tab>
|
|
210
222
|
</Tabs>
|
|
211
223
|
|
|
212
|
-
|
|
224
|
+
#### Named variant
|
|
213
225
|
|
|
214
226
|
```tsx
|
|
215
227
|
const { headline, cta } = useIntlayer("hero-banner", {
|
|
@@ -217,7 +229,7 @@ const { headline, cta } = useIntlayer("hero-banner", {
|
|
|
217
229
|
});
|
|
218
230
|
```
|
|
219
231
|
|
|
220
|
-
|
|
232
|
+
#### Named variant with explicit locale
|
|
221
233
|
|
|
222
234
|
```tsx
|
|
223
235
|
const content = useIntlayer("hero-banner", {
|
|
@@ -226,9 +238,255 @@ const content = useIntlayer("hero-banner", {
|
|
|
226
238
|
});
|
|
227
239
|
```
|
|
228
240
|
|
|
241
|
+
## Object (structured) variants
|
|
242
|
+
|
|
243
|
+
An object variant addresses content by an arbitrary set of key-value pairs declared in the `variant` field — making it possible to model CMS records, user-specific copy, or any content whose key is an opaque ID. The **whole object** is the identity: the selector must provide an equal object for the entry to resolve.
|
|
244
|
+
|
|
245
|
+
```ts fileName="product.abc.content.ts" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
|
|
246
|
+
import { t, type Dictionary } from "intlayer";
|
|
247
|
+
|
|
248
|
+
const dictionary = {
|
|
249
|
+
key: "product",
|
|
250
|
+
variant: { id: "prod_abc", userId: "user_123" },
|
|
251
|
+
content: {
|
|
252
|
+
name: t({ en: "Widget Pro", fr: "Widget Pro" }),
|
|
253
|
+
description: t({ en: "The best widget.", fr: "Le meilleur widget." }),
|
|
254
|
+
},
|
|
255
|
+
} satisfies Dictionary;
|
|
256
|
+
|
|
257
|
+
export default dictionary;
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
```ts fileName="product.abcd.content.ts" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
|
|
261
|
+
import { t, type Dictionary } from "intlayer";
|
|
262
|
+
|
|
263
|
+
const dictionary = {
|
|
264
|
+
key: "product",
|
|
265
|
+
variant: { id: "prod_abcd", userId: "user_123" },
|
|
266
|
+
content: {
|
|
267
|
+
name: t({ en: "Widget Lite", fr: "Widget Lite" }),
|
|
268
|
+
description: t({ en: "A lighter option.", fr: "Une option plus légère." }),
|
|
269
|
+
},
|
|
270
|
+
} satisfies Dictionary;
|
|
271
|
+
|
|
272
|
+
export default dictionary;
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Consuming object variants
|
|
276
|
+
|
|
277
|
+
Pass the matching object to `variant`. Every field declared on the dictionary must be provided and equal; otherwise the result is `null`. Field order does not matter.
|
|
278
|
+
|
|
279
|
+
<Tabs group="framework">
|
|
280
|
+
<Tab label="React" value="react">
|
|
281
|
+
```tsx fileName="Product.tsx" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
|
|
282
|
+
import { useIntlayer } from "react-intlayer";
|
|
283
|
+
|
|
284
|
+
export const Product = ({
|
|
285
|
+
productId,
|
|
286
|
+
userId,
|
|
287
|
+
}: {
|
|
288
|
+
productId: string;
|
|
289
|
+
userId: string;
|
|
290
|
+
}) => {
|
|
291
|
+
const content = useIntlayer("product", {
|
|
292
|
+
variant: { id: productId, userId },
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
if (!content) return null;
|
|
296
|
+
|
|
297
|
+
return <p>{content.description}</p>;
|
|
298
|
+
};
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
</Tab>
|
|
302
|
+
<Tab label="Next.js" value="nextjs">
|
|
303
|
+
```tsx fileName="Product.tsx" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
|
|
304
|
+
import { useIntlayer } from "next-intlayer";
|
|
305
|
+
|
|
306
|
+
export const Product = ({
|
|
307
|
+
productId,
|
|
308
|
+
userId,
|
|
309
|
+
}: {
|
|
310
|
+
productId: string;
|
|
311
|
+
userId: string;
|
|
312
|
+
}) => {
|
|
313
|
+
const content = useIntlayer("product", {
|
|
314
|
+
variant: { id: productId, userId },
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
if (!content) return null;
|
|
318
|
+
|
|
319
|
+
return <p>{content.description}</p>;
|
|
320
|
+
};
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
</Tab>
|
|
324
|
+
<Tab label="Vue" value="vue">
|
|
325
|
+
```vue fileName="Product.vue" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
|
|
326
|
+
<script setup>
|
|
327
|
+
import { useIntlayer } from "vue-intlayer";
|
|
328
|
+
|
|
329
|
+
const props = defineProps({
|
|
330
|
+
productId: String,
|
|
331
|
+
userId: String,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const content = useIntlayer("product", {
|
|
335
|
+
variant: { id: props.productId, userId: props.userId },
|
|
336
|
+
});
|
|
337
|
+
</script>
|
|
338
|
+
|
|
339
|
+
<template>
|
|
340
|
+
<p v-if="content">{{ content.description }}</p>
|
|
341
|
+
</template>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
</Tab>
|
|
345
|
+
<Tab label="Svelte" value="svelte">
|
|
346
|
+
```svelte fileName="Product.svelte" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
|
|
347
|
+
<script lang="ts">
|
|
348
|
+
import { useIntlayer } from "svelte-intlayer";
|
|
349
|
+
|
|
350
|
+
export let productId: string;
|
|
351
|
+
export let userId: string;
|
|
352
|
+
|
|
353
|
+
const content = useIntlayer("product", {
|
|
354
|
+
variant: { id: productId, userId },
|
|
355
|
+
});
|
|
356
|
+
</script>
|
|
357
|
+
|
|
358
|
+
{#if $content}
|
|
359
|
+
<p>{$content.description}</p>
|
|
360
|
+
{/if}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
</Tab>
|
|
364
|
+
<Tab label="Preact" value="preact">
|
|
365
|
+
```tsx fileName="Product.tsx" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
|
|
366
|
+
import { useIntlayer } from "preact-intlayer";
|
|
367
|
+
|
|
368
|
+
export const Product = ({
|
|
369
|
+
productId,
|
|
370
|
+
userId,
|
|
371
|
+
}: {
|
|
372
|
+
productId: string;
|
|
373
|
+
userId: string;
|
|
374
|
+
}) => {
|
|
375
|
+
const content = useIntlayer("product", {
|
|
376
|
+
variant: { id: productId, userId },
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
if (!content) return null;
|
|
380
|
+
|
|
381
|
+
return <p>{content.description}</p>;
|
|
382
|
+
};
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
</Tab>
|
|
386
|
+
<Tab label="Solid" value="solid">
|
|
387
|
+
```tsx fileName="Product.tsx" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
|
|
388
|
+
import { useIntlayer } from "solid-intlayer";
|
|
389
|
+
|
|
390
|
+
export const Product = (props: {
|
|
391
|
+
productId: string;
|
|
392
|
+
userId: string;
|
|
393
|
+
}) => {
|
|
394
|
+
const content = useIntlayer("product", {
|
|
395
|
+
variant: { id: props.productId, userId: props.userId },
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
return (
|
|
399
|
+
<>
|
|
400
|
+
{content() && <p>{content().description}</p>}
|
|
401
|
+
</>
|
|
402
|
+
);
|
|
403
|
+
};
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
</Tab>
|
|
407
|
+
<Tab label="Angular" value="angular">
|
|
408
|
+
```typescript fileName="product.component.ts" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
|
|
409
|
+
import { Component, Input, OnInit } from "@angular/core";
|
|
410
|
+
import { useIntlayer } from "angular-intlayer";
|
|
411
|
+
|
|
412
|
+
@Component({
|
|
413
|
+
selector: "app-product",
|
|
414
|
+
template: `
|
|
415
|
+
@if (content()) {
|
|
416
|
+
<p>{{ content().description }}</p>
|
|
417
|
+
}
|
|
418
|
+
`,
|
|
419
|
+
})
|
|
420
|
+
export class ProductComponent implements OnInit {
|
|
421
|
+
@Input() productId!: string;
|
|
422
|
+
@Input() userId!: string;
|
|
423
|
+
|
|
424
|
+
content: any;
|
|
425
|
+
|
|
426
|
+
ngOnInit() {
|
|
427
|
+
this.content = useIntlayer("product", {
|
|
428
|
+
variant: { id: this.productId, userId: this.userId },
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
</Tab>
|
|
435
|
+
<Tab label="Vanilla JS" value="vanilla">
|
|
436
|
+
```javascript fileName="product.js"
|
|
437
|
+
import { useIntlayer } from "vanilla-intlayer";
|
|
438
|
+
|
|
439
|
+
const content = useIntlayer("product", {
|
|
440
|
+
variant: { id: "prod_abcd", userId: "user_123" },
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
if (content) {
|
|
444
|
+
document.body.innerHTML = `<p>${content.description}</p>`;
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
</Tab>
|
|
449
|
+
</Tabs>
|
|
450
|
+
|
|
451
|
+
#### With explicit locale
|
|
452
|
+
|
|
453
|
+
```tsx
|
|
454
|
+
const content = useIntlayer("product", {
|
|
455
|
+
variant: { id: "prod_abc", userId: "user_123" },
|
|
456
|
+
locale: "fr",
|
|
457
|
+
});
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
#### Missing field — no match
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
// Returns null: `userId` is missing, so the object does not match the declared variant
|
|
464
|
+
const content = useIntlayer("product", { variant: { id: "prod_abc" } });
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## Loading mode
|
|
468
|
+
|
|
469
|
+
Object variants are often loaded lazily. Set `importMode` on the dictionary to control this:
|
|
470
|
+
|
|
471
|
+
```ts contentDeclarationFormat={["typescript", "esm", "commonjs"]}
|
|
472
|
+
const dictionary = {
|
|
473
|
+
key: "product",
|
|
474
|
+
importMode: "fetch", // or "dynamic"
|
|
475
|
+
variant: { id: "prod_abc", userId: "user_123" },
|
|
476
|
+
content: { … },
|
|
477
|
+
} satisfies Dictionary;
|
|
478
|
+
|
|
479
|
+
export default dictionary;
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
See [bundle optimization](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/bundle_optimization.md) for details on `static`, `dynamic`, and `fetch` modes.
|
|
483
|
+
|
|
229
484
|
## Typical use-cases
|
|
230
485
|
|
|
231
486
|
- A/B copy tests driven by an experiment key
|
|
232
487
|
- Seasonal or promotional banners
|
|
233
488
|
- Feature-flagged messaging
|
|
234
489
|
- Locale-specific marketing campaigns
|
|
490
|
+
- Per-product marketing copy managed in a CMS
|
|
491
|
+
- User-specific or account-specific content
|
|
492
|
+
- Any content keyed by an opaque runtime ID
|
|
@@ -108,7 +108,6 @@ const config: IntlayerConfig = {
|
|
|
108
108
|
* Will load, and write the output and translations back to the JSON files in the locales directory
|
|
109
109
|
*/
|
|
110
110
|
syncJSON({
|
|
111
|
-
format: "i18next",
|
|
112
111
|
source: ({ key, locale }) => `./locales/${locale}/${key}.json`,
|
|
113
112
|
priority: 0,
|
|
114
113
|
format: "i18next",
|
package/docs/en/releases/v9.md
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
createdAt: 2026-06-14
|
|
3
|
-
updatedAt: 2026-06-
|
|
3
|
+
updatedAt: 2026-06-26
|
|
4
4
|
title: New Intlayer v9 - What's new?
|
|
5
|
-
description: Discover what's new in Intlayer v9. Introducing drop-in compatibility packages for popular i18n libraries and support for Collections
|
|
5
|
+
description: Discover what's new in Intlayer v9. Introducing drop-in compatibility packages for popular i18n libraries and support for Collections and Variants.
|
|
6
6
|
keywords:
|
|
7
7
|
- Intlayer
|
|
8
8
|
- Compatibility
|
|
9
9
|
- Migration
|
|
10
10
|
- Collections
|
|
11
11
|
- Variants
|
|
12
|
-
- Dynamic Records
|
|
13
12
|
- i18next
|
|
14
13
|
- next-intl
|
|
15
14
|
- vue-i18n
|
|
@@ -22,7 +21,7 @@ author: aymericzip
|
|
|
22
21
|
|
|
23
22
|
# New Intlayer v9 - What's new?
|
|
24
23
|
|
|
25
|
-
Welcome to Intlayer v9! This major release marks a huge milestone in simplifying the migration path to Intlayer with **Compat Adapter Packages** for major i18n libraries (`react-i18next`, `next-intl`, `vue-i18n`, etc.) and adds support for rich content structures: **Collections
|
|
24
|
+
Welcome to Intlayer v9! This major release marks a huge milestone in simplifying the migration path to Intlayer with **Compat Adapter Packages** for major i18n libraries (`react-i18next`, `next-intl`, `vue-i18n`, etc.) and adds support for rich content structures: **Collections** and **Variants**.
|
|
26
25
|
|
|
27
26
|
## Table of contents
|
|
28
27
|
|
|
@@ -41,8 +40,10 @@ By replacing imports, you get all the benefits of Intlayer (including compile-ti
|
|
|
41
40
|
- `@intlayer/i18next`
|
|
42
41
|
- `@intlayer/react-i18next`
|
|
43
42
|
- `@intlayer/next-intl`
|
|
43
|
+
- `@intlayer/react-intl`
|
|
44
44
|
- `@intlayer/next-i18next`
|
|
45
45
|
- `@intlayer/vue-i18n`
|
|
46
|
+
- `@intlayer/lingui`
|
|
46
47
|
|
|
47
48
|
For example, simply change:
|
|
48
49
|
|
|
@@ -100,7 +101,7 @@ All compat adapters now route translation resolution through a single, highly op
|
|
|
100
101
|
|
|
101
102
|
---
|
|
102
103
|
|
|
103
|
-
## Feature Spec: Collections
|
|
104
|
+
## Feature Spec: Collections & Variants
|
|
104
105
|
|
|
105
106
|
Intlayer v9 expands beyond static key-value objects, allowing dictionaries to declare dynamic layout structures that are fully typed end-to-end.
|
|
106
107
|
|
|
@@ -136,6 +137,8 @@ const faq = useIntlayer("faq", { item: 1 }); // -> { question: string, answer: s
|
|
|
136
137
|
|
|
137
138
|
Deliver A/B tests, seasonal headers, feature flags, or custom landing pages:
|
|
138
139
|
|
|
140
|
+
#### String variants (e.g. A/B testing)
|
|
141
|
+
|
|
139
142
|
```ts fileName="hero.content.ts"
|
|
140
143
|
import { t, type Dictionary } from "intlayer";
|
|
141
144
|
|
|
@@ -155,16 +158,14 @@ export default {
|
|
|
155
158
|
const banner = useIntlayer("hero-banner", { variant: "black_friday" });
|
|
156
159
|
```
|
|
157
160
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
Define dictionaries whose entries are loaded dynamically at runtime via query IDs:
|
|
161
|
+
#### Object variants (e.g. Dynamic Records)
|
|
161
162
|
|
|
162
163
|
```ts fileName="product.content.ts"
|
|
163
164
|
import { t, type Dictionary } from "intlayer";
|
|
164
165
|
|
|
165
166
|
export default {
|
|
166
167
|
key: "product-copy",
|
|
167
|
-
|
|
168
|
+
variant: {
|
|
168
169
|
id: "prod_123",
|
|
169
170
|
category: "books",
|
|
170
171
|
},
|
|
@@ -217,7 +218,7 @@ export default defineConfig({
|
|
|
217
218
|
});
|
|
218
219
|
```
|
|
219
220
|
|
|
220
|
-
- **Compiler**: Activates automatically when `compiler.enabled` is set and a `compiler.output` path is configured. You no longer need to register `intlayerCompiler()` separately.
|
|
221
|
+
- **Compiler**: Activates automatically when `compiler.enabled` is set to `true` and a `compiler.output` path is configured. You no longer need to register `intlayerCompiler()` separately.
|
|
221
222
|
- **Proxy**: Activates automatically based on the new `routing.enableProxy` option (`true` by default). It wires the locale detection / redirect / rewrite middleware in development, preview, and production SSR. You no longer need to register `intlayerProxy()` separately.
|
|
222
223
|
|
|
223
224
|
### `routing.enableProxy` option
|
|
@@ -240,6 +241,27 @@ The standalone `intlayerCompiler()` and `intlayerProxy()` plugins remain exporte
|
|
|
240
241
|
|
|
241
242
|
---
|
|
242
243
|
|
|
244
|
+
## Compiler disabled by default
|
|
245
|
+
|
|
246
|
+
Starting with Intlayer v9, the **compiler is disabled by default** (`compiler.enabled` now defaults to `false`). To opt in to build-time extraction of your `.content.ts` files, set `compiler.enabled: true` in your configuration:
|
|
247
|
+
|
|
248
|
+
```ts fileName="intlayer.config.ts"
|
|
249
|
+
import type { IntlayerConfig } from "intlayer";
|
|
250
|
+
|
|
251
|
+
const config: IntlayerConfig = {
|
|
252
|
+
compiler: {
|
|
253
|
+
enabled: true, // Default: false — required to enable the compiler since v9
|
|
254
|
+
output: ({ fileName }) => `./${fileName}.content.ts`,
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
export default config;
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
When the compiler is disabled, Intlayer skips the build-time extraction step and relies on your already-declared dictionaries. Enable it only when you want the bundler plugin (`@intlayer/swc`, `@intlayer/babel`, or the `intlayer()` Vite plugin) to extract content automatically.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
243
265
|
## React Native: single-package imports
|
|
244
266
|
|
|
245
267
|
In a React Native or Expo app, you no longer need to juggle both `react-intlayer` and `react-native-intlayer`. The `react-native-intlayer` package now **re-exports the full `react-intlayer` API** (hooks, utilities, and the `/format`, `/html`, and `/markdown` subpaths), and its `IntlayerProvider` automatically applies the React Native polyfills.
|
|
@@ -266,9 +288,10 @@ npm install intlayer react-native-intlayer
|
|
|
266
288
|
|
|
267
289
|
If you are upgrading from v8, note that the v9 does not include breaking changes. But here are the key changes:
|
|
268
290
|
|
|
291
|
+
- **Compiler disabled by default**: `compiler.enabled` now defaults to `false`. If you rely on build-time extraction of your `.content.ts` files, set `compiler.enabled: true` in your `intlayer.config.ts`.
|
|
269
292
|
- **Vite plugin**: The compiler and locale-routing proxy are now bundled into `intlayer()`. If you previously registered `intlayerCompiler()` or `intlayerProxy()` manually, you can remove them — they are deduplicated automatically, so keeping them is harmless. Use `routing.enableProxy: false` to opt out of the proxy.
|
|
270
293
|
- **Locales & Dialects**: If using external i18n dependencies, add their respective compat adapter plugins in your configuration or bundler setup to automatically rewrite imports.
|
|
271
|
-
- **Custom Selectors**: When calling `useIntlayer`, the second parameter is now reserved for an option object containing `{ locale, item, variant
|
|
294
|
+
- **Custom Selectors**: When calling `useIntlayer`, the second parameter is now reserved for an option object containing `{ locale, item, variant }`. If you previously passed a locale string directly, you can still do so, but using the options object is recommended for advanced selections.
|
|
272
295
|
- **React Native**: `react-native-intlayer` now re-exports the entire `react-intlayer` API. In a React Native app, import everything from `react-native-intlayer` and drop the direct `react-intlayer` dependency. Existing `react-intlayer` imports continue to work.
|
|
273
296
|
|
|
274
297
|
---
|
|
@@ -276,6 +299,6 @@ If you are upgrading from v8, note that the v9 does not include breaking changes
|
|
|
276
299
|
## Useful links
|
|
277
300
|
|
|
278
301
|
- [Compat Adapter Packages Guide](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/compat/index.md)
|
|
279
|
-
- [Dynamic Dictionaries - Collections
|
|
302
|
+
- [Dynamic Dictionaries - Collections & Variants](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dynamic_dictionaries/index.md)
|
|
280
303
|
- [Configuration Reference](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/configuration.md)
|
|
281
304
|
- [React Native & Expo Guide](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/intlayer_with_react_native+expo.md)
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
createdAt: 2026-06-12
|
|
3
|
-
updatedAt: 2026-06-
|
|
3
|
+
updatedAt: 2026-06-26
|
|
4
4
|
title: Dynamic Dictionaries
|
|
5
|
-
description: Overview of Intlayer's
|
|
5
|
+
description: Overview of Intlayer's dynamic dictionary features — collections and variants — for building flexible, runtime-driven i18n content.
|
|
6
6
|
keywords:
|
|
7
7
|
- Dynamic Dictionaries
|
|
8
8
|
- Collections
|
|
9
9
|
- Variants
|
|
10
|
-
- Dynamic Records
|
|
11
10
|
- Intlayer
|
|
12
|
-
-
|
|
11
|
+
- Internationalization
|
|
13
12
|
slugs:
|
|
14
13
|
- doc
|
|
15
14
|
- concept
|
|
@@ -18,31 +17,38 @@ history:
|
|
|
18
17
|
- version: 9.0.0
|
|
19
18
|
date: 2026-06-12
|
|
20
19
|
changes: "Release of the dynamic dictionaries feature"
|
|
20
|
+
- version: 9.1.0
|
|
21
|
+
date: 2026-06-26
|
|
22
|
+
changes: "Merged dynamic records into variants — `variant` now accepts a string or an object"
|
|
21
23
|
author: aymericzip
|
|
22
24
|
---
|
|
23
25
|
|
|
24
26
|
# Dynamic Dictionaries
|
|
25
27
|
|
|
26
|
-
Intlayer supports
|
|
28
|
+
Intlayer supports two mechanisms for expressing content that goes beyond a single static dictionary per key. Each is declared through a **top-level metadata field** in the content file; no wrapper function is needed.
|
|
27
29
|
|
|
28
|
-
| Feature
|
|
29
|
-
|
|
|
30
|
-
| [Collections](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en
|
|
31
|
-
| [Variants](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en
|
|
32
|
-
| [Dynamic Records](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en-GB/dynamic_dictionaries/dynamic_content.md) | `meta: { id, … }` | `{ id, … }` |
|
|
30
|
+
| Feature | Metadata field | Selector in `useIntlayer` |
|
|
31
|
+
| ---------------------------------------------------------------------------------------------------------------- | --------------------------------------- | ----------------------------------------------- |
|
|
32
|
+
| [Collections](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dynamic_dictionaries/collections.md) | `item: N` | `{ item: N }` |
|
|
33
|
+
| [Variants](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dynamic_dictionaries/variants.md) | `variant: "name"` _or_ `variant: { … }` | `{ variant: "name" }` _or_ `{ variant: { … } }` |
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
Both compose with the locale argument and support selective / lazy loading via `importMode`.
|
|
35
36
|
|
|
36
37
|
## When to use which
|
|
37
38
|
|
|
38
39
|
- **Collections** — ordered list of items managed in separate files (FAQ entries, blog posts, products).
|
|
39
|
-
- **Variants** — named content alternatives
|
|
40
|
-
-
|
|
40
|
+
- **Variants** — named or structured content alternatives:
|
|
41
|
+
- a **string** variant for A/B tests, seasonal banners, or feature flags;
|
|
42
|
+
- an **object** variant for CMS records, user-specific copy, or any content addressed by a set of fields (the former "dynamic records").
|
|
43
|
+
|
|
44
|
+
> Earlier versions exposed a separate `meta` field for record-keyed content. It has been merged into `variant`: pass an **object** to `variant` instead of using `meta`.
|
|
41
45
|
|
|
42
46
|
## Selector disambiguation
|
|
43
47
|
|
|
44
|
-
|
|
48
|
+
A key may declare both dimensions at once (e.g. a collection whose items each have a variant). They are resolved in the order:
|
|
45
49
|
|
|
46
50
|
```
|
|
47
|
-
variant →
|
|
51
|
+
variant → item
|
|
48
52
|
```
|
|
53
|
+
|
|
54
|
+
So `{ variant: "promo" }` on a variant × item key returns every promo item as an array, and adding `{ item: 2 }` narrows it to a single entry.
|