@xndrjs/i18n 0.7.0 → 0.8.0-alpha.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,12 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
mkdtempSync,
|
|
3
|
-
existsSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
readFileSync,
|
|
6
|
-
rmSync,
|
|
7
|
-
statSync,
|
|
8
|
-
writeFileSync,
|
|
9
|
-
} from "node:fs";
|
|
1
|
+
import { mkdtempSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
10
2
|
import { tmpdir } from "node:os";
|
|
11
3
|
import { join } from "node:path";
|
|
12
4
|
import { spawnSync } from "node:child_process";
|
|
@@ -65,11 +57,8 @@ describe("generate-i18n-types", () => {
|
|
|
65
57
|
default: "src/i18n/translations/default.json",
|
|
66
58
|
billing: "src/i18n/translations/billing.json",
|
|
67
59
|
},
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
instanceOutput: "src/i18n/instance.generated.ts",
|
|
71
|
-
paramsTypeName: "AppParams",
|
|
72
|
-
schemaTypeName: "AppSchema",
|
|
60
|
+
projectName: "App",
|
|
61
|
+
codegenPath: "src/i18n",
|
|
73
62
|
})
|
|
74
63
|
);
|
|
75
64
|
|
|
@@ -79,13 +68,11 @@ describe("generate-i18n-types", () => {
|
|
|
79
68
|
const types = readFileSync(join(tempDir, "src/i18n/i18n-types.generated.ts"), "utf8");
|
|
80
69
|
const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
|
|
81
70
|
expect(types).toContain("export const I18N_MODE = 'multi' as const");
|
|
82
|
-
expect(types).toContain(
|
|
71
|
+
expect(types).toContain('export const AppLocales = ["en"] as const;');
|
|
72
|
+
expect(types).toContain("export type AppLocale = (typeof AppLocales)[number];");
|
|
83
73
|
expect(factory).toContain("export function createI18n(");
|
|
84
|
-
expect(factory).toContain("
|
|
85
|
-
expect(factory).toContain("
|
|
86
|
-
expect(factory).toContain("projectDictionaryLocalesCore(dictionary, locales)");
|
|
87
|
-
expect(factory).toContain("export function projectNamespaceLocales(");
|
|
88
|
-
expect(factory).toContain("projectNamespaceLocalesCore(dictionary, locales)");
|
|
74
|
+
expect(factory).toContain("onMissing?: OnMissingTranslation");
|
|
75
|
+
expect(factory).toContain("state?: { dictionary: InitialSchema");
|
|
89
76
|
expect(factory).toContain("IcuTranslationProviderMulti");
|
|
90
77
|
expect(types).toContain("login_button: never");
|
|
91
78
|
expect(types).toContain("welcome: { name: string }");
|
|
@@ -110,12 +97,11 @@ describe("generate-i18n-types", () => {
|
|
|
110
97
|
writeFileSync(
|
|
111
98
|
join(tempDir, "i18n.codegen.json"),
|
|
112
99
|
JSON.stringify({
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
schemaTypeName: "AppSchema",
|
|
100
|
+
namespaces: {
|
|
101
|
+
default: "src/i18n/translations/translations.json",
|
|
102
|
+
},
|
|
103
|
+
projectName: "App",
|
|
104
|
+
codegenPath: "src/i18n",
|
|
119
105
|
})
|
|
120
106
|
);
|
|
121
107
|
|
|
@@ -123,8 +109,8 @@ describe("generate-i18n-types", () => {
|
|
|
123
109
|
|
|
124
110
|
const generatedPaths = [
|
|
125
111
|
join(tempDir, "src/i18n/i18n-types.generated.ts"),
|
|
126
|
-
join(tempDir, "src/i18n/dictionary.generated.ts"),
|
|
127
112
|
join(tempDir, "src/i18n/instance.generated.ts"),
|
|
113
|
+
join(tempDir, "src/i18n/namespace-loaders.generated.ts"),
|
|
128
114
|
];
|
|
129
115
|
const mtimesBefore = generatedPaths.map((filePath) => statSync(filePath).mtimeMs);
|
|
130
116
|
|
|
@@ -134,7 +120,7 @@ describe("generate-i18n-types", () => {
|
|
|
134
120
|
expect(mtimesAfter).toEqual(mtimesBefore);
|
|
135
121
|
});
|
|
136
122
|
|
|
137
|
-
it("generates dictionary schema file
|
|
123
|
+
it("always generates dictionary schema file under codegenPath", () => {
|
|
138
124
|
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
139
125
|
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
140
126
|
|
|
@@ -159,12 +145,8 @@ describe("generate-i18n-types", () => {
|
|
|
159
145
|
default: "src/i18n/translations/default.json",
|
|
160
146
|
billing: "src/i18n/translations/billing.json",
|
|
161
147
|
},
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
instanceOutput: "src/i18n/instance.generated.ts",
|
|
165
|
-
dictionarySchemaOutput: "src/i18n/dictionary-schema.generated.ts",
|
|
166
|
-
paramsTypeName: "AppParams",
|
|
167
|
-
schemaTypeName: "AppSchema",
|
|
148
|
+
projectName: "App",
|
|
149
|
+
codegenPath: "src/i18n",
|
|
168
150
|
})
|
|
169
151
|
);
|
|
170
152
|
|
|
@@ -180,12 +162,12 @@ describe("generate-i18n-types", () => {
|
|
|
180
162
|
expect(schema).toContain('"name": "string"');
|
|
181
163
|
});
|
|
182
164
|
|
|
183
|
-
it("generates
|
|
165
|
+
it("generates multi-mode dictionary schema with namespace validator", () => {
|
|
184
166
|
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
185
167
|
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
186
168
|
|
|
187
169
|
writeFileSync(
|
|
188
|
-
join(tempDir, "src/i18n/translations/
|
|
170
|
+
join(tempDir, "src/i18n/translations/default.json"),
|
|
189
171
|
JSON.stringify({
|
|
190
172
|
welcome: { en: "Welcome {name}!" },
|
|
191
173
|
})
|
|
@@ -193,13 +175,11 @@ describe("generate-i18n-types", () => {
|
|
|
193
175
|
writeFileSync(
|
|
194
176
|
join(tempDir, "i18n.codegen.json"),
|
|
195
177
|
JSON.stringify({
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
paramsTypeName: "AppParams",
|
|
202
|
-
schemaTypeName: "AppSchema",
|
|
178
|
+
namespaces: {
|
|
179
|
+
default: "src/i18n/translations/default.json",
|
|
180
|
+
},
|
|
181
|
+
projectName: "App",
|
|
182
|
+
codegenPath: "src/i18n",
|
|
203
183
|
})
|
|
204
184
|
);
|
|
205
185
|
|
|
@@ -207,49 +187,12 @@ describe("generate-i18n-types", () => {
|
|
|
207
187
|
expect(result.status).toBe(0);
|
|
208
188
|
|
|
209
189
|
const schema = readFileSync(join(tempDir, "src/i18n/dictionary-schema.generated.ts"), "utf8");
|
|
210
|
-
expect(schema).toContain("mode: '
|
|
190
|
+
expect(schema).toContain("mode: 'multi' as const");
|
|
211
191
|
expect(schema).toContain("validateExternalDictionaryPartial");
|
|
192
|
+
expect(schema).toContain("validateExternalNamespacePartial");
|
|
212
193
|
expect(schema).toContain("validateExternalKey");
|
|
213
194
|
});
|
|
214
195
|
|
|
215
|
-
it("generates flat types for single-file config", () => {
|
|
216
|
-
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
217
|
-
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
218
|
-
|
|
219
|
-
writeFileSync(
|
|
220
|
-
join(tempDir, "src/i18n/translations/translations.json"),
|
|
221
|
-
JSON.stringify({
|
|
222
|
-
login_button: { en: "Login" },
|
|
223
|
-
welcome: { en: "Welcome {name}!" },
|
|
224
|
-
})
|
|
225
|
-
);
|
|
226
|
-
writeFileSync(
|
|
227
|
-
join(tempDir, "i18n.codegen.json"),
|
|
228
|
-
JSON.stringify({
|
|
229
|
-
dictionary: "src/i18n/translations/translations.json",
|
|
230
|
-
typesOutput: "src/i18n/i18n-types.generated.ts",
|
|
231
|
-
dictionaryOutput: "src/i18n/dictionary.generated.ts",
|
|
232
|
-
instanceOutput: "src/i18n/instance.generated.ts",
|
|
233
|
-
paramsTypeName: "AppParams",
|
|
234
|
-
schemaTypeName: "AppSchema",
|
|
235
|
-
})
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
const result = runCodegen(tempDir);
|
|
239
|
-
expect(result.status).toBe(0);
|
|
240
|
-
|
|
241
|
-
const types = readFileSync(join(tempDir, "src/i18n/i18n-types.generated.ts"), "utf8");
|
|
242
|
-
const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
|
|
243
|
-
expect(types).toContain("export const I18N_MODE = 'single' as const");
|
|
244
|
-
expect(types).toContain("export type AppLocale = 'en'");
|
|
245
|
-
expect(factory).toContain("export function createI18n(");
|
|
246
|
-
expect(factory).toContain("export function projectDictionaryLocales(");
|
|
247
|
-
expect(factory).toContain("dictionary: AppSchema");
|
|
248
|
-
expect(factory).toContain("IcuTranslationProviderSingle");
|
|
249
|
-
expect(types).toContain("login_button: never;");
|
|
250
|
-
expect(types).toContain("welcome: { name: string };");
|
|
251
|
-
});
|
|
252
|
-
|
|
253
196
|
it("fails with a non-zero exit code on malformed ICU", () => {
|
|
254
197
|
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
255
198
|
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
@@ -263,12 +206,11 @@ describe("generate-i18n-types", () => {
|
|
|
263
206
|
writeFileSync(
|
|
264
207
|
join(tempDir, "i18n.codegen.json"),
|
|
265
208
|
JSON.stringify({
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
schemaTypeName: "AppSchema",
|
|
209
|
+
namespaces: {
|
|
210
|
+
default: "src/i18n/translations/translations.json",
|
|
211
|
+
},
|
|
212
|
+
projectName: "App",
|
|
213
|
+
codegenPath: "src/i18n",
|
|
272
214
|
})
|
|
273
215
|
);
|
|
274
216
|
|
|
@@ -290,12 +232,11 @@ describe("generate-i18n-types", () => {
|
|
|
290
232
|
writeFileSync(
|
|
291
233
|
join(tempDir, "i18n.codegen.json"),
|
|
292
234
|
JSON.stringify({
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
schemaTypeName: "AppSchema",
|
|
235
|
+
namespaces: {
|
|
236
|
+
default: "src/i18n/translations/translations.json",
|
|
237
|
+
},
|
|
238
|
+
projectName: "App",
|
|
239
|
+
codegenPath: "src/i18n",
|
|
299
240
|
localeFallback: {
|
|
300
241
|
en: null,
|
|
301
242
|
"de-CH": "en",
|
|
@@ -310,12 +251,12 @@ describe("generate-i18n-types", () => {
|
|
|
310
251
|
const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
|
|
311
252
|
expect(types).toContain("export const LOCALE_FALLBACK");
|
|
312
253
|
expect(types).toContain('"de-CH": "en"');
|
|
313
|
-
expect(types).toContain(
|
|
254
|
+
expect(types).toContain('export const AppLocales = ["de-CH", "en"] as const;');
|
|
255
|
+
expect(types).toContain("export type AppLocale = (typeof AppLocales)[number];");
|
|
314
256
|
expect(factory).toContain("localeFallback: LOCALE_FALLBACK");
|
|
315
|
-
expect(factory).toContain("...
|
|
316
|
-
expect(factory).toContain("projectNamespaceLocalesCore(dictionary, locales, LOCALE_FALLBACK)");
|
|
257
|
+
expect(factory).toContain("...providerOptions");
|
|
317
258
|
expect(factory).toContain(
|
|
318
|
-
"
|
|
259
|
+
"IcuTranslationProviderMulti<AppSchema, AppParams, AppLocale, typeof LOCALE_FALLBACK>"
|
|
319
260
|
);
|
|
320
261
|
});
|
|
321
262
|
|
|
@@ -332,12 +273,11 @@ describe("generate-i18n-types", () => {
|
|
|
332
273
|
writeFileSync(
|
|
333
274
|
join(tempDir, "i18n.codegen.json"),
|
|
334
275
|
JSON.stringify({
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
schemaTypeName: "AppSchema",
|
|
276
|
+
namespaces: {
|
|
277
|
+
default: "src/i18n/translations/translations.json",
|
|
278
|
+
},
|
|
279
|
+
projectName: "App",
|
|
280
|
+
codegenPath: "src/i18n",
|
|
341
281
|
localeFallback: {
|
|
342
282
|
en: null,
|
|
343
283
|
it: "en",
|
|
@@ -351,7 +291,8 @@ describe("generate-i18n-types", () => {
|
|
|
351
291
|
const types = readFileSync(join(tempDir, "src/i18n/i18n-types.generated.ts"), "utf8");
|
|
352
292
|
expect(types).toContain('"fr": null');
|
|
353
293
|
expect(types).toContain('"it": "en"');
|
|
354
|
-
expect(types).toContain(
|
|
294
|
+
expect(types).toContain('export const AppLocales = ["en", "fr", "it"] as const;');
|
|
295
|
+
expect(types).toContain("export type AppLocale = (typeof AppLocales)[number];");
|
|
355
296
|
});
|
|
356
297
|
|
|
357
298
|
it("does not emit LOCALE_FALLBACK when localeFallback is absent from config", () => {
|
|
@@ -367,12 +308,11 @@ describe("generate-i18n-types", () => {
|
|
|
367
308
|
writeFileSync(
|
|
368
309
|
join(tempDir, "i18n.codegen.json"),
|
|
369
310
|
JSON.stringify({
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
schemaTypeName: "AppSchema",
|
|
311
|
+
namespaces: {
|
|
312
|
+
default: "src/i18n/translations/translations.json",
|
|
313
|
+
},
|
|
314
|
+
projectName: "App",
|
|
315
|
+
codegenPath: "src/i18n",
|
|
376
316
|
})
|
|
377
317
|
);
|
|
378
318
|
|
|
@@ -380,9 +320,8 @@ describe("generate-i18n-types", () => {
|
|
|
380
320
|
expect(result.status).toBe(0);
|
|
381
321
|
|
|
382
322
|
const types = readFileSync(join(tempDir, "src/i18n/i18n-types.generated.ts"), "utf8");
|
|
383
|
-
const
|
|
384
|
-
expect(
|
|
385
|
-
expect(types).toContain("export type AppLocale = 'en' | 'it'");
|
|
323
|
+
expect(types).toContain('export const AppLocales = ["en", "it"] as const;');
|
|
324
|
+
expect(types).toContain("export type AppLocale = (typeof AppLocales)[number];");
|
|
386
325
|
});
|
|
387
326
|
|
|
388
327
|
it("fails on circular locale fallback in config", () => {
|
|
@@ -398,12 +337,11 @@ describe("generate-i18n-types", () => {
|
|
|
398
337
|
writeFileSync(
|
|
399
338
|
join(tempDir, "i18n.codegen.json"),
|
|
400
339
|
JSON.stringify({
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
schemaTypeName: "AppSchema",
|
|
340
|
+
namespaces: {
|
|
341
|
+
default: "src/i18n/translations/translations.json",
|
|
342
|
+
},
|
|
343
|
+
projectName: "App",
|
|
344
|
+
codegenPath: "src/i18n",
|
|
407
345
|
localeFallback: {
|
|
408
346
|
a: "b",
|
|
409
347
|
b: "a",
|
|
@@ -416,7 +354,7 @@ describe("generate-i18n-types", () => {
|
|
|
416
354
|
expect(result.stderr).toContain("Circular locale fallback");
|
|
417
355
|
});
|
|
418
356
|
|
|
419
|
-
it("generates
|
|
357
|
+
it("generates namespace loaders for all namespaces under split-by-locale", () => {
|
|
420
358
|
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
421
359
|
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
422
360
|
|
|
@@ -441,14 +379,8 @@ describe("generate-i18n-types", () => {
|
|
|
441
379
|
default: "src/i18n/translations/default.json",
|
|
442
380
|
billing: "src/i18n/translations/billing.json",
|
|
443
381
|
},
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
dictionaryOutput: "src/i18n/dictionary.generated.ts",
|
|
447
|
-
instanceOutput: "src/i18n/instance.generated.ts",
|
|
448
|
-
dictionarySchemaOutput: "src/i18n/dictionary-schema.generated.ts",
|
|
449
|
-
namespaceLoadersOutput: "src/i18n/namespace-loaders.generated.ts",
|
|
450
|
-
paramsTypeName: "AppParams",
|
|
451
|
-
schemaTypeName: "AppSchema",
|
|
382
|
+
projectName: "App",
|
|
383
|
+
codegenPath: "src/i18n",
|
|
452
384
|
})
|
|
453
385
|
);
|
|
454
386
|
|
|
@@ -456,82 +388,23 @@ describe("generate-i18n-types", () => {
|
|
|
456
388
|
expect(result.status).toBe(0);
|
|
457
389
|
|
|
458
390
|
const types = readFileSync(join(tempDir, "src/i18n/i18n-types.generated.ts"), "utf8");
|
|
459
|
-
const dictionary = readFileSync(join(tempDir, "src/i18n/dictionary.generated.ts"), "utf8");
|
|
460
391
|
const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
|
|
461
392
|
const loaders = readFileSync(join(tempDir, "src/i18n/namespace-loaders.generated.ts"), "utf8");
|
|
462
393
|
|
|
463
|
-
expect(types).toContain("export type
|
|
464
|
-
expect(types).toContain("export type
|
|
465
|
-
expect(
|
|
466
|
-
|
|
467
|
-
expect(factory).toContain("dictionary: InitialSchema,");
|
|
468
|
-
expect(loaders).toContain("export const namespaceLoaders");
|
|
469
|
-
expect(loaders).toContain("[K in LazyNamespace]: () => Promise<AppSchema[K]>");
|
|
470
|
-
expect(loaders).toContain("import('./translations/billing.json')");
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
it("keeps eager output when loadOnInit is omitted", () => {
|
|
474
|
-
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
475
|
-
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
476
|
-
|
|
477
|
-
writeFileSync(
|
|
478
|
-
join(tempDir, "src/i18n/translations/default.json"),
|
|
479
|
-
JSON.stringify({ welcome: { en: "Welcome {name}!" } })
|
|
480
|
-
);
|
|
481
|
-
writeFileSync(
|
|
482
|
-
join(tempDir, "src/i18n/translations/billing.json"),
|
|
483
|
-
JSON.stringify({ invoice_summary: { en: "Invoice" } })
|
|
484
|
-
);
|
|
485
|
-
writeFileSync(
|
|
486
|
-
join(tempDir, "i18n.codegen.json"),
|
|
487
|
-
JSON.stringify({
|
|
488
|
-
namespaces: {
|
|
489
|
-
default: "src/i18n/translations/default.json",
|
|
490
|
-
billing: "src/i18n/translations/billing.json",
|
|
491
|
-
},
|
|
492
|
-
typesOutput: "src/i18n/i18n-types.generated.ts",
|
|
493
|
-
dictionaryOutput: "src/i18n/dictionary.generated.ts",
|
|
494
|
-
instanceOutput: "src/i18n/instance.generated.ts",
|
|
495
|
-
paramsTypeName: "AppParams",
|
|
496
|
-
schemaTypeName: "AppSchema",
|
|
497
|
-
})
|
|
394
|
+
expect(types).toContain("export type LazyNamespace = 'default' | 'billing'");
|
|
395
|
+
expect(types).toContain("export type InitialSchema = Record<string, never>");
|
|
396
|
+
expect(factory).toContain(
|
|
397
|
+
"state?: { dictionary: InitialSchema; resources?: readonly (readonly [string, string])[] }"
|
|
498
398
|
);
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
expect(
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
|
|
505
|
-
|
|
506
|
-
expect(dictionary).toContain("export const defaultDictionary: AppSchema");
|
|
507
|
-
expect(dictionary).toContain("billingNs");
|
|
508
|
-
expect(factory).toContain("dictionary: AppSchema,");
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
it("fails when loadOnInit is used in single mode", () => {
|
|
512
|
-
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
513
|
-
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
514
|
-
|
|
515
|
-
writeFileSync(
|
|
516
|
-
join(tempDir, "src/i18n/translations/translations.json"),
|
|
517
|
-
JSON.stringify({ welcome: { en: "Welcome" } })
|
|
399
|
+
expect(factory).toContain("normalizeI18nCreateInput");
|
|
400
|
+
expect(factory).toContain("seedBuilderResources");
|
|
401
|
+
expect(loaders).toContain("export const namespaceLoaders");
|
|
402
|
+
expect(loaders).toContain(
|
|
403
|
+
"[K in LazyNamespace]: (locale: AppLocale) => Promise<AppSchema[K]>;"
|
|
518
404
|
);
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
JSON.stringify({
|
|
522
|
-
dictionary: "src/i18n/translations/translations.json",
|
|
523
|
-
loadOnInit: ["default"],
|
|
524
|
-
typesOutput: "src/i18n/i18n-types.generated.ts",
|
|
525
|
-
dictionaryOutput: "src/i18n/dictionary.generated.ts",
|
|
526
|
-
instanceOutput: "src/i18n/instance.generated.ts",
|
|
527
|
-
paramsTypeName: "AppParams",
|
|
528
|
-
schemaTypeName: "AppSchema",
|
|
529
|
-
})
|
|
405
|
+
expect(loaders).toContain(
|
|
406
|
+
"return import('./translations/billing.en.json').then((m) => m.default);"
|
|
530
407
|
);
|
|
531
|
-
|
|
532
|
-
const result = runCodegen(tempDir);
|
|
533
|
-
expect(result.status).not.toBe(0);
|
|
534
|
-
expect(result.stderr).toContain("loadOnInit");
|
|
535
408
|
});
|
|
536
409
|
|
|
537
410
|
it("infers number when English plural and Italian simple interpolation share a key", () => {
|
|
@@ -550,12 +423,11 @@ describe("generate-i18n-types", () => {
|
|
|
550
423
|
writeFileSync(
|
|
551
424
|
join(tempDir, "i18n.codegen.json"),
|
|
552
425
|
JSON.stringify({
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
schemaTypeName: "AppSchema",
|
|
426
|
+
namespaces: {
|
|
427
|
+
default: "src/i18n/translations/translations.json",
|
|
428
|
+
},
|
|
429
|
+
projectName: "App",
|
|
430
|
+
codegenPath: "src/i18n",
|
|
559
431
|
})
|
|
560
432
|
);
|
|
561
433
|
|
|
@@ -582,12 +454,11 @@ describe("generate-i18n-types", () => {
|
|
|
582
454
|
writeFileSync(
|
|
583
455
|
join(tempDir, "i18n.codegen.json"),
|
|
584
456
|
JSON.stringify({
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
schemaTypeName: "AppSchema",
|
|
457
|
+
namespaces: {
|
|
458
|
+
default: "src/i18n/translations/translations.json",
|
|
459
|
+
},
|
|
460
|
+
projectName: "App",
|
|
461
|
+
codegenPath: "src/i18n",
|
|
591
462
|
})
|
|
592
463
|
);
|
|
593
464
|
|
|
@@ -612,12 +483,11 @@ describe("generate-i18n-types", () => {
|
|
|
612
483
|
writeFileSync(
|
|
613
484
|
join(tempDir, "i18n.codegen.json"),
|
|
614
485
|
JSON.stringify({
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
schemaTypeName: "AppSchema",
|
|
486
|
+
namespaces: {
|
|
487
|
+
default: "src/i18n/translations/translations.json",
|
|
488
|
+
},
|
|
489
|
+
projectName: "App",
|
|
490
|
+
codegenPath: "src/i18n",
|
|
621
491
|
})
|
|
622
492
|
);
|
|
623
493
|
|
|
@@ -642,12 +512,11 @@ describe("generate-i18n-types", () => {
|
|
|
642
512
|
writeFileSync(
|
|
643
513
|
join(tempDir, "i18n.codegen.json"),
|
|
644
514
|
JSON.stringify({
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
schemaTypeName: "AppSchema",
|
|
515
|
+
namespaces: {
|
|
516
|
+
default: "src/i18n/translations/translations.json",
|
|
517
|
+
},
|
|
518
|
+
projectName: "App",
|
|
519
|
+
codegenPath: "src/i18n",
|
|
651
520
|
})
|
|
652
521
|
);
|
|
653
522
|
|
|
@@ -670,11 +539,8 @@ describe("generate-i18n-types", () => {
|
|
|
670
539
|
namespaces: {
|
|
671
540
|
default: "src/i18n/translations/default.json",
|
|
672
541
|
},
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
instanceOutput: "src/i18n/instance.generated.ts",
|
|
676
|
-
paramsTypeName: "AppParams",
|
|
677
|
-
schemaTypeName: "AppSchema",
|
|
542
|
+
projectName: "App",
|
|
543
|
+
codegenPath: "src/i18n",
|
|
678
544
|
})
|
|
679
545
|
);
|
|
680
546
|
|
|
@@ -699,108 +565,22 @@ describe("generate-i18n-types", () => {
|
|
|
699
565
|
writeFileSync(
|
|
700
566
|
join(tempDir, "i18n.codegen.json"),
|
|
701
567
|
JSON.stringify({
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
schemaTypeName: "AppSchema",
|
|
568
|
+
namespaces: {
|
|
569
|
+
default: "src/i18n/translations/translations.json",
|
|
570
|
+
},
|
|
571
|
+
projectName: "App",
|
|
572
|
+
codegenPath: "src/i18n",
|
|
708
573
|
})
|
|
709
574
|
);
|
|
710
575
|
|
|
711
576
|
const result = runCodegen(tempDir);
|
|
712
577
|
expect(result.status).toBe(0);
|
|
713
578
|
|
|
714
|
-
const dictionary = readFileSync(join(tempDir, "src/i18n/dictionary.generated.ts"), "utf8");
|
|
715
579
|
const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
|
|
716
|
-
|
|
580
|
+
const loaders = readFileSync(join(tempDir, "src/i18n/namespace-loaders.generated.ts"), "utf8");
|
|
717
581
|
expect(factory).toContain("from './i18n-types.generated'");
|
|
718
|
-
expect(factory).toContain("dictionary:
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
it("supports importExtension .ts", () => {
|
|
722
|
-
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
723
|
-
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
724
|
-
|
|
725
|
-
writeFileSync(
|
|
726
|
-
join(tempDir, "src/i18n/translations/translations.json"),
|
|
727
|
-
JSON.stringify({ welcome: { en: "Welcome {name}!" } })
|
|
728
|
-
);
|
|
729
|
-
writeFileSync(
|
|
730
|
-
join(tempDir, "i18n.codegen.json"),
|
|
731
|
-
JSON.stringify({
|
|
732
|
-
dictionary: "src/i18n/translations/translations.json",
|
|
733
|
-
typesOutput: "src/i18n/i18n-types.generated.ts",
|
|
734
|
-
dictionaryOutput: "src/i18n/dictionary.generated.ts",
|
|
735
|
-
instanceOutput: "src/i18n/instance.generated.ts",
|
|
736
|
-
importExtension: ".ts",
|
|
737
|
-
paramsTypeName: "AppParams",
|
|
738
|
-
schemaTypeName: "AppSchema",
|
|
739
|
-
})
|
|
740
|
-
);
|
|
741
|
-
|
|
742
|
-
const result = runCodegen(tempDir);
|
|
743
|
-
expect(result.status).toBe(0);
|
|
744
|
-
|
|
745
|
-
const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
|
|
746
|
-
expect(factory).toContain("from './i18n-types.generated.ts'");
|
|
747
|
-
});
|
|
748
|
-
|
|
749
|
-
it("supports importExtension .js for NodeNext ESM projects", () => {
|
|
750
|
-
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
751
|
-
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
752
|
-
|
|
753
|
-
writeFileSync(
|
|
754
|
-
join(tempDir, "src/i18n/translations/translations.json"),
|
|
755
|
-
JSON.stringify({ welcome: { en: "Welcome {name}!" } })
|
|
756
|
-
);
|
|
757
|
-
writeFileSync(
|
|
758
|
-
join(tempDir, "i18n.codegen.json"),
|
|
759
|
-
JSON.stringify({
|
|
760
|
-
dictionary: "src/i18n/translations/translations.json",
|
|
761
|
-
typesOutput: "src/i18n/i18n-types.generated.ts",
|
|
762
|
-
dictionaryOutput: "src/i18n/dictionary.generated.ts",
|
|
763
|
-
instanceOutput: "src/i18n/instance.generated.ts",
|
|
764
|
-
importExtension: ".js",
|
|
765
|
-
paramsTypeName: "AppParams",
|
|
766
|
-
schemaTypeName: "AppSchema",
|
|
767
|
-
})
|
|
768
|
-
);
|
|
769
|
-
|
|
770
|
-
const result = runCodegen(tempDir);
|
|
771
|
-
expect(result.status).toBe(0);
|
|
772
|
-
|
|
773
|
-
const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
|
|
774
|
-
expect(factory).toContain("from './i18n-types.generated.js'");
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
it("fails when importExtension is invalid", () => {
|
|
778
|
-
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
779
|
-
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
780
|
-
|
|
781
|
-
writeFileSync(
|
|
782
|
-
join(tempDir, "src/i18n/translations/translations.json"),
|
|
783
|
-
JSON.stringify({ welcome: { en: "Welcome {name}!" } })
|
|
784
|
-
);
|
|
785
|
-
writeFileSync(
|
|
786
|
-
join(tempDir, "i18n.codegen.json"),
|
|
787
|
-
JSON.stringify({
|
|
788
|
-
dictionary: "src/i18n/translations/translations.json",
|
|
789
|
-
typesOutput: "src/i18n/i18n-types.generated.ts",
|
|
790
|
-
dictionaryOutput: "src/i18n/dictionary.generated.ts",
|
|
791
|
-
instanceOutput: "src/i18n/instance.generated.ts",
|
|
792
|
-
importExtension: ".mjs",
|
|
793
|
-
paramsTypeName: "AppParams",
|
|
794
|
-
schemaTypeName: "AppSchema",
|
|
795
|
-
})
|
|
796
|
-
);
|
|
797
|
-
|
|
798
|
-
const result = runCodegen(tempDir);
|
|
799
|
-
expect(result.status).not.toBe(0);
|
|
800
|
-
expect(result.stderr).toContain("Invalid i18n.codegen.json");
|
|
801
|
-
expect(result.stderr).toContain("importExtension");
|
|
802
|
-
expect(result.stderr).toContain('one of "none"|".ts"|".js"');
|
|
803
|
-
expect(result.stderr).toContain("Allowed keys:");
|
|
582
|
+
expect(factory).toContain("dictionary: InitialSchema");
|
|
583
|
+
expect(loaders).toContain("from './i18n-types.generated'");
|
|
804
584
|
});
|
|
805
585
|
|
|
806
586
|
it("compiles yaml dictionaries to json and generates json imports", () => {
|
|
@@ -822,33 +602,34 @@ describe("generate-i18n-types", () => {
|
|
|
822
602
|
writeFileSync(
|
|
823
603
|
join(tempDir, "i18n.codegen.json"),
|
|
824
604
|
JSON.stringify({
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
schemaTypeName: "AppSchema",
|
|
605
|
+
namespaces: {
|
|
606
|
+
default: "src/i18n/translations/translations.yaml",
|
|
607
|
+
},
|
|
608
|
+
projectName: "App",
|
|
609
|
+
codegenPath: "src/i18n/generated",
|
|
831
610
|
})
|
|
832
611
|
);
|
|
833
612
|
|
|
834
613
|
const result = runCodegen(tempDir);
|
|
835
614
|
expect(result.status).toBe(0);
|
|
836
615
|
expect(result.stdout).toContain(
|
|
837
|
-
"Compiled: src/i18n/translations/translations.yaml → src/i18n/generated/translations/translations.json"
|
|
616
|
+
"Compiled: src/i18n/translations/translations.yaml → src/i18n/generated/translations/translations.en.json"
|
|
838
617
|
);
|
|
839
618
|
|
|
840
619
|
const compiled = JSON.parse(
|
|
841
|
-
readFileSync(join(tempDir, "src/i18n/generated/translations/translations.json"), "utf8")
|
|
620
|
+
readFileSync(join(tempDir, "src/i18n/generated/translations/translations.en.json"), "utf8")
|
|
842
621
|
) as Record<string, Record<string, string>>;
|
|
843
622
|
expect(compiled.welcome?.en).toBe("Line one\nLine two\n");
|
|
844
623
|
|
|
845
|
-
const
|
|
846
|
-
|
|
624
|
+
const types = readFileSync(join(tempDir, "src/i18n/generated/i18n-types.generated.ts"), "utf8");
|
|
625
|
+
const loaders = readFileSync(
|
|
626
|
+
join(tempDir, "src/i18n/generated/namespace-loaders.generated.ts"),
|
|
847
627
|
"utf8"
|
|
848
628
|
);
|
|
849
|
-
const types = readFileSync(join(tempDir, "src/i18n/generated/i18n-types.generated.ts"), "utf8");
|
|
850
|
-
expect(dictionary).toContain("from './translations/translations.json'");
|
|
851
629
|
expect(types).toContain("welcome: Partial<Record<AppLocale, string>>;");
|
|
630
|
+
expect(loaders).toContain(
|
|
631
|
+
"return import('./translations/translations.en.json').then((m) => m.default);"
|
|
632
|
+
);
|
|
852
633
|
});
|
|
853
634
|
|
|
854
635
|
it("supports mixed json and yaml namespaces", () => {
|
|
@@ -875,26 +656,27 @@ describe("generate-i18n-types", () => {
|
|
|
875
656
|
default: "src/i18n/translations/default.json",
|
|
876
657
|
billing: "src/i18n/translations/billing.yaml",
|
|
877
658
|
},
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
instanceOutput: "src/i18n/generated/instance.generated.ts",
|
|
881
|
-
paramsTypeName: "AppParams",
|
|
882
|
-
schemaTypeName: "AppSchema",
|
|
659
|
+
projectName: "App",
|
|
660
|
+
codegenPath: "src/i18n/generated",
|
|
883
661
|
})
|
|
884
662
|
);
|
|
885
663
|
|
|
886
664
|
const result = runCodegen(tempDir);
|
|
887
665
|
expect(result.status).toBe(0);
|
|
888
666
|
|
|
889
|
-
const
|
|
890
|
-
|
|
667
|
+
const types = readFileSync(join(tempDir, "src/i18n/generated/i18n-types.generated.ts"), "utf8");
|
|
668
|
+
const loaders = readFileSync(
|
|
669
|
+
join(tempDir, "src/i18n/generated/namespace-loaders.generated.ts"),
|
|
891
670
|
"utf8"
|
|
892
671
|
);
|
|
893
|
-
const types = readFileSync(join(tempDir, "src/i18n/generated/i18n-types.generated.ts"), "utf8");
|
|
894
|
-
expect(dictionary).toContain("from '../translations/default.json'");
|
|
895
|
-
expect(dictionary).toContain("from './translations/billing.json'");
|
|
896
672
|
expect(types).toContain("login_button: Partial<Record<AppLocale, string>>;");
|
|
897
673
|
expect(types).toContain("invoice_summary: Partial<Record<AppLocale, string>>;");
|
|
674
|
+
expect(loaders).toContain(
|
|
675
|
+
"return import('./translations/billing.en.json').then((m) => m.default);"
|
|
676
|
+
);
|
|
677
|
+
expect(loaders).toContain(
|
|
678
|
+
"return import('./translations/default.en.json').then((m) => m.default);"
|
|
679
|
+
);
|
|
898
680
|
});
|
|
899
681
|
|
|
900
682
|
it("generates lazy loaders that import compiled json for yaml namespaces", () => {
|
|
@@ -921,14 +703,8 @@ describe("generate-i18n-types", () => {
|
|
|
921
703
|
default: "src/i18n/translations/default.json",
|
|
922
704
|
billing: "src/i18n/translations/billing.yaml",
|
|
923
705
|
},
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
dictionaryOutput: "src/i18n/generated/dictionary.generated.ts",
|
|
927
|
-
instanceOutput: "src/i18n/generated/instance.generated.ts",
|
|
928
|
-
dictionarySchemaOutput: "src/i18n/generated/dictionary-schema.generated.ts",
|
|
929
|
-
namespaceLoadersOutput: "src/i18n/generated/namespace-loaders.generated.ts",
|
|
930
|
-
paramsTypeName: "AppParams",
|
|
931
|
-
schemaTypeName: "AppSchema",
|
|
706
|
+
projectName: "App",
|
|
707
|
+
codegenPath: "src/i18n/generated",
|
|
932
708
|
})
|
|
933
709
|
);
|
|
934
710
|
|
|
@@ -939,7 +715,9 @@ describe("generate-i18n-types", () => {
|
|
|
939
715
|
join(tempDir, "src/i18n/generated/namespace-loaders.generated.ts"),
|
|
940
716
|
"utf8"
|
|
941
717
|
);
|
|
942
|
-
expect(loaders).toContain(
|
|
718
|
+
expect(loaders).toContain(
|
|
719
|
+
"return import('./translations/billing.en.json').then((m) => m.default);"
|
|
720
|
+
);
|
|
943
721
|
});
|
|
944
722
|
|
|
945
723
|
it("fails when dictionary extension is unsupported", () => {
|
|
@@ -950,12 +728,11 @@ describe("generate-i18n-types", () => {
|
|
|
950
728
|
writeFileSync(
|
|
951
729
|
join(tempDir, "i18n.codegen.json"),
|
|
952
730
|
JSON.stringify({
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
schemaTypeName: "AppSchema",
|
|
731
|
+
namespaces: {
|
|
732
|
+
default: "src/i18n/translations/translations.toml",
|
|
733
|
+
},
|
|
734
|
+
projectName: "App",
|
|
735
|
+
codegenPath: "src/i18n",
|
|
959
736
|
})
|
|
960
737
|
);
|
|
961
738
|
|
|
@@ -964,7 +741,7 @@ describe("generate-i18n-types", () => {
|
|
|
964
741
|
expect(result.stderr).toMatch(/unsupported dictionary (extension|format)/i);
|
|
965
742
|
});
|
|
966
743
|
|
|
967
|
-
it("generates split-by-locale delivery with per-locale files and
|
|
744
|
+
it("generates split-by-locale delivery with per-locale files and namespace loaders", () => {
|
|
968
745
|
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
969
746
|
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
970
747
|
mkdirSync(join(tempDir, "src/i18n/generated"), { recursive: true });
|
|
@@ -999,12 +776,8 @@ describe("generate-i18n-types", () => {
|
|
|
999
776
|
billing: "src/i18n/translations/billing.json",
|
|
1000
777
|
},
|
|
1001
778
|
delivery: "split-by-locale",
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
instanceOutput: "src/i18n/generated/instance.generated.ts",
|
|
1005
|
-
namespaceLoadersOutput: "src/i18n/generated/namespace-loaders.generated.ts",
|
|
1006
|
-
paramsTypeName: "AppParams",
|
|
1007
|
-
schemaTypeName: "AppSchema",
|
|
779
|
+
projectName: "App",
|
|
780
|
+
codegenPath: "src/i18n/generated",
|
|
1008
781
|
})
|
|
1009
782
|
);
|
|
1010
783
|
|
|
@@ -1034,14 +807,12 @@ describe("generate-i18n-types", () => {
|
|
|
1034
807
|
"utf8"
|
|
1035
808
|
);
|
|
1036
809
|
|
|
1037
|
-
expect(existsSync(join(tempDir, "src/i18n/generated/dictionary.generated.ts"))).toBe(false);
|
|
1038
|
-
|
|
1039
810
|
expect(types).toContain("welcome: Partial<Record<AppLocale, string>>;");
|
|
1040
811
|
expect(types).toContain("invoice_summary: Partial<Record<AppLocale, string>>;");
|
|
1041
|
-
expect(types).toContain(
|
|
812
|
+
expect(types).toContain('export const AppLocales = ["en", "it"] as const;');
|
|
813
|
+
expect(types).toContain("export type AppLocale = (typeof AppLocales)[number];");
|
|
1042
814
|
expect(types).toContain("invoice_summary: { count: number }");
|
|
1043
815
|
|
|
1044
|
-
expect(types).toContain("export type LoadOnInitNamespace = never;");
|
|
1045
816
|
expect(types).toContain("export type LazyNamespace = 'default' | 'user' | 'billing';");
|
|
1046
817
|
|
|
1047
818
|
expect(loaders).toContain(
|
|
@@ -1061,42 +832,7 @@ describe("generate-i18n-types", () => {
|
|
|
1061
832
|
expect(loaders).toContain("user: (locale) => {");
|
|
1062
833
|
});
|
|
1063
834
|
|
|
1064
|
-
it("
|
|
1065
|
-
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
1066
|
-
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
1067
|
-
mkdirSync(join(tempDir, "src/i18n/generated"), { recursive: true });
|
|
1068
|
-
|
|
1069
|
-
writeFileSync(
|
|
1070
|
-
join(tempDir, "src/i18n/translations/default.json"),
|
|
1071
|
-
JSON.stringify({
|
|
1072
|
-
welcome: { en: "Welcome {name}!", it: "Benvenuto {name}!" },
|
|
1073
|
-
})
|
|
1074
|
-
);
|
|
1075
|
-
writeFileSync(
|
|
1076
|
-
join(tempDir, "src/i18n/generated/dictionary.generated.ts"),
|
|
1077
|
-
"// stale dictionary manifest\n"
|
|
1078
|
-
);
|
|
1079
|
-
writeFileSync(
|
|
1080
|
-
join(tempDir, "i18n.codegen.json"),
|
|
1081
|
-
JSON.stringify({
|
|
1082
|
-
namespaces: {
|
|
1083
|
-
default: "src/i18n/translations/default.json",
|
|
1084
|
-
},
|
|
1085
|
-
delivery: "split-by-locale",
|
|
1086
|
-
typesOutput: "src/i18n/generated/i18n-types.generated.ts",
|
|
1087
|
-
instanceOutput: "src/i18n/generated/instance.generated.ts",
|
|
1088
|
-
namespaceLoadersOutput: "src/i18n/generated/namespace-loaders.generated.ts",
|
|
1089
|
-
paramsTypeName: "AppParams",
|
|
1090
|
-
schemaTypeName: "AppSchema",
|
|
1091
|
-
})
|
|
1092
|
-
);
|
|
1093
|
-
|
|
1094
|
-
const result = runCodegen(tempDir);
|
|
1095
|
-
expect(result.status).toBe(0);
|
|
1096
|
-
expect(existsSync(join(tempDir, "src/i18n/generated/dictionary.generated.ts"))).toBe(false);
|
|
1097
|
-
});
|
|
1098
|
-
|
|
1099
|
-
it("writes delivery json under deliveryOutput when it differs from typesOutput directory", () => {
|
|
835
|
+
it("writes delivery json under artifactsPath when it differs from codegenPath", () => {
|
|
1100
836
|
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
1101
837
|
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
1102
838
|
mkdirSync(join(tempDir, "src/i18n/generated"), { recursive: true });
|
|
@@ -1122,13 +858,9 @@ describe("generate-i18n-types", () => {
|
|
|
1122
858
|
billing: "src/i18n/translations/billing.yaml",
|
|
1123
859
|
},
|
|
1124
860
|
delivery: "split-by-locale",
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
instanceOutput: "src/i18n/generated/instance.generated.ts",
|
|
1129
|
-
namespaceLoadersOutput: "src/i18n/generated/namespace-loaders.generated.ts",
|
|
1130
|
-
paramsTypeName: "AppParams",
|
|
1131
|
-
schemaTypeName: "AppSchema",
|
|
861
|
+
artifactsPath: "public/i18n",
|
|
862
|
+
projectName: "App",
|
|
863
|
+
codegenPath: "src/i18n/generated",
|
|
1132
864
|
})
|
|
1133
865
|
);
|
|
1134
866
|
|
|
@@ -1150,8 +882,6 @@ describe("generate-i18n-types", () => {
|
|
|
1150
882
|
join(tempDir, "src/i18n/generated/namespace-loaders.generated.ts"),
|
|
1151
883
|
"utf8"
|
|
1152
884
|
);
|
|
1153
|
-
|
|
1154
|
-
expect(existsSync(join(tempDir, "src/i18n/generated/dictionary.generated.ts"))).toBe(false);
|
|
1155
885
|
expect(loaders).toContain("export const defaultLazyNamespaces");
|
|
1156
886
|
expect(loaders).toContain(
|
|
1157
887
|
"return import('../../../public/i18n/translations/default.en.json').then((m) => m.default);"
|
|
@@ -1188,11 +918,8 @@ describe("generate-i18n-types", () => {
|
|
|
1188
918
|
"de-CH": "de-DE",
|
|
1189
919
|
it: "en",
|
|
1190
920
|
},
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
instanceOutput: "src/i18n/generated/instance.generated.ts",
|
|
1194
|
-
paramsTypeName: "AppParams",
|
|
1195
|
-
schemaTypeName: "AppSchema",
|
|
921
|
+
projectName: "App",
|
|
922
|
+
codegenPath: "src/i18n/generated",
|
|
1196
923
|
})
|
|
1197
924
|
);
|
|
1198
925
|
|
|
@@ -1208,48 +935,8 @@ describe("generate-i18n-types", () => {
|
|
|
1208
935
|
});
|
|
1209
936
|
|
|
1210
937
|
const types = readFileSync(join(tempDir, "src/i18n/generated/i18n-types.generated.ts"), "utf8");
|
|
1211
|
-
expect(types).toContain(
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
it("keeps canonical output when delivery is explicitly set to canonical", () => {
|
|
1215
|
-
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
1216
|
-
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
1217
|
-
|
|
1218
|
-
writeFileSync(
|
|
1219
|
-
join(tempDir, "src/i18n/translations/default.json"),
|
|
1220
|
-
JSON.stringify({ welcome: { en: "Welcome {name}!" } })
|
|
1221
|
-
);
|
|
1222
|
-
writeFileSync(
|
|
1223
|
-
join(tempDir, "src/i18n/translations/billing.json"),
|
|
1224
|
-
JSON.stringify({ invoice_summary: { en: "Invoice" } })
|
|
1225
|
-
);
|
|
1226
|
-
writeFileSync(
|
|
1227
|
-
join(tempDir, "i18n.codegen.json"),
|
|
1228
|
-
JSON.stringify({
|
|
1229
|
-
namespaces: {
|
|
1230
|
-
default: "src/i18n/translations/default.json",
|
|
1231
|
-
billing: "src/i18n/translations/billing.json",
|
|
1232
|
-
},
|
|
1233
|
-
loadOnInit: ["default"],
|
|
1234
|
-
delivery: "canonical",
|
|
1235
|
-
typesOutput: "src/i18n/i18n-types.generated.ts",
|
|
1236
|
-
dictionaryOutput: "src/i18n/dictionary.generated.ts",
|
|
1237
|
-
instanceOutput: "src/i18n/instance.generated.ts",
|
|
1238
|
-
namespaceLoadersOutput: "src/i18n/namespace-loaders.generated.ts",
|
|
1239
|
-
paramsTypeName: "AppParams",
|
|
1240
|
-
schemaTypeName: "AppSchema",
|
|
1241
|
-
})
|
|
1242
|
-
);
|
|
1243
|
-
|
|
1244
|
-
const result = runCodegen(tempDir);
|
|
1245
|
-
expect(result.status).toBe(0);
|
|
1246
|
-
|
|
1247
|
-
const dictionary = readFileSync(join(tempDir, "src/i18n/dictionary.generated.ts"), "utf8");
|
|
1248
|
-
const loaders = readFileSync(join(tempDir, "src/i18n/namespace-loaders.generated.ts"), "utf8");
|
|
1249
|
-
|
|
1250
|
-
expect(dictionary).toContain("export const defaultDictionary: InitialSchema");
|
|
1251
|
-
expect(loaders).toContain("[K in LazyNamespace]: () => Promise<AppSchema[K]>");
|
|
1252
|
-
expect(loaders).toContain("import('./translations/billing.json')");
|
|
938
|
+
expect(types).toContain('export const AppLocales = ["de-CH", "de-DE", "en", "it"] as const;');
|
|
939
|
+
expect(types).toContain("export type AppLocale = (typeof AppLocales)[number];");
|
|
1253
940
|
});
|
|
1254
941
|
|
|
1255
942
|
it("generates custom delivery with per-area json files", () => {
|
|
@@ -1290,12 +977,8 @@ describe("generate-i18n-types", () => {
|
|
|
1290
977
|
it: "en-US",
|
|
1291
978
|
fr: null,
|
|
1292
979
|
},
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
instanceOutput: "src/i18n/generated/instance.generated.ts",
|
|
1296
|
-
namespaceLoadersOutput: "src/i18n/generated/namespace-loaders.generated.ts",
|
|
1297
|
-
paramsTypeName: "AppParams",
|
|
1298
|
-
schemaTypeName: "AppSchema",
|
|
980
|
+
projectName: "App",
|
|
981
|
+
codegenPath: "src/i18n/generated",
|
|
1299
982
|
})
|
|
1300
983
|
);
|
|
1301
984
|
|
|
@@ -1349,7 +1032,8 @@ describe("generate-i18n-types", () => {
|
|
|
1349
1032
|
readFileSync(join(tempDir, "src/i18n/generated/translations/billing.json"), "utf8")
|
|
1350
1033
|
).toThrow();
|
|
1351
1034
|
|
|
1352
|
-
expect(types).toContain(
|
|
1035
|
+
expect(types).toContain('export const AppDeliveryAreas = ["eu", "us"] as const;');
|
|
1036
|
+
expect(types).toContain("export type AppDeliveryArea = (typeof AppDeliveryAreas)[number];");
|
|
1353
1037
|
expect(types).toContain("export const DELIVERY_ARTIFACTS = {");
|
|
1354
1038
|
expect(types).toContain('"eu": ["fr", "it"] as const');
|
|
1355
1039
|
expect(types).toContain('"us": ["en-US"] as const');
|
|
@@ -1360,16 +1044,14 @@ describe("generate-i18n-types", () => {
|
|
|
1360
1044
|
expect(types).toContain('"fr": "eu"');
|
|
1361
1045
|
expect(types).toContain('"en-US": "us"');
|
|
1362
1046
|
expect(types).toContain("} as const satisfies Record<AppLocale, AppDeliveryArea>;");
|
|
1363
|
-
expect(types).toContain(
|
|
1047
|
+
expect(types).toContain('export const AppLocales = ["en-US", "fr", "it"] as const;');
|
|
1048
|
+
expect(types).toContain("export type AppLocale = (typeof AppLocales)[number];");
|
|
1364
1049
|
expect(types).toContain("export const LOCALE_FALLBACK");
|
|
1365
1050
|
expect(types).toContain('"it": "en-US"');
|
|
1366
1051
|
expect(types).toContain("some_key: Partial<Record<AppLocale, string>>;");
|
|
1367
1052
|
expect(types).toContain("invoice_summary: Partial<Record<AppLocale, string>>;");
|
|
1368
1053
|
expect(types).toContain("invoice_summary: { count: number }");
|
|
1369
1054
|
|
|
1370
|
-
expect(existsSync(join(tempDir, "src/i18n/generated/dictionary.generated.ts"))).toBe(false);
|
|
1371
|
-
|
|
1372
|
-
expect(types).toContain("export type LoadOnInitNamespace = never;");
|
|
1373
1055
|
expect(types).toContain("export type LazyNamespace = 'default' | 'billing';");
|
|
1374
1056
|
|
|
1375
1057
|
expect(loaders).toContain(
|
|
@@ -1387,14 +1069,7 @@ describe("generate-i18n-types", () => {
|
|
|
1387
1069
|
"return import('./translations/billing.us.json').then((m) => m.default);"
|
|
1388
1070
|
);
|
|
1389
1071
|
|
|
1390
|
-
expect(factory).toContain("
|
|
1391
|
-
expect(factory).toContain("export function projectNamespaceForDeliveryArea(");
|
|
1392
|
-
expect(factory).toContain(
|
|
1393
|
-
"projectDictionaryForDeliveryAreaCore(dictionary, areaLocales, LOCALE_FALLBACK)"
|
|
1394
|
-
);
|
|
1395
|
-
expect(factory).toContain(
|
|
1396
|
-
"projectNamespaceForDeliveryAreaCore(dictionary, areaLocales, LOCALE_FALLBACK)"
|
|
1397
|
-
);
|
|
1072
|
+
expect(factory).toContain("partitionForLocale: (locale) => LOCALE_DELIVERY_AREA[locale]");
|
|
1398
1073
|
});
|
|
1399
1074
|
|
|
1400
1075
|
it("fails when deliveryArtifacts does not partition request locales", () => {
|
|
@@ -1423,11 +1098,8 @@ describe("generate-i18n-types", () => {
|
|
|
1423
1098
|
it: "en-US",
|
|
1424
1099
|
fr: null,
|
|
1425
1100
|
},
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
instanceOutput: "src/i18n/generated/instance.generated.ts",
|
|
1429
|
-
paramsTypeName: "AppParams",
|
|
1430
|
-
schemaTypeName: "AppSchema",
|
|
1101
|
+
projectName: "App",
|
|
1102
|
+
codegenPath: "src/i18n/generated",
|
|
1431
1103
|
})
|
|
1432
1104
|
);
|
|
1433
1105
|
|
|
@@ -1450,12 +1122,11 @@ describe("generate-i18n-types", () => {
|
|
|
1450
1122
|
writeFileSync(
|
|
1451
1123
|
join(tempDir, "i18n.codegen.json"),
|
|
1452
1124
|
JSON.stringify({
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
schemaTypeName: "AppSchema",
|
|
1125
|
+
namespaces: {
|
|
1126
|
+
default: "src/i18n/translations/translations.yaml",
|
|
1127
|
+
},
|
|
1128
|
+
projectName: "App",
|
|
1129
|
+
codegenPath: "src/i18n",
|
|
1459
1130
|
})
|
|
1460
1131
|
);
|
|
1461
1132
|
|