@xndrjs/i18n 0.7.0 → 0.8.0
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/README.md +67 -872
- package/dist/codegen/index.d.ts +117 -51
- package/dist/codegen/index.js +1585 -93
- package/dist/codegen/index.js.map +1 -1
- package/dist/index.d.ts +100 -234
- package/dist/index.js +74 -638
- package/dist/index.js.map +1 -1
- package/dist/validation/index.d.ts +5 -0
- package/dist/validation/index.js.map +1 -1
- package/package.json +2 -2
- package/src/IcuTranslationProviderMulti.test.ts +18 -40
- package/src/IcuTranslationProviderMulti.ts +27 -37
- package/src/audit/audit-dictionaries.ts +1 -1
- package/src/audit/run-audit.test.ts +6 -7
- package/src/builder-load-registry.ts +26 -5
- package/src/builder-loaders.ts +8 -11
- package/src/builder-types.test.ts +11 -42
- package/src/builder.test.ts +82 -243
- package/src/builder.ts +8 -110
- package/src/codegen/codegen-config-schema.ts +82 -77
- package/src/codegen/config.test.ts +55 -143
- package/src/codegen/config.ts +10 -67
- package/src/codegen/dictionary-spec-contract.test.ts +28 -0
- package/src/codegen/dictionary-spec-contract.ts +138 -0
- package/src/codegen/emit/dictionary-schema-file.ts +7 -43
- package/src/codegen/emit/instance-file.test.ts +35 -52
- package/src/codegen/emit/instance-file.ts +170 -268
- package/src/codegen/emit/namespace-loaders-file.test.ts +72 -74
- package/src/codegen/emit/namespace-loaders-file.ts +147 -90
- package/src/codegen/emit/types-file.test.ts +20 -41
- package/src/codegen/emit/types-file.ts +48 -74
- package/src/codegen/generate-i18n-types.test.ts +163 -492
- package/src/codegen/generate-i18n-types.ts +7 -269
- package/src/codegen/paths.ts +0 -14
- package/src/codegen/project-locales-set-namespace.test.ts +44 -86
- package/src/codegen/read-dictionary.test.ts +27 -9
- package/src/codegen/read-dictionary.ts +18 -40
- package/src/codegen/regenerate-namespaces.ts +180 -0
- package/src/codegen/run-codegen.test.ts +230 -0
- package/src/codegen/run-codegen.ts +252 -0
- package/src/codegen/types.ts +0 -6
- package/src/codegen-config/build-config.ts +4 -23
- package/src/codegen-config/codegen-config.test.ts +10 -6
- package/src/codegen-config/index.ts +18 -3
- package/src/engine.ts +3 -27
- package/src/fetch-artifact.ts +16 -0
- package/src/i18n-handle.ts +202 -0
- package/src/index.ts +15 -34
- package/src/project-locales.ts +2 -2
- package/src/scope-multi.ts +1 -20
- package/src/scope-types.ts +14 -6
- package/src/scope.test.ts +3 -312
- package/src/serialized-state.test.ts +114 -0
- package/src/serialized-state.ts +28 -0
- package/src/setup/setup-i18n.test.ts +22 -33
- package/src/setup/setup-i18n.ts +22 -27
- package/src/types.ts +3 -3
- package/src/validation/index.ts +4 -0
- package/src/IcuTranslationProviderSingle.test.ts +0 -177
- package/src/IcuTranslationProviderSingle.ts +0 -114
- package/src/builder-multi.ts +0 -481
- package/src/codegen/emit/dictionary-file.test.ts +0 -215
- package/src/codegen/emit/dictionary-file.ts +0 -244
- package/src/deep-freeze.test.ts +0 -40
- package/src/deep-freeze.ts +0 -22
- package/src/patch-key.test.ts +0 -186
- package/src/patch-key.ts +0 -140
- package/src/scope-single.ts +0 -85
- package/src/single-builder.ts +0 -153
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { describe, expectTypeOf, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
2
|
+
import { createI18nHandle } from "./builder.js";
|
|
3
|
+
import type { I18nHandle } from "./i18n-handle.js";
|
|
4
4
|
import { IcuTranslationProviderMulti } from "./IcuTranslationProviderMulti.js";
|
|
5
5
|
import type { LocalesForDeliveryArea, LocalesForDeliveryAreaOrAll } from "./builder-types.js";
|
|
6
|
+
import type { PartialMultiDictionary } from "./types.js";
|
|
6
7
|
|
|
7
8
|
type TypestateSchema = {
|
|
8
9
|
billing: { invoice_summary: { en: string; it: string } };
|
|
@@ -12,26 +13,6 @@ type TypestateParams = {
|
|
|
12
13
|
billing: { invoice_summary: { count: number } };
|
|
13
14
|
};
|
|
14
15
|
|
|
15
|
-
function assertBuilderTypestate(): void {
|
|
16
|
-
const engine = new IcuTranslationProviderMulti<TypestateSchema, TypestateParams>({});
|
|
17
|
-
const initial = createI18nMultiBuilder(engine);
|
|
18
|
-
|
|
19
|
-
// @ts-expect-error withLocale requires withNamespaces first
|
|
20
|
-
initial.withLocale("en");
|
|
21
|
-
// @ts-expect-error load requires withNamespaces first
|
|
22
|
-
void initial.load();
|
|
23
|
-
// @ts-expect-error withDeliveryArea requires withNamespaces first
|
|
24
|
-
initial.withDeliveryArea("eu");
|
|
25
|
-
// @ts-expect-error empty namespace list is rejected
|
|
26
|
-
initial.withNamespaces([]);
|
|
27
|
-
|
|
28
|
-
const ready = initial.withNamespaces(["billing"]);
|
|
29
|
-
ready.withLocale("en");
|
|
30
|
-
void ready.load();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
void assertBuilderTypestate;
|
|
34
|
-
|
|
35
16
|
describe("builder-types", () => {
|
|
36
17
|
type ProjectLocale = "en" | "en-US" | "it" | "fr";
|
|
37
18
|
type DeliveryArea = "eu" | "amer";
|
|
@@ -55,26 +36,14 @@ describe("builder-types", () => {
|
|
|
55
36
|
});
|
|
56
37
|
});
|
|
57
38
|
|
|
58
|
-
describe("
|
|
59
|
-
it("
|
|
60
|
-
const engine = new IcuTranslationProviderMulti<TypestateSchema, TypestateParams>({});
|
|
61
|
-
const initial = createI18nMultiBuilder(engine);
|
|
62
|
-
expectTypeOf(initial).toEqualTypeOf<
|
|
63
|
-
I18nBuilderMultiInitialImpl<TypestateSchema, TypestateParams>
|
|
64
|
-
>();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("ready builder exposes partition and load", () => {
|
|
39
|
+
describe("I18nHandle typestate", () => {
|
|
40
|
+
it("exposes load / peek / serialize", () => {
|
|
68
41
|
const engine = new IcuTranslationProviderMulti<TypestateSchema, TypestateParams>({});
|
|
69
|
-
const
|
|
70
|
-
expectTypeOf(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"en" | "it",
|
|
76
|
-
readonly ["billing"]
|
|
77
|
-
>
|
|
78
|
-
>();
|
|
42
|
+
const handle = createI18nHandle(engine);
|
|
43
|
+
expectTypeOf(handle).toMatchTypeOf<I18nHandle<TypestateSchema, TypestateParams>>();
|
|
44
|
+
expectTypeOf(handle.serialize).returns.toEqualTypeOf<{
|
|
45
|
+
dictionary: PartialMultiDictionary<TypestateSchema>;
|
|
46
|
+
resources: readonly (readonly [string, string])[];
|
|
47
|
+
}>();
|
|
79
48
|
});
|
|
80
49
|
});
|
package/src/builder.test.ts
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import { describe, expect, expectTypeOf, it, vi } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { createI18nHandle } from "./builder.js";
|
|
3
3
|
import { IcuTranslationProviderMulti } from "./IcuTranslationProviderMulti.js";
|
|
4
|
-
import { IcuTranslationProviderSingle } from "./IcuTranslationProviderSingle.js";
|
|
5
|
-
|
|
6
|
-
type SingleSchema = {
|
|
7
|
-
login_button: { en: string; it: string };
|
|
8
|
-
welcome: { en: string; it: string };
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
type SingleParams = {
|
|
12
|
-
login_button: never;
|
|
13
|
-
welcome: { name: string };
|
|
14
|
-
};
|
|
15
4
|
|
|
16
5
|
type MultiSchema = {
|
|
17
6
|
default: {
|
|
@@ -33,12 +22,6 @@ type TestMultiParams = {
|
|
|
33
22
|
};
|
|
34
23
|
};
|
|
35
24
|
|
|
36
|
-
type TestDeliveryArea = "eu" | "us";
|
|
37
|
-
type TestDeliveryArtifacts = {
|
|
38
|
-
eu: readonly ["it"];
|
|
39
|
-
us: readonly ["en"];
|
|
40
|
-
};
|
|
41
|
-
|
|
42
25
|
const billingIt = {
|
|
43
26
|
invoice_summary: {
|
|
44
27
|
it: "Hai {count, plural, one {1 fattura} other {{count} fatture}} per {name}",
|
|
@@ -56,89 +39,64 @@ const defaultIt = {
|
|
|
56
39
|
welcome: { it: "Benvenuto {name}!" },
|
|
57
40
|
};
|
|
58
41
|
|
|
59
|
-
describe("
|
|
42
|
+
describe("I18nHandle", () => {
|
|
60
43
|
it("load() invokes mock loaders, merges into the engine, and returns a bound view", async () => {
|
|
61
44
|
const billingLoader = vi.fn(async (locale: string) => {
|
|
62
45
|
return locale === "it" ? billingIt : billingEn;
|
|
63
46
|
});
|
|
64
47
|
const engine = new IcuTranslationProviderMulti<MultiSchema, TestMultiParams>({});
|
|
65
|
-
const
|
|
48
|
+
const handle = createI18nHandle(engine, {
|
|
66
49
|
namespaceLoaders: {
|
|
67
50
|
billing: billingLoader,
|
|
68
51
|
},
|
|
69
52
|
});
|
|
70
53
|
|
|
71
|
-
const view = await
|
|
54
|
+
const view = await handle.load({ namespaces: ["billing"], locale: "it" });
|
|
72
55
|
|
|
73
|
-
expect(billingLoader).toHaveBeenCalledWith("it");
|
|
56
|
+
expect(billingLoader).toHaveBeenCalledWith("it", { locale: "it" });
|
|
74
57
|
expect(view.locale).toBe("it");
|
|
75
58
|
expect(view.t("billing", "invoice_summary", { count: 2, name: "Ada" })).toBe(
|
|
76
59
|
"Hai 2 fatture per Ada"
|
|
77
60
|
);
|
|
78
61
|
});
|
|
79
62
|
|
|
80
|
-
it("
|
|
63
|
+
it("loads multiple namespaces in one call", async () => {
|
|
81
64
|
const billingLoader = vi.fn(async (locale: string) =>
|
|
82
65
|
locale === "it" ? billingIt : billingEn
|
|
83
66
|
);
|
|
84
67
|
const defaultLoader = vi.fn(async () => defaultIt);
|
|
85
68
|
const engine = new IcuTranslationProviderMulti<MultiSchema, TestMultiParams>({});
|
|
86
69
|
|
|
87
|
-
const view = await
|
|
70
|
+
const view = await createI18nHandle(engine, {
|
|
88
71
|
namespaceLoaders: {
|
|
89
72
|
billing: billingLoader,
|
|
90
73
|
default: defaultLoader,
|
|
91
74
|
},
|
|
92
|
-
})
|
|
93
|
-
.withNamespaces(["billing", "default"])
|
|
94
|
-
.withLocale("it")
|
|
95
|
-
.load();
|
|
75
|
+
}).load({ namespaces: ["billing", "default"], locale: "it" });
|
|
96
76
|
|
|
97
|
-
expect(billingLoader).toHaveBeenCalledWith("it");
|
|
98
|
-
expect(defaultLoader).toHaveBeenCalledWith("it");
|
|
77
|
+
expect(billingLoader).toHaveBeenCalledWith("it", { locale: "it" });
|
|
78
|
+
expect(defaultLoader).toHaveBeenCalledWith("it", { locale: "it" });
|
|
99
79
|
expect(view.t("default", "login_button")).toBe("Accedi");
|
|
100
80
|
expect(view.t("billing", "invoice_summary", { count: 1, name: "Bob" })).toBe(
|
|
101
81
|
"Hai 1 fattura per Bob"
|
|
102
82
|
);
|
|
103
83
|
});
|
|
104
84
|
|
|
105
|
-
it("
|
|
106
|
-
const billingLoader = vi.fn(async () => billingEn);
|
|
107
|
-
const engine = new IcuTranslationProviderMulti<MultiSchema, TestMultiParams>({});
|
|
108
|
-
|
|
109
|
-
const view = await createI18nBuilder(engine, {
|
|
110
|
-
namespaceLoaders: { billing: billingLoader },
|
|
111
|
-
})
|
|
112
|
-
.withNamespaces(["billing"])
|
|
113
|
-
.withLocale("en")
|
|
114
|
-
.load();
|
|
115
|
-
|
|
116
|
-
view.set(
|
|
117
|
-
"billing",
|
|
118
|
-
"invoice_summary",
|
|
119
|
-
"You have {count, plural, one {1 invoice only} other {{count} invoices only}} for {name}"
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
expect(view.t("billing", "invoice_summary", { count: 1, name: "Ada" })).toBe(
|
|
123
|
-
"You have 1 invoice only for Ada"
|
|
124
|
-
);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("accumulates engine data across repeated load() calls on the same builder", async () => {
|
|
85
|
+
it("accumulates engine data across repeated load() calls on the same handle", async () => {
|
|
128
86
|
const billingLoader = vi.fn(async (locale: string) =>
|
|
129
87
|
locale === "it" ? billingIt : billingEn
|
|
130
88
|
);
|
|
131
89
|
const defaultLoader = vi.fn(async () => defaultIt);
|
|
132
90
|
const engine = new IcuTranslationProviderMulti<MultiSchema, TestMultiParams>({});
|
|
133
|
-
const
|
|
91
|
+
const handle = createI18nHandle(engine, {
|
|
134
92
|
namespaceLoaders: {
|
|
135
93
|
billing: billingLoader,
|
|
136
94
|
default: defaultLoader,
|
|
137
95
|
},
|
|
138
96
|
});
|
|
139
97
|
|
|
140
|
-
const billingView = await
|
|
141
|
-
const defaultView = await
|
|
98
|
+
const billingView = await handle.load({ namespaces: ["billing"], locale: "it" });
|
|
99
|
+
const defaultView = await handle.load({ namespaces: ["default"], locale: "it" });
|
|
142
100
|
|
|
143
101
|
expect(billingLoader).toHaveBeenCalledTimes(1);
|
|
144
102
|
expect(defaultLoader).toHaveBeenCalledTimes(1);
|
|
@@ -146,8 +104,8 @@ describe("I18nBuilder multi", () => {
|
|
|
146
104
|
"Hai 1 fattura per Ada"
|
|
147
105
|
);
|
|
148
106
|
expect(defaultView.t("default", "login_button")).toBe("Accedi");
|
|
149
|
-
expect(engine.getAll().billing
|
|
150
|
-
expect(engine.getAll().default
|
|
107
|
+
expect(engine.getAll().billing?.invoice_summary?.it).toBe(billingIt.invoice_summary.it);
|
|
108
|
+
expect(engine.getAll().default?.login_button?.it).toBe("Accedi");
|
|
151
109
|
});
|
|
152
110
|
|
|
153
111
|
it("returns a view scoped to namespaces declared at load time", async () => {
|
|
@@ -156,15 +114,15 @@ describe("I18nBuilder multi", () => {
|
|
|
156
114
|
);
|
|
157
115
|
const defaultLoader = vi.fn(async () => defaultIt);
|
|
158
116
|
const engine = new IcuTranslationProviderMulti<MultiSchema, TestMultiParams>({});
|
|
159
|
-
const
|
|
117
|
+
const handle = createI18nHandle(engine, {
|
|
160
118
|
namespaceLoaders: {
|
|
161
119
|
billing: billingLoader,
|
|
162
120
|
default: defaultLoader,
|
|
163
121
|
},
|
|
164
122
|
});
|
|
165
123
|
|
|
166
|
-
const billingView = await
|
|
167
|
-
await
|
|
124
|
+
const billingView = await handle.load({ namespaces: ["billing"], locale: "it" });
|
|
125
|
+
await handle.load({ namespaces: ["default"], locale: "it" });
|
|
168
126
|
|
|
169
127
|
expect(billingView.t("billing", "invoice_summary", { count: 1, name: "Ada" })).toBe(
|
|
170
128
|
"Hai 1 fattura per Ada"
|
|
@@ -172,74 +130,84 @@ describe("I18nBuilder multi", () => {
|
|
|
172
130
|
// billingView is typed for the billing namespace only; default is excluded at compile time.
|
|
173
131
|
});
|
|
174
132
|
|
|
175
|
-
it("
|
|
133
|
+
it("maps locale to delivery partition via partitionForLocale", async () => {
|
|
176
134
|
const billingLoader = vi.fn(async (area: string) => {
|
|
177
135
|
return area === "eu" ? billingIt : billingEn;
|
|
178
136
|
});
|
|
179
137
|
const engine = new IcuTranslationProviderMulti<MultiSchema, TestMultiParams>({});
|
|
180
138
|
|
|
181
|
-
const
|
|
182
|
-
MultiSchema,
|
|
183
|
-
TestMultiParams,
|
|
184
|
-
"en" | "it",
|
|
185
|
-
TestDeliveryArea,
|
|
186
|
-
TestDeliveryArtifacts
|
|
187
|
-
>(engine, {
|
|
139
|
+
const view = await createI18nHandle(engine, {
|
|
188
140
|
namespaceLoaders: {
|
|
189
141
|
billing: billingLoader,
|
|
190
142
|
},
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
.withDeliveryArea("eu")
|
|
194
|
-
.load();
|
|
143
|
+
partitionForLocale: (locale) => (locale === "it" ? "eu" : "us"),
|
|
144
|
+
}).load({ namespaces: ["billing"], locale: "it" });
|
|
195
145
|
|
|
196
|
-
expect(billingLoader).toHaveBeenCalledWith("eu");
|
|
197
|
-
expect(
|
|
146
|
+
expect(billingLoader).toHaveBeenCalledWith("eu", { locale: "it" });
|
|
147
|
+
expect(view.locale).toBe("it");
|
|
148
|
+
expect(view.t("billing", "invoice_summary", { count: 2, name: "Ada" })).toBe(
|
|
198
149
|
"Hai 2 fatture per Ada"
|
|
199
150
|
);
|
|
200
|
-
expectTypeOf(
|
|
151
|
+
expectTypeOf(view.t).toBeCallableWith("billing", "invoice_summary", {
|
|
201
152
|
count: 2,
|
|
202
153
|
name: "Ada",
|
|
203
154
|
});
|
|
204
|
-
expectTypeOf(euView.forLocale).toBeCallableWith("it");
|
|
205
|
-
expectTypeOf(euView.forLocale).parameters.not.toMatchTypeOf<["en-US"]>();
|
|
206
155
|
});
|
|
207
156
|
|
|
208
|
-
it("loads
|
|
209
|
-
const billingLoader = vi.fn(async () =>
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
157
|
+
it("loads partitioned namespace loaders with locale", async () => {
|
|
158
|
+
const billingLoader = vi.fn(async (locale: string) =>
|
|
159
|
+
locale === "it"
|
|
160
|
+
? {
|
|
161
|
+
invoice_summary: {
|
|
162
|
+
it: "Hai {count, plural, one {1 fattura} other {{count} fatture}} per {name}",
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
: {
|
|
166
|
+
invoice_summary: {
|
|
167
|
+
en: "You have {count, plural, one {1 invoice} other {{count} invoices}} for {name}",
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
);
|
|
215
171
|
const engine = new IcuTranslationProviderMulti<MultiSchema, TestMultiParams>({});
|
|
216
172
|
|
|
217
|
-
const view = await
|
|
173
|
+
const view = await createI18nHandle(engine, {
|
|
218
174
|
namespaceLoaders: {
|
|
219
175
|
billing: billingLoader,
|
|
220
176
|
},
|
|
221
|
-
})
|
|
222
|
-
.withNamespaces(["billing"])
|
|
223
|
-
.load();
|
|
177
|
+
}).load({ namespaces: ["billing"], locale: "en" });
|
|
224
178
|
|
|
225
179
|
expect(billingLoader).toHaveBeenCalledTimes(1);
|
|
226
|
-
expect(billingLoader.mock.calls[0]).toEqual([]);
|
|
227
|
-
expect(view.t("billing", "invoice_summary",
|
|
180
|
+
expect(billingLoader.mock.calls[0]).toEqual(["en", { locale: "en" }]);
|
|
181
|
+
expect(view.t("billing", "invoice_summary", { count: 1, name: "Ada" })).toBe(
|
|
228
182
|
"You have 1 invoice for Ada"
|
|
229
183
|
);
|
|
230
184
|
});
|
|
231
185
|
|
|
186
|
+
it("rejects an empty namespaces array", async () => {
|
|
187
|
+
const billingLoader = vi.fn(async () => billingEn);
|
|
188
|
+
const engine = new IcuTranslationProviderMulti<MultiSchema, TestMultiParams>({});
|
|
189
|
+
const handle = createI18nHandle(engine, {
|
|
190
|
+
namespaceLoaders: {
|
|
191
|
+
billing: billingLoader,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await expect(
|
|
196
|
+
handle.load({ namespaces: [] as unknown as ["billing"], locale: "en" })
|
|
197
|
+
).rejects.toThrow("load() requires a non-empty namespaces array");
|
|
198
|
+
});
|
|
199
|
+
|
|
232
200
|
it("skips a repeated load for the same namespace partition", async () => {
|
|
233
201
|
const billingLoader = vi.fn(async (locale: string) =>
|
|
234
202
|
locale === "it" ? billingIt : billingEn
|
|
235
203
|
);
|
|
236
204
|
const engine = new IcuTranslationProviderMulti<MultiSchema, TestMultiParams>({});
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
205
|
+
const handle = createI18nHandle(engine, {
|
|
206
|
+
namespaceLoaders: { billing: billingLoader },
|
|
207
|
+
});
|
|
240
208
|
|
|
241
|
-
await
|
|
242
|
-
await
|
|
209
|
+
await handle.load({ namespaces: ["billing"], locale: "it" });
|
|
210
|
+
await handle.load({ namespaces: ["billing"], locale: "it" });
|
|
243
211
|
|
|
244
212
|
expect(billingLoader).toHaveBeenCalledTimes(1);
|
|
245
213
|
});
|
|
@@ -247,175 +215,46 @@ describe("I18nBuilder multi", () => {
|
|
|
247
215
|
it("returns a destructuring-safe locale-bound scope from load()", async () => {
|
|
248
216
|
const billingLoader = vi.fn(async () => billingEn);
|
|
249
217
|
const engine = new IcuTranslationProviderMulti<MultiSchema, TestMultiParams>({});
|
|
250
|
-
const { t } = await
|
|
218
|
+
const { t } = await createI18nHandle(engine, {
|
|
251
219
|
namespaceLoaders: { billing: billingLoader },
|
|
252
|
-
})
|
|
253
|
-
.withNamespaces(["billing"])
|
|
254
|
-
.withLocale("en")
|
|
255
|
-
.load();
|
|
220
|
+
}).load({ namespaces: ["billing"], locale: "en" });
|
|
256
221
|
|
|
257
222
|
expect(t("billing", "invoice_summary", { count: 1, name: "Ada" })).toBe(
|
|
258
223
|
"You have 1 invoice for Ada"
|
|
259
224
|
);
|
|
260
225
|
});
|
|
261
226
|
|
|
262
|
-
it("preserves runtime patches when a later builder reloads the same resource", async () => {
|
|
263
|
-
const billingLoader = vi.fn(async () => billingEn);
|
|
264
|
-
const engine = new IcuTranslationProviderMulti<MultiSchema, TestMultiParams>({});
|
|
265
|
-
const options = { namespaceLoaders: { billing: billingLoader } };
|
|
266
|
-
|
|
267
|
-
const firstView = await createI18nBuilder(engine, options)
|
|
268
|
-
.withNamespaces(["billing"])
|
|
269
|
-
.withLocale("en")
|
|
270
|
-
.load();
|
|
271
|
-
|
|
272
|
-
firstView.set(
|
|
273
|
-
"billing",
|
|
274
|
-
"invoice_summary",
|
|
275
|
-
"You have {count, plural, one {1 invoice only} other {{count} invoices only}} for {name}"
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
const secondView = await createI18nBuilder(engine, options)
|
|
279
|
-
.withNamespaces(["billing"])
|
|
280
|
-
.withLocale("en")
|
|
281
|
-
.load();
|
|
282
|
-
|
|
283
|
-
expect(billingLoader).toHaveBeenCalledTimes(1);
|
|
284
|
-
expect(secondView.t("billing", "invoice_summary", { count: 1, name: "Ada" })).toBe(
|
|
285
|
-
"You have 1 invoice only for Ada"
|
|
286
|
-
);
|
|
287
|
-
});
|
|
288
|
-
|
|
289
227
|
it("still loads a different partition for the same namespace", async () => {
|
|
290
228
|
const billingLoader = vi.fn(async (locale: string) =>
|
|
291
229
|
locale === "it" ? billingIt : billingEn
|
|
292
230
|
);
|
|
293
231
|
const engine = new IcuTranslationProviderMulti<MultiSchema, TestMultiParams>({});
|
|
294
|
-
const
|
|
232
|
+
const handle = createI18nHandle(engine, {
|
|
295
233
|
namespaceLoaders: { billing: billingLoader },
|
|
296
234
|
});
|
|
297
235
|
|
|
298
|
-
await
|
|
299
|
-
await
|
|
236
|
+
await handle.load({ namespaces: ["billing"], locale: "it" });
|
|
237
|
+
await handle.load({ namespaces: ["billing"], locale: "en" });
|
|
300
238
|
|
|
301
239
|
expect(billingLoader).toHaveBeenCalledTimes(2);
|
|
302
|
-
expect(billingLoader).toHaveBeenNthCalledWith(1, "it");
|
|
303
|
-
expect(billingLoader).toHaveBeenNthCalledWith(2, "en");
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
describe("I18nBuilder single", () => {
|
|
308
|
-
it("load() invokes a locale loader and returns a bound view", async () => {
|
|
309
|
-
const dictionaryLoader = vi.fn(async (locale: string) => {
|
|
310
|
-
return locale === "it"
|
|
311
|
-
? {
|
|
312
|
-
login_button: { it: "Accedi" },
|
|
313
|
-
welcome: { it: "Benvenuto {name}!" },
|
|
314
|
-
}
|
|
315
|
-
: {
|
|
316
|
-
login_button: { en: "Login" },
|
|
317
|
-
welcome: { en: "Welcome {name}!" },
|
|
318
|
-
};
|
|
319
|
-
});
|
|
320
|
-
const engine = new IcuTranslationProviderSingle<SingleSchema, SingleParams>({
|
|
321
|
-
login_button: { en: "", it: "" },
|
|
322
|
-
welcome: { en: "", it: "" },
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
const view = await createI18nBuilder<SingleSchema, SingleParams>(engine, { dictionaryLoader })
|
|
326
|
-
.withLocale("it")
|
|
327
|
-
.load();
|
|
328
|
-
|
|
329
|
-
expect(dictionaryLoader).toHaveBeenCalledWith("it");
|
|
330
|
-
expect(view.locale).toBe("it");
|
|
331
|
-
expect(view.t("login_button")).toBe("Accedi");
|
|
332
|
-
expect(view.t("welcome", { name: "Ada" })).toBe("Benvenuto Ada!");
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
it("patches a preloaded key via scope.set() after load()", async () => {
|
|
336
|
-
const dictionaryLoader = vi.fn(async () => ({
|
|
337
|
-
login_button: { en: "Login" },
|
|
338
|
-
welcome: { en: "Welcome {name}!" },
|
|
339
|
-
}));
|
|
340
|
-
const engine = new IcuTranslationProviderSingle<SingleSchema, SingleParams>({
|
|
341
|
-
login_button: { en: "", it: "" },
|
|
342
|
-
welcome: { en: "", it: "" },
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
const view = await createI18nBuilder<SingleSchema, SingleParams>(engine, { dictionaryLoader })
|
|
346
|
-
.withLocale("en")
|
|
347
|
-
.load();
|
|
348
|
-
|
|
349
|
-
view.set("login_button", "Sign in");
|
|
350
|
-
|
|
351
|
-
expect(view.t("login_button")).toBe("Sign in");
|
|
352
|
-
expect(view.t("welcome", { name: "Ada" })).toBe("Welcome Ada!");
|
|
240
|
+
expect(billingLoader).toHaveBeenNthCalledWith(1, "it", { locale: "it" });
|
|
241
|
+
expect(billingLoader).toHaveBeenNthCalledWith(2, "en", { locale: "en" });
|
|
353
242
|
});
|
|
354
243
|
|
|
355
|
-
it("
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const engine = new IcuTranslationProviderSingle<SingleSchema, SingleParams>({
|
|
361
|
-
// @ts-expect-error
|
|
362
|
-
login_button: { en: "" },
|
|
363
|
-
// @ts-expect-error
|
|
364
|
-
welcome: { en: "" },
|
|
244
|
+
it("peek() returns null until resources are loaded, then a sync scope", async () => {
|
|
245
|
+
const billingLoader = vi.fn(async () => billingEn);
|
|
246
|
+
const engine = new IcuTranslationProviderMulti<MultiSchema, TestMultiParams>({});
|
|
247
|
+
const handle = createI18nHandle(engine, {
|
|
248
|
+
namespaceLoaders: { billing: billingLoader },
|
|
365
249
|
});
|
|
366
250
|
|
|
367
|
-
|
|
368
|
-
.withLocale("en")
|
|
369
|
-
.load();
|
|
251
|
+
expect(handle.peek({ namespaces: ["billing"], locale: "en" })).toBeNull();
|
|
370
252
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
253
|
+
await handle.load({ namespaces: ["billing"], locale: "en" });
|
|
254
|
+
const sync = handle.peek({ namespaces: ["billing"], locale: "en" });
|
|
255
|
+
expect(sync).not.toBeNull();
|
|
256
|
+
expect(sync!.t("billing", "invoice_summary", { count: 1, name: "Ada" })).toBe(
|
|
257
|
+
"You have 1 invoice for Ada"
|
|
374
258
|
);
|
|
375
259
|
});
|
|
376
|
-
|
|
377
|
-
it("skips a repeated load for the same locale partition", async () => {
|
|
378
|
-
const dictionaryLoader = vi.fn(async () => ({
|
|
379
|
-
login_button: { en: "Login" },
|
|
380
|
-
welcome: { en: "Welcome {name}!" },
|
|
381
|
-
}));
|
|
382
|
-
const engine = new IcuTranslationProviderSingle<SingleSchema, SingleParams>({
|
|
383
|
-
login_button: { en: "", it: "" },
|
|
384
|
-
welcome: { en: "", it: "" },
|
|
385
|
-
});
|
|
386
|
-
const options = { dictionaryLoader };
|
|
387
|
-
const chain = () =>
|
|
388
|
-
createI18nBuilder<SingleSchema, SingleParams>(engine, options).withLocale("en");
|
|
389
|
-
|
|
390
|
-
await chain().load();
|
|
391
|
-
await chain().load();
|
|
392
|
-
|
|
393
|
-
expect(dictionaryLoader).toHaveBeenCalledTimes(1);
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it("preserves runtime patches when a later builder reloads the same resource", async () => {
|
|
397
|
-
const dictionaryLoader = vi.fn(async () => ({
|
|
398
|
-
login_button: { en: "Login" },
|
|
399
|
-
welcome: { en: "Welcome {name}!" },
|
|
400
|
-
}));
|
|
401
|
-
const engine = new IcuTranslationProviderSingle<SingleSchema, SingleParams>({
|
|
402
|
-
login_button: { en: "", it: "" },
|
|
403
|
-
welcome: { en: "", it: "" },
|
|
404
|
-
});
|
|
405
|
-
const options = { dictionaryLoader };
|
|
406
|
-
|
|
407
|
-
const firstView = await createI18nBuilder<SingleSchema, SingleParams>(engine, options)
|
|
408
|
-
.withLocale("en")
|
|
409
|
-
.load();
|
|
410
|
-
|
|
411
|
-
firstView.set("login_button", "Sign in");
|
|
412
|
-
|
|
413
|
-
const secondView = await createI18nBuilder<SingleSchema, SingleParams>(engine, options)
|
|
414
|
-
.withLocale("en")
|
|
415
|
-
.load();
|
|
416
|
-
|
|
417
|
-
expect(dictionaryLoader).toHaveBeenCalledTimes(1);
|
|
418
|
-
expect(secondView.t("login_button")).toBe("Sign in");
|
|
419
|
-
expect(secondView.t("welcome", { name: "Ada" })).toBe("Welcome Ada!");
|
|
420
|
-
});
|
|
421
260
|
});
|
package/src/builder.ts
CHANGED
|
@@ -1,112 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
import { I18nBuilderMultiInitialImpl, type I18nBuilderMultiOptions } from "./builder-multi.js";
|
|
3
|
-
import { I18nBuilderSingleImpl, type I18nBuilderSingleOptions } from "./single-builder.js";
|
|
4
|
-
import type { I18nEngineMultiImpl, I18nEngineSingleImpl } from "./engine.js";
|
|
5
|
-
import type { KeyDictionary, LocaleOfMulti, LocaleOfSingle, MultiDictionary } from "./types.js";
|
|
6
|
-
import type { MultiParams } from "./scope-types.js";
|
|
7
|
-
|
|
8
|
-
export type { CanonicalLoader, NamespaceLoader, PartitionedLoader } from "./builder-loaders.js";
|
|
1
|
+
export type { NamespaceLoader, PartitionedLoader } from "./builder-loaders.js";
|
|
9
2
|
export { invokeNamespaceLoader } from "./builder-loaders.js";
|
|
10
3
|
export type {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from "./
|
|
18
|
-
export type {
|
|
19
|
-
I18nBuilderSingle,
|
|
20
|
-
I18nBuilderSingleForLocale,
|
|
21
|
-
I18nBuilderSingleOptions,
|
|
22
|
-
} from "./single-builder.js";
|
|
23
|
-
|
|
24
|
-
type AnySingleEngine = I18nEngineSingleImpl<KeyDictionary, { [K in keyof KeyDictionary]: unknown }>;
|
|
25
|
-
type AnyMultiEngine = I18nEngineMultiImpl<MultiDictionary, MultiParams<MultiDictionary>>;
|
|
26
|
-
|
|
27
|
-
function isMultiEngine(engine: AnySingleEngine | AnyMultiEngine): engine is AnyMultiEngine {
|
|
28
|
-
return engine.__i18nEngineMode === "multi";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function createI18nSingleBuilder<
|
|
32
|
-
Schema extends KeyDictionary,
|
|
33
|
-
Params extends { [K in keyof Schema]: unknown },
|
|
34
|
-
RequestLocales extends string = LocaleOfSingle<Schema>,
|
|
35
|
-
>(
|
|
36
|
-
engine: I18nEngineSingleImpl<Schema, Params, RequestLocales>,
|
|
37
|
-
options?: I18nBuilderSingleOptions<Schema, RequestLocales>
|
|
38
|
-
): I18nBuilderSingleImpl<Schema, Params, RequestLocales> {
|
|
39
|
-
return new I18nBuilderSingleImpl(engine, options);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function createI18nMultiBuilder<
|
|
43
|
-
Schema extends MultiDictionary,
|
|
44
|
-
Params extends MultiParams<Schema>,
|
|
45
|
-
RequestLocales extends string = LocaleOfMulti<Schema>,
|
|
46
|
-
DeliveryArea extends string = never,
|
|
47
|
-
DeliveryArtifacts extends DeliveryArtifactsMap<RequestLocales, DeliveryArea> =
|
|
48
|
-
DeliveryArtifactsMap<RequestLocales, DeliveryArea>,
|
|
49
|
-
>(
|
|
50
|
-
engine: I18nEngineMultiImpl<Schema, Params, RequestLocales>,
|
|
51
|
-
options?: I18nBuilderMultiOptions<Schema, RequestLocales>
|
|
52
|
-
): I18nBuilderMultiInitialImpl<
|
|
53
|
-
Schema,
|
|
54
|
-
Params,
|
|
55
|
-
RequestLocales,
|
|
56
|
-
RequestLocales,
|
|
57
|
-
DeliveryArea,
|
|
58
|
-
DeliveryArtifacts
|
|
59
|
-
> {
|
|
60
|
-
return new I18nBuilderMultiInitialImpl<
|
|
61
|
-
Schema,
|
|
62
|
-
Params,
|
|
63
|
-
RequestLocales,
|
|
64
|
-
RequestLocales,
|
|
65
|
-
DeliveryArea,
|
|
66
|
-
DeliveryArtifacts
|
|
67
|
-
>(engine, options);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function createI18nBuilder<
|
|
71
|
-
Schema extends MultiDictionary,
|
|
72
|
-
Params extends MultiParams<Schema>,
|
|
73
|
-
RequestLocales extends string = LocaleOfMulti<Schema>,
|
|
74
|
-
>(
|
|
75
|
-
engine: I18nEngineMultiImpl<Schema, Params, RequestLocales>,
|
|
76
|
-
options?: I18nBuilderMultiOptions<Schema, RequestLocales>
|
|
77
|
-
): I18nBuilderMultiInitialImpl<
|
|
78
|
-
Schema,
|
|
79
|
-
Params,
|
|
80
|
-
RequestLocales,
|
|
81
|
-
RequestLocales,
|
|
82
|
-
never,
|
|
83
|
-
DeliveryArtifactsMap<RequestLocales, never>
|
|
84
|
-
>;
|
|
85
|
-
|
|
86
|
-
export function createI18nBuilder<
|
|
87
|
-
Schema extends KeyDictionary,
|
|
88
|
-
Params extends { [K in keyof Schema]: unknown },
|
|
89
|
-
RequestLocales extends string = LocaleOfSingle<Schema>,
|
|
90
|
-
>(
|
|
91
|
-
engine: I18nEngineSingleImpl<Schema, Params, RequestLocales>,
|
|
92
|
-
options?: I18nBuilderSingleOptions<Schema, RequestLocales>
|
|
93
|
-
): I18nBuilderSingleImpl<Schema, Params, RequestLocales>;
|
|
94
|
-
|
|
95
|
-
export function createI18nBuilder(
|
|
96
|
-
engine: AnySingleEngine | AnyMultiEngine,
|
|
97
|
-
options?:
|
|
98
|
-
| I18nBuilderMultiOptions<MultiDictionary, string>
|
|
99
|
-
| I18nBuilderSingleOptions<KeyDictionary, string>
|
|
100
|
-
): never {
|
|
101
|
-
if (isMultiEngine(engine)) {
|
|
102
|
-
return createI18nMultiBuilder(
|
|
103
|
-
engine,
|
|
104
|
-
options as I18nBuilderMultiOptions<MultiDictionary, string> | undefined
|
|
105
|
-
) as never;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return createI18nSingleBuilder(
|
|
109
|
-
engine,
|
|
110
|
-
options as I18nBuilderSingleOptions<KeyDictionary, string> | undefined
|
|
111
|
-
) as never;
|
|
112
|
-
}
|
|
4
|
+
I18nHandle,
|
|
5
|
+
I18nHandleOptions,
|
|
6
|
+
LoadNamespacesInput,
|
|
7
|
+
PartitionForLocale,
|
|
8
|
+
ScopeForLocale,
|
|
9
|
+
} from "./i18n-handle.js";
|
|
10
|
+
export { createI18nHandle } from "./i18n-handle.js";
|