@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: Biến thể
5
5
  description: Dùng trường metadata variant trong các tệp nội dung Intlayer để khai báo các lựa chọn nội dung được đặt tên hoặc có cấu trúc — thử nghiệm A/B, banner theo mùa, nội dung gắn feature flag, bản ghi CMS, nội dung riêng theo người dùng — và chuyển đổi giữa chúng trong thời gian chạy mà không cần đổi mã.
6
6
  keywords:
@@ -22,7 +22,13 @@ history:
22
22
  changes: "Phát hành tính năng biến thể"
23
23
  - version: 9.1.0
24
24
  date: 2026-06-26
25
- changes: "`variant` giờ chấp nhận một chuỗi hoặc một đối tượng — `meta` / bản ghi động trước đây được khai báo dưới dạng biến thể đối tượng"
25
+ changes: "`variant` hiện chấp nhận một chuỗi hoặc một đối tượng — trước đây `meta` / bản ghi động được khai báo biến thể đối tượng"
26
+ - version: 9.1.1
27
+ date: 2026-07-31
28
+ changes: "Biến thể chỉ khai báo các khóa mà nó ghi đè; các biến thể không được khai báo sẽ quay lại mục mặc định"
29
+ - version: 9.1.2
30
+ date: 2026-08-04
31
+ changes: "Provider chấp nhận prop `variant` bao trùm; bộ chọn chấp nhận chuỗi ưu tiên có thứ tự"
26
32
  author: aymericzip
27
33
  ---
28
34
 
