@intlayer/docs 9.1.0 → 9.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  createdAt: 2026-06-12
3
- updatedAt: 2026-06-26
3
+ updatedAt: 2026-08-04
4
4
  title: Variants
5
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:
@@ -23,6 +23,12 @@ history:
23
23
  - version: 9.1.0
24
24
  date: 2026-06-26
25
25
  changes: "`variant` now accepts a string or an object — the former `meta` / dynamic records are declared as object variants"
26
+ - version: 9.1.1
27
+ date: 2026-07-31
28
+ changes: "A variant declares only the keys it overrides; undeclared variants fall back to the default entry"
29
+ - version: 9.1.2
30
+ date: 2026-08-04
31
+ changes: "Providers accept an ambient `variant` prop; selectors accept an ordered preference chain"
26
32
  author: aymericzip
27
33
  ---
28
34
 
@@ -77,6 +83,37 @@ const dictionary = {
77
83
  export default dictionary;
78
84
  ```
79
85
 
86
+ ### Partial variants
87
+
88
+ A variant declares **only the keys it overrides**; the rest are inherited from the default entry.
89
+
90
+ ```ts fileName="hero-banner.summer.content.ts" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
91
+ import { t, type Dictionary } from "intlayer";
92
+
93
+ const dictionary = {
94
+ key: "hero-banner",
95
+ variant: "summer",
96
+ content: {
97
+ headline: t({
98
+ en: "Build faster all summer",
99
+ fr: "Développez plus vite tout l'été",
100
+ }),
101
+ },
102
+ } satisfies Dictionary;
103
+
104
+ export default dictionary;
105
+ ```
106
+
107
+ ```tsx
108
+ useIntlayer("hero-banner", { variant: "summer" });
109
+ // → { headline: "Build faster all summer", cta: "Get started" } — `cta` inherited
110
+
111
+ useIntlayer("hero-banner", { variant: "never-declared" });
112
+ // → the default entry
113
+ ```
114
+
115
+ So you only add a variant file where the wording actually differs. A key resolves to `null` only when it declares variants but no default entry.
116
+
80
117
  ### Consuming named variants
81
118
 
82
119
  #### Default variant
@@ -464,6 +501,176 @@ const content = useIntlayer("product", {
464
501
  const content = useIntlayer("product", { variant: { id: "prod_abc" } });
465
502
  ```
466
503
 
504
+ ## Ambient variant
505
+
506
+ Some variant dimensions are fixed for a whole session — the tenant, the school type, the plan tier. They are resolved once, and no component should have to pass them by hand.
507
+
508
+ > Do not wrap `useIntlayer` in your own hook to inject them. The build-time optimization only rewrites a literal `useIntlayer("key")` imported from the framework package, so nothing behind a wrapper gets bundled.
509
+
510
+ Declare the variant once on the provider instead, exactly like `locale`:
511
+
512
+ <Tabs group="framework">
513
+ <Tab label="React" value="react">
514
+ ```tsx fileName="App.tsx" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
515
+ import { IntlayerProvider } from "react-intlayer";
516
+
517
+ export const App = ({ locale, schoolType }) => (
518
+ <IntlayerProvider locale={locale} variant={schoolType}>
519
+ <Hero />
520
+ </IntlayerProvider>
521
+ );
522
+ ```
523
+
524
+ </Tab>
525
+ <Tab label="Next.js" value="nextjs">
526
+ ```tsx fileName="layout.tsx" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
527
+ import { IntlayerServerProvider } from "next-intlayer/server";
528
+ import { IntlayerClientProvider } from "next-intlayer";
529
+
530
+ export default async function Layout({ children, params }) {
531
+ const { locale } = await params;
532
+ const schoolType = await getSchoolType();
533
+
534
+ return (
535
+ <IntlayerServerProvider locale={locale} variant={schoolType}>
536
+ <IntlayerClientProvider locale={locale} variant={schoolType}>
537
+ {children}
538
+ </IntlayerClientProvider>
539
+ </IntlayerServerProvider>
540
+ );
541
+ }
542
+ ```
543
+
544
+ </Tab>
545
+ <Tab label="Vue" value="vue">
546
+ ```ts fileName="main.ts" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
547
+ import { createApp } from "vue";
548
+ import { installIntlayer } from "vue-intlayer";
549
+ import App from "./App.vue";
550
+
551
+ const app = createApp(App);
552
+
553
+ installIntlayer(app, { locale: "en", variant: schoolType });
554
+
555
+ app.mount("#app");
556
+ ```
557
+
558
+ </Tab>
559
+ <Tab label="Svelte" value="svelte">
560
+ ```svelte fileName="+layout.svelte" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
561
+ <script lang="ts">
562
+ import { setupIntlayer } from "svelte-intlayer";
563
+
564
+ export let schoolType: string;
565
+
566
+ setupIntlayer("en", schoolType);
567
+ </script>
568
+
569
+ <slot />
570
+ ```
571
+
572
+ </Tab>
573
+ <Tab label="Preact" value="preact">
574
+ ```tsx fileName="App.tsx" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
575
+ import { IntlayerProvider } from "preact-intlayer";
576
+
577
+ export const App = ({ locale, schoolType }) => (
578
+ <IntlayerProvider locale={locale} variant={schoolType}>
579
+ <Hero />
580
+ </IntlayerProvider>
581
+ );
582
+ ```
583
+
584
+ </Tab>
585
+ <Tab label="Solid" value="solid">
586
+ ```tsx fileName="App.tsx" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
587
+ import { IntlayerProvider } from "solid-intlayer";
588
+
589
+ export const App = (props) => (
590
+ <IntlayerProvider locale={props.locale} variant={props.schoolType}>
591
+ <Hero />
592
+ </IntlayerProvider>
593
+ );
594
+ ```
595
+
596
+ </Tab>
597
+ <Tab label="Angular" value="angular">
598
+ ```typescript fileName="app.config.ts" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
599
+ import { ApplicationConfig } from "@angular/core";
600
+ import { provideIntlayer } from "angular-intlayer";
601
+
602
+ export const appConfig: ApplicationConfig = {
603
+ providers: [provideIntlayer("en", true, schoolType)],
604
+ };
605
+ ```
606
+
607
+ </Tab>
608
+ <Tab label="Vanilla JS" value="vanilla">
609
+ ```javascript fileName="main.js"
610
+ import { installIntlayer } from "vanilla-intlayer";
611
+
612
+ installIntlayer({ locale: "en", variant: schoolType });
613
+ ```
614
+
615
+ </Tab>
616
+ </Tabs>
617
+
618
+ Every dictionary read below the provider now resolves against that variant, and a call-site selector always wins:
619
+
620
+ ```tsx
621
+ useIntlayer("hero-banner");
622
+ // → the provider variant
623
+
624
+ useIntlayer("hero-banner", { variant: "summer" });
625
+ // → "summer" — replaces the provider variant, it is not extended
626
+ ```
627
+
628
+ ### Forms
629
+
630
+ The `variant` prop accepts three forms:
631
+
632
+ | Form | Meaning |
633
+ | --------------------------------------------------------- | ------------------------------- |
634
+ | `variant="school1"` | one named variant for every key |
635
+ | `variant={["school1", "default"]}` | an ordered preference chain |
636
+ | `variant={{ "hero-banner": "school1", default: "base" }}` | one variant per dictionary key |
637
+
638
+ #### Preference chain
639
+
640
+ A chain is tried left to right against the entries each key declares, and the first declared one wins. When none is declared, the implicit default entry is used — exactly as for a single value.
641
+
642
+ ```tsx
643
+ <IntlayerProvider variant={["school1", "school2"]} />
644
+ // `hero-banner` declares no `school1` entry but declares `school2` → "school2"
645
+ // a key declaring neither → the default entry
646
+ ```
647
+
648
+ So `["black_friday", "summer"]` reads as "black friday if this key has one, else summer, else default". Chains are also accepted at the call site:
649
+
650
+ ```tsx
651
+ useIntlayer("hero-banner", { variant: ["black_friday", "summer"] });
652
+ ```
653
+
654
+ > Note this is the mirror image of the array accepted by the `variant` **field** of a content file: there an array _declares_ one entry per element, here it _consumes_ them in priority order.
655
+
656
+ #### Per-key map
657
+
658
+ Address each dictionary key separately. The reserved `default` entry covers every key not listed:
659
+
660
+ ```tsx
661
+ <IntlayerProvider
662
+ variant={{
663
+ "hero-banner": "school1",
664
+ product: ["school1", "default"],
665
+ default: "base",
666
+ }}
667
+ />
668
+ ```
669
+
670
+ > On a provider a plain object is **always** read as the per-key map, never as an object variant — the two are structurally identical. To pin an object variant globally, nest it under an entry: `variant={{ default: { id: "prod_abc" } }}`.
671
+
672
+ Because the map's keys are checked against your declared dictionary keys, a typo — or an object variant written directly, such as `variant={{ id: "prod_abc" }}` — is a compile-time error.
673
+
467
674
  ## Loading mode
468
675
 
469
676
  Object variants are often loaded lazily. Set `importMode` on the dictionary to control this:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  createdAt: 2026-06-12
3
- updatedAt: 2026-06-26
3
+ updatedAt: 2026-08-04
4
4
  title: Variants
5
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:
@@ -23,6 +23,12 @@ history:
23
23
  - version: 9.1.0
24
24
  date: 2026-06-26
25
25
  changes: "`variant` now accepts a string or an object — the former `meta` / dynamic records are declared as object variants"
26
+ - version: 9.1.1
27
+ date: 2026-07-31
28
+ changes: "A variant declares only the keys it overrides; undeclared variants fall back to the default entry"
29
+ - version: 9.1.2
30
+ date: 2026-08-04
31
+ changes: "Providers accept an ambient `variant` prop; selectors accept an ordered preference chain"
26
32
  author: aymericzip
27
33
  ---
28
34
 
@@ -77,6 +83,37 @@ const dictionary = {
77
83
  export default dictionary;
78
84
  ```
79
85
 
86
+ ### Partial variants
87
+
88
+ A variant declares **only the keys it overrides**; the rest are inherited from the default entry.
89
+
90
+ ```ts fileName="hero-banner.summer.content.ts" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
91
+ import { t, type Dictionary } from "intlayer";
92
+
93
+ const dictionary = {
94
+ key: "hero-banner",
95
+ variant: "summer",
96
+ content: {
97
+ headline: t({
98
+ en: "Build faster all summer",
99
+ fr: "Développez plus vite tout l'été",
100
+ }),
101
+ },
102
+ } satisfies Dictionary;
103
+
104
+ export default dictionary;
105
+ ```
106
+
107
+ ```tsx
108
+ useIntlayer("hero-banner", { variant: "summer" });
109
+ // → { headline: "Build faster all summer", cta: "Get started" } — `cta` inherited
110
+
111
+ useIntlayer("hero-banner", { variant: "never-declared" });
112
+ // → the default entry
113
+ ```
114
+
115
+ So you only add a variant file where the wording actually differs. A key resolves to `null` only when it declares variants but no default entry.
116
+
80
117
  ### Consuming named variants
81
118
 
82
119
  #### Default variant
@@ -464,6 +501,176 @@ const content = useIntlayer("product", {
464
501
  const content = useIntlayer("product", { variant: { id: "prod_abc" } });
465
502
  ```
466
503
 
504
+ ## Ambient variant
505
+
506
+ Some variant dimensions are fixed for a whole session — the tenant, the school type, the plan tier. They are resolved once, and no component should have to pass them by hand.
507
+
508
+ > Do not wrap `useIntlayer` in your own hook to inject them. The build-time optimisation only rewrites a literal `useIntlayer("key")` imported from the framework package, so nothing behind a wrapper gets bundled.
509
+
510
+ Declare the variant once on the provider instead, exactly like `locale`:
511
+
512
+ <Tabs group="framework">
513
+ <Tab label="React" value="react">
514
+ ```tsx fileName="App.tsx" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
515
+ import { IntlayerProvider } from "react-intlayer";
516
+
517
+ export const App = ({ locale, schoolType }) => (
518
+ <IntlayerProvider locale={locale} variant={schoolType}>
519
+ <Hero />
520
+ </IntlayerProvider>
521
+ );
522
+ ```
523
+
524
+ </Tab>
525
+ <Tab label="Next.js" value="nextjs">
526
+ ```tsx fileName="layout.tsx" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
527
+ import { IntlayerServerProvider } from "next-intlayer/server";
528
+ import { IntlayerClientProvider } from "next-intlayer";
529
+
530
+ export default async function Layout({ children, params }) {
531
+ const { locale } = await params;
532
+ const schoolType = await getSchoolType();
533
+
534
+ return (
535
+ <IntlayerServerProvider locale={locale} variant={schoolType}>
536
+ <IntlayerClientProvider locale={locale} variant={schoolType}>
537
+ {children}
538
+ </IntlayerClientProvider>
539
+ </IntlayerServerProvider>
540
+ );
541
+ }
542
+ ```
543
+
544
+ </Tab>
545
+ <Tab label="Vue" value="vue">
546
+ ```ts fileName="main.ts" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
547
+ import { createApp } from "vue";
548
+ import { installIntlayer } from "vue-intlayer";
549
+ import App from "./App.vue";
550
+
551
+ const app = createApp(App);
552
+
553
+ installIntlayer(app, { locale: "en", variant: schoolType });
554
+
555
+ app.mount("#app");
556
+ ```
557
+
558
+ </Tab>
559
+ <Tab label="Svelte" value="svelte">
560
+ ```svelte fileName="+layout.svelte" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
561
+ <script lang="ts">
562
+ import { setupIntlayer } from "svelte-intlayer";
563
+
564
+ export let schoolType: string;
565
+
566
+ setupIntlayer("en", schoolType);
567
+ </script>
568
+
569
+ <slot />
570
+ ```
571
+
572
+ </Tab>
573
+ <Tab label="Preact" value="preact">
574
+ ```tsx fileName="App.tsx" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
575
+ import { IntlayerProvider } from "preact-intlayer";
576
+
577
+ export const App = ({ locale, schoolType }) => (
578
+ <IntlayerProvider locale={locale} variant={schoolType}>
579
+ <Hero />
580
+ </IntlayerProvider>
581
+ );
582
+ ```
583
+
584
+ </Tab>
585
+ <Tab label="Solid" value="solid">
586
+ ```tsx fileName="App.tsx" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
587
+ import { IntlayerProvider } from "solid-intlayer";
588
+
589
+ export const App = (props) => (
590
+ <IntlayerProvider locale={props.locale} variant={props.schoolType}>
591
+ <Hero />
592
+ </IntlayerProvider>
593
+ );
594
+ ```
595
+
596
+ </Tab>
597
+ <Tab label="Angular" value="angular">
598
+ ```typescript fileName="app.config.ts" contentDeclarationFormat={["typescript", "esm", "commonjs"]}
599
+ import { ApplicationConfig } from "@angular/core";
600
+ import { provideIntlayer } from "angular-intlayer";
601
+
602
+ export const appConfig: ApplicationConfig = {
603
+ providers: [provideIntlayer("en", true, schoolType)],
604
+ };
605
+ ```
606
+
607
+ </Tab>
608
+ <Tab label="Vanilla JS" value="vanilla">
609
+ ```javascript fileName="main.js"
610
+ import { installIntlayer } from "vanilla-intlayer";
611
+
612
+ installIntlayer({ locale: "en", variant: schoolType });
613
+ ```
614
+
615
+ </Tab>
616
+ </Tabs>
617
+
618
+ Every dictionary read below the provider now resolves against that variant, and a call-site selector always wins:
619
+
620
+ ```tsx
621
+ useIntlayer("hero-banner");
622
+ // → the provider variant
623
+
624
+ useIntlayer("hero-banner", { variant: "summer" });
625
+ // → "summer" — replaces the provider variant, it is not extended
626
+ ```
627
+
628
+ ### Forms
629
+
630
+ The `variant` prop accepts three forms:
631
+
632
+ | Form | Meaning |
633
+ | --------------------------------------------------------- | ------------------------------- |
634
+ | `variant="school1"` | one named variant for every key |
635
+ | `variant={["school1", "default"]}` | an ordered preference chain |
636
+ | `variant={{ "hero-banner": "school1", default: "base" }}` | one variant per dictionary key |
637
+
638
+ #### Preference chain
639
+
640
+ A chain is tried left to right against the entries each key declares, and the first declared one wins. When none is declared, the implicit default entry is used — exactly as for a single value.
641
+
642
+ ```tsx
643
+ <IntlayerProvider variant={["school1", "school2"]} />
644
+ // `hero-banner` declares no `school1` entry but declares `school2` → "school2"
645
+ // a key declaring neither → the default entry
646
+ ```
647
+
648
+ So `["black_friday", "summer"]` reads as "black friday if this key has one, else summer, else default". Chains are also accepted at the call site:
649
+
650
+ ```tsx
651
+ useIntlayer("hero-banner", { variant: ["black_friday", "summer"] });
652
+ ```
653
+
654
+ > Note this is the mirror image of the array accepted by the `variant` **field** of a content file: there an array _declares_ one entry per element, here it _consumes_ them in priority order.
655
+
656
+ #### Per-key map
657
+
658
+ Address each dictionary key separately. The reserved `default` entry covers every key not listed:
659
+
660
+ ```tsx
661
+ <IntlayerProvider
662
+ variant={{
663
+ "hero-banner": "school1",
664
+ product: ["school1", "default"],
665
+ default: "base",
666
+ }}
667
+ />
668
+ ```
669
+
670
+ > On a provider a plain object is **always** read as the per-key map, never as an object variant — the two are structurally identical. To pin an object variant globally, nest it under an entry: `variant={{ default: { id: "prod_abc" } }}`.
671
+
672
+ Because the map's keys are checked against your declared dictionary keys, a typo — or an object variant written directly, such as `variant={{ id: "prod_abc" }}` — is a compile-time error.
673
+
467
674
  ## Loading mode
468
675
 
469
676
  Object variants are often loaded lazily. Set `importMode` on the dictionary to control this: