@intlayer/docs 9.1.1 → 9.1.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.
- package/docs/ar/dynamic_dictionaries/variants.md +214 -1
- package/docs/de/dynamic_dictionaries/variants.md +174 -1
- package/docs/en/dynamic_dictionaries/variants.md +174 -1
- package/docs/en-GB/dynamic_dictionaries/variants.md +174 -1
- package/docs/es/dynamic_dictionaries/variants.md +174 -1
- package/docs/fr/dynamic_dictionaries/variants.md +174 -1
- package/docs/hi/dynamic_dictionaries/variants.md +174 -1
- package/docs/id/dynamic_dictionaries/variants.md +174 -1
- package/docs/it/dynamic_dictionaries/variants.md +174 -1
- package/docs/ja/dynamic_dictionaries/variants.md +174 -1
- package/docs/ko/dynamic_dictionaries/variants.md +174 -1
- package/docs/pl/dynamic_dictionaries/variants.md +174 -1
- package/docs/pt/dynamic_dictionaries/variants.md +174 -1
- package/docs/ru/dynamic_dictionaries/variants.md +174 -1
- package/docs/tr/dynamic_dictionaries/variants.md +174 -1
- package/docs/uk/dynamic_dictionaries/variants.md +174 -1
- package/docs/vi/dynamic_dictionaries/variants.md +174 -1
- package/docs/zh/dynamic_dictionaries/variants.md +174 -1
- package/package.json +6 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
createdAt: 2026-06-12
|
|
3
|
-
updatedAt: 2026-
|
|
3
|
+
updatedAt: 2026-08-04
|
|
4
4
|
title: Варіанти
|
|
5
5
|
description: Використовуйте поле метаданих variant у файлах контенту Intlayer, щоб оголошувати іменовані або структуровані альтернативи контенту — A/B-тести, сезонні банери, тексти під feature-прапорцями, записи CMS, контент конкретного користувача — і перемикатися між ними під час виконання без змін коду.
|
|
6
6
|
keywords:
|
|
@@ -26,6 +26,9 @@ history:
|
|
|
26
26
|
- version: 9.1.1
|
|
27
27
|
date: 2026-07-31
|
|
28
28
|
changes: "Варіант оголошує лише ключі, які він перевизначає; неоголошені варіанти повертаються до запису за замовчуванням"
|
|
29
|
+
- version: 9.1.2
|
|
30
|
+
date: 2026-08-04
|
|
31
|
+
changes: "Провайдери приймають зовнішній проп `variant`; селектори приймають упорядкований ланцюжок переваг"
|
|
29
32
|
author: aymericzip
|
|
30
33
|
---
|
|
31
34
|
|
|
@@ -498,6 +501,176 @@ const content = useIntlayer("product", {
|
|
|
498
501
|
const content = useIntlayer("product", { variant: { id: "prod_abc" } });
|
|
499
502
|
```
|
|
500
503
|
|
|
504
|
+
## Зовнішній варіант
|
|
505
|
+
|
|
506
|
+
Деякі виміри варіанта незмінні протягом усієї сесії — орендар, тип навчального закладу, тарифний рівень. Вони визначаються один раз, і жоден компонент не має передавати їх вручну.
|
|
507
|
+
|
|
508
|
+
> Не загортайте `useIntlayer` у власний хук, щоб їх підставити. Оптимізація під час збірки переписує лише літеральний виклик `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
|
+
|
|
501
674
|
## Режим завантаження
|
|
502
675
|
|
|
503
676
|
Об'єктні варіанти часто завантажуються ліниво. Задайте `importMode` у словнику, щоб керувати цим:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
createdAt: 2026-06-12
|
|
3
|
-
updatedAt: 2026-
|
|
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:
|
|
@@ -26,6 +26,9 @@ history:
|
|
|
26
26
|
- version: 9.1.1
|
|
27
27
|
date: 2026-07-31
|
|
28
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ự"
|
|
29
32
|
author: aymericzip
|
|
30
33
|
---
|
|
31
34
|
|
|
@@ -498,6 +501,176 @@ const content = useIntlayer("product", {
|
|
|
498
501
|
const content = useIntlayer("product", { variant: { id: "prod_abc" } });
|
|
499
502
|
```
|
|
500
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
|
+
|
|
501
674
|
## Chế độ tải
|
|
502
675
|
|
|
503
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-
|
|
3
|
+
updatedAt: 2026-08-04
|
|
4
4
|
title: 变体
|
|
5
5
|
description: 在 Intlayer 内容文件中使用 variant 元数据字段来声明具名或结构化的内容替代项——A/B 测试、季节性横幅、功能开关文案、CMS 记录、用户特定内容——并在运行时无需更改代码即可在它们之间切换。
|
|
6
6
|
keywords:
|
|
@@ -26,6 +26,9 @@ history:
|
|
|
26
26
|
- version: 9.1.1
|
|
27
27
|
date: 2026-07-31
|
|
28
28
|
changes: "变体仅声明它覆盖的键;未声明的变体将回退到默认条目"
|
|
29
|
+
- version: 9.1.2
|
|
30
|
+
date: 2026-08-04
|
|
31
|
+
changes: "提供者接受环境级 `variant` 属性;选择器接受有序的优先级链"
|
|
29
32
|
author: aymericzip
|
|
30
33
|
---
|
|
31
34
|
|
|
@@ -498,6 +501,176 @@ const content = useIntlayer("product", {
|
|
|
498
501
|
const content = useIntlayer("product", { variant: { id: "prod_abc" } });
|
|
499
502
|
```
|
|
500
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
|
+
|
|
501
674
|
## 加载模式
|
|
502
675
|
|
|
503
676
|
对象变体通常被惰性加载。在字典上设置 `importMode` 以控制此行为:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intlayer/docs",
|
|
3
|
-
"version": "9.1.
|
|
3
|
+
"version": "9.1.3",
|
|
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.
|
|
77
|
-
"@intlayer/core": "9.1.
|
|
78
|
-
"@intlayer/types": "9.1.
|
|
76
|
+
"@intlayer/config": "9.1.3",
|
|
77
|
+
"@intlayer/core": "9.1.3",
|
|
78
|
+
"@intlayer/types": "9.1.3"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
|
-
"@intlayer/api": "9.1.
|
|
82
|
-
"@intlayer/cli": "9.1.
|
|
81
|
+
"@intlayer/api": "9.1.3",
|
|
82
|
+
"@intlayer/cli": "9.1.3",
|
|
83
83
|
"@types/node": "26.1.2",
|
|
84
84
|
"@utils/ts-config": "1.0.4",
|
|
85
85
|
"@utils/ts-config-types": "1.0.4",
|