@@ -77,7 +83,38 @@ const dictionary = {
77
83
  export default dictionary;
78
84
  ```
79
85
 
80
- ### Sử dụng biến thể được đặt tên
86
+ ### Biến thể một phần
87
+
88
+ Một biến thể **chỉ khai báo các khóa mà nó ghi đè**; phần còn lại được kế thừa từ mục mặc định.
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: "Développez plus vite tout l'été", cta: "Commencer" } — `cta` được kế thừa
110
+
111
+ useIntlayer("hero-banner", { variant: "never-declared" });
112
+ // → mục mặc định
113
+ ```
114
+
115
+ Vì vậy, bạn chỉ thêm một tệp biến thể ở nơi văn bản thực sự khác biệt. Một khóa chỉ giải quyết thành `null` khi nó khai báo các biến thể nhưng không có mục mặc định.
116
+
117
+ ### Tiêu thụ các biến thể được đặt tên
81
118
 
82
119
  #### Biến thể mặc định
83
120
 
@@ -464,6 +501,176 @@ const content = useIntlayer("product", {
464
501
  const content = useIntlayer("product", { variant: { id: "prod_abc" } });
465
502
  ```
466
503
 
504
+ ## Biến thể bao trùm
505
+
506
+ Một số chiều biến thể cố định trong suốt phiên làm việc — tenant, loại trường học, hạng gói. Chúng được xác định một lần, và không component nào phải truyền chúng thủ công.
507
+
508
+ > Đừng bọc `useIntlayer` trong hook riêng của bạn để chèn chúng. Tối ưu hóa lúc build chỉ viết lại lời gọi `useIntlayer("key")` dạng literal được import từ gói framework, nên mọi thứ nằm sau một lớp bọc sẽ không được đóng gói.
509
+
510
+ Thay vào đó, hãy khai báo biến thể một lần trên provider, y như `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
+ Mọi lần đọc từ điển bên dưới provider giờ đây được phân giải theo biến thể đó, và bộ chọn tại nơi gọi luôn thắng:
619
+
620
+ ```tsx
621
+ useIntlayer("hero-banner");
622
+ // → biến thể của provider
623
+
624
+ useIntlayer("hero-banner", { variant: "summer" });
625
+ // → "summer" — thay thế biến thể của provider, không mở rộng nó
626
+ ```
627
+
628
+ ### Các dạng
629
+
630
+ Prop `variant` chấp nhận ba dạng:
631
+
632
+ | Dạng | Ý nghĩa |
633
+ | --------------------------------------------------------- | --------------------------------- |
634
+ | `variant="school1"` | một biến thể có tên cho mọi khóa |
635
+ | `variant={["school1", "default"]}` | một chuỗi ưu tiên có thứ tự |
636
+ | `variant={{ "hero-banner": "school1", default: "base" }}` | một biến thể cho mỗi khóa từ điển |
637
+
638
+ #### Chuỗi ưu tiên
639
+
640
+ Chuỗi được thử từ trái sang phải theo các mục mà mỗi khóa khai báo, và mục được khai báo đầu tiên sẽ thắng. Khi không có mục nào được khai báo, mục mặc định ngầm định sẽ được dùng — hệt như với một giá trị đơn.
641
+
642
+ ```tsx
643
+ <IntlayerProvider variant={["school1", "school2"]} />
644
+ // `hero-banner` không khai báo mục `school1` nhưng có khai báo `school2` → "school2"
645
+ // một khóa không khai báo mục nào trong hai → mục mặc định
646
+ ```
647
+
648
+ Vậy `["black_friday", "summer"]` đọc là «black friday nếu khóa này có, nếu không thì summer, nếu không nữa thì mặc định». Chuỗi cũng được chấp nhận tại nơi gọi:
649
+
650
+ ```tsx
651
+ useIntlayer("hero-banner", { variant: ["black_friday", "summer"] });
652
+ ```
653
+
654
+ > Lưu ý đây là hình ảnh phản chiếu của mảng được **trường** `variant` trong tệp nội dung chấp nhận: ở đó một mảng _khai báo_ mỗi phần tử một mục, còn ở đây nó _tiêu thụ_ chúng theo thứ tự ưu tiên.
655
+
656
+ #### Ánh xạ theo khóa
657
+
658
+ Chỉ định riêng từng khóa từ điển. Mục `default` được dành riêng sẽ bao phủ mọi khóa không được liệt kê:
659
+
660
+ ```tsx
661
+ <IntlayerProvider
662
+ variant={{
663
+ "hero-banner": "school1",
664
+ product: ["school1", "default"],
665
+ default: "base",
666
+ }}
667
+ />
668
+ ```
669
+
670
+ > Trên provider, một object thuần **luôn** được đọc là ánh xạ theo khóa, không bao giờ là biến thể object — hai thứ này giống hệt nhau về cấu trúc. Để cố định một biến thể object trên toàn cục, hãy lồng nó dưới một mục: `variant={{ default: { id: "prod_abc" } }}`.
671
+
672
+ Vì các khóa của ánh xạ được đối chiếu với các khóa từ điển bạn đã khai báo, một lỗi gõ nhầm — hoặc một biến thể object viết trực tiếp, chẳng hạn `variant={{ id: "prod_abc" }}` — sẽ là lỗi biên dịch.
673
+
467
674
  ## Chế độ tải
468
675
 
469
676
  Biến thể đối tượng thường được tải lười. Đặt `importMode` trên từ điển để kiểm soát điều này:
@@ -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: 变体
5
5
  description: 在 Intlayer 内容文件中使用 variant 元数据字段来声明具名或结构化的内容替代项——A/B 测试、季节性横幅、功能开关文案、CMS 记录、用户特定内容——并在运行时无需更改代码即可在它们之间切换。
6
6
  keywords:
@@ -22,7 +22,13 @@ history:
22
22
  changes: "变体功能发布"
23
23
  - version: 9.1.0
24
24
  date: 2026-06-26
25
- changes: "`variant` 现在接受字符串或对象——以前的 `meta` / 动态记录现声明为对象变体"
25
+ changes: "`variant` 现在接受字符串或对象 — 以前的 `meta` / 动态记录现在声明为对象变体"
26
+ - version: 9.1.1
27
+ date: 2026-07-31
28
+ changes: "变体仅声明它覆盖的键;未声明的变体将回退到默认条目"
29
+ - version: 9.1.2
30
+ date: 2026-08-04
31
+ changes: "提供者接受环境级 `variant` 属性;选择器接受有序的优先级链"
26
32
  author: aymericzip
27
33
  ---
28
34
 
@@ -77,6 +83,37 @@ const dictionary = {
77
83
  export default dictionary;
78
84
  ```
79
85
 
86
+ ### 部分变体
87
+
88
+ 变体**仅声明它覆盖的键**;其余部分从默认条目继承。
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: "Développez plus vite tout l'été", cta: "Commencer" } — 继承了 `cta`
110
+
111
+ useIntlayer("hero-banner", { variant: "never-declared" });
112
+ // → 默认条目
113
+ ```
114
+
115
+ 因此,您只需在文本确实不同的地方添加变体文件。只有在声明了变体但没有默认条目的情况下,键才会解析为 `null`。
116
+
80
117
  ### 使用具名变体
81
118
 
82
119
  #### 默认变体
@@ -464,6 +501,176 @@ const content = useIntlayer("product", {
464
501
  const content = useIntlayer("product", { variant: { id: "prod_abc" } });
465
502
  ```
466
503
 
504
+ ## 环境变体
505
+
506
+ 有些变体维度在整个会话中都是固定的——租户、学校类型、套餐等级。它们只需解析一次,任何组件都不应手动传递它们。
507
+
508
+ > 不要为了注入它们而把 `useIntlayer` 包装进你自己的 Hook。构建期优化只会重写从框架包中导入的字面量 `useIntlayer("key")` 调用,因此包装器背后的内容不会被打包。
509
+
510
+ 请改为在提供者上声明一次变体,就像 `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
+ 现在提供者下的每次字典读取都会基于该变体解析,而调用处的选择器始终优先:
619
+
620
+ ```tsx
621
+ useIntlayer("hero-banner");
622
+ // → 提供者的变体
623
+
624
+ useIntlayer("hero-banner", { variant: "summer" });
625
+ // → "summer" —— 替换提供者的变体,而不是扩展它
626
+ ```
627
+
628
+ ### 形式
629
+
630
+ `variant` 属性接受三种形式:
631
+
632
+ | 形式 | 含义 |
633
+ | --------------------------------------------------------- | -------------------------- |
634
+ | `variant="school1"` | 对所有键使用同一个具名变体 |
635
+ | `variant={["school1", "default"]}` | 有序的优先级链 |
636
+ | `variant={{ "hero-banner": "school1", default: "base" }}` | 按字典键分别指定变体 |
637
+
638
+ #### 优先级链
639
+
640
+ 链会针对每个键所声明的条目从左到右依次尝试,第一个已声明的胜出。若都未声明,则使用隐式的默认条目——与单个值的行为完全一致。
641
+
642
+ ```tsx
643
+ <IntlayerProvider variant={["school1", "school2"]} />
644
+ // `hero-banner` 未声明 `school1` 条目,但声明了 `school2` → "school2"
645
+ // 两者都未声明的键 → 默认条目
646
+ ```
647
+
648
+ 因此 `["black_friday", "summer"]` 可读作「若该键有 black friday 则用它,否则用 summer,再否则用默认」。调用处同样接受链:
649
+
650
+ ```tsx
651
+ useIntlayer("hero-banner", { variant: ["black_friday", "summer"] });
652
+ ```
653
+
654
+ > 请注意,这与内容文件中 `variant` **字段**所接受的数组正好相反:在那里,数组为每个元素*声明*一个条目;而在这里,它按优先级顺序*消费*这些条目。
655
+
656
+ #### 按键映射
657
+
658
+ 分别指定每个字典键。保留的 `default` 条目覆盖所有未列出的键:
659
+
660
+ ```tsx
661
+ <IntlayerProvider
662
+ variant={{
663
+ "hero-banner": "school1",
664
+ product: ["school1", "default"],
665
+ default: "base",
666
+ }}
667
+ />
668
+ ```
669
+
670
+ > 在提供者上,普通对象**始终**被解读为按键映射,而绝不会被当作对象变体——两者在结构上完全相同。若要全局指定对象变体,请将其嵌套在某个条目下:`variant={{ default: { id: "prod_abc" } }}`。
671
+
672
+ 由于映射的键会与你声明的字典键进行校验,拼写错误——或直接写成对象变体,例如 `variant={{ id: "prod_abc" }}`——都会导致编译期错误。
673
+
467
674
  ## 加载模式
468
675
 
469
676
  对象变体通常被惰性加载。在字典上设置 `importMode` 以控制此行为:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intlayer/docs",
3
- "version": "9.1.0",
3
+ "version": "9.1.2",
4
4
  "private": false,
5
5
  "description": "Intlayer documentation",
6
6
  "keywords": [
@@ -73,13 +73,13 @@
73
73
  "watch": "webpack --config ./webpack.config.ts --watch"
74
74
  },
75
75
  "dependencies": {
76
- "@intlayer/config": "9.1.0",
77
- "@intlayer/core": "9.1.0",
78
- "@intlayer/types": "9.1.0"
76
+ "@intlayer/config": "9.1.2",
77
+ "@intlayer/core": "9.1.2",
78
+ "@intlayer/types": "9.1.2"
79
79
  },
80
80
  "devDependencies": {
81
- "@intlayer/api": "9.1.0",
82
- "@intlayer/cli": "9.1.0",
81
+ "@intlayer/api": "9.1.2",
82
+ "@intlayer/cli": "9.1.2",
83
83
  "@types/node": "26.1.2",
84
84
  "@utils/ts-config": "1.0.4",
85
85
  "@utils/ts-config-types": "1.0.4",