@xndrjs/i18n 0.8.0 → 0.8.2-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.
Files changed (94) hide show
  1. package/README.md +1 -2
  2. package/dist/cli/audit.js +739 -0
  3. package/dist/cli/audit.js.map +1 -0
  4. package/dist/cli/codegen.js +1471 -0
  5. package/dist/cli/codegen.js.map +1 -0
  6. package/dist/cli/setup.js +148 -0
  7. package/dist/cli/setup.js.map +1 -0
  8. package/dist/codegen/index.js +43 -40
  9. package/dist/codegen/index.js.map +1 -1
  10. package/dist/index.d.ts +20 -1
  11. package/dist/index.js +56 -10
  12. package/dist/index.js.map +1 -1
  13. package/package.json +6 -9
  14. package/bin/audit.mjs +0 -14
  15. package/bin/codegen.mjs +0 -17
  16. package/bin/setup.mjs +0 -14
  17. package/src/IcuTranslationProviderMulti.test.ts +0 -215
  18. package/src/IcuTranslationProviderMulti.ts +0 -176
  19. package/src/audit/audit-dictionaries.test.ts +0 -134
  20. package/src/audit/audit-dictionaries.ts +0 -178
  21. package/src/audit/run-audit.test.ts +0 -169
  22. package/src/audit/run-audit.ts +0 -94
  23. package/src/builder-load-registry.ts +0 -39
  24. package/src/builder-loaders.ts +0 -15
  25. package/src/builder-types.test.ts +0 -49
  26. package/src/builder-types.ts +0 -24
  27. package/src/builder.test.ts +0 -260
  28. package/src/builder.ts +0 -10
  29. package/src/codegen/codegen-config-schema.ts +0 -156
  30. package/src/codegen/config.test.ts +0 -231
  31. package/src/codegen/config.ts +0 -42
  32. package/src/codegen/constants.ts +0 -9
  33. package/src/codegen/delivery-artifacts.test.ts +0 -167
  34. package/src/codegen/delivery-artifacts.ts +0 -154
  35. package/src/codegen/dictionary-spec-contract.test.ts +0 -28
  36. package/src/codegen/dictionary-spec-contract.ts +0 -138
  37. package/src/codegen/emit/dictionary-schema-file.ts +0 -113
  38. package/src/codegen/emit/instance-file.test.ts +0 -71
  39. package/src/codegen/emit/instance-file.ts +0 -257
  40. package/src/codegen/emit/namespace-loaders-file.test.ts +0 -251
  41. package/src/codegen/emit/namespace-loaders-file.ts +0 -229
  42. package/src/codegen/emit/types-file.test.ts +0 -96
  43. package/src/codegen/emit/types-file.ts +0 -142
  44. package/src/codegen/fixtures/billing-namespace.json +0 -6
  45. package/src/codegen/generate-i18n-types.test.ts +0 -1137
  46. package/src/codegen/generate-i18n-types.ts +0 -20
  47. package/src/codegen/icu-analysis.ts +0 -110
  48. package/src/codegen/locale-fallback.ts +0 -73
  49. package/src/codegen/locale-policy.test.ts +0 -35
  50. package/src/codegen/locale-policy.ts +0 -30
  51. package/src/codegen/paths.ts +0 -44
  52. package/src/codegen/project-locales-set-namespace.test.ts +0 -134
  53. package/src/codegen/read-dictionary.test.ts +0 -594
  54. package/src/codegen/read-dictionary.ts +0 -260
  55. package/src/codegen/regenerate-namespaces.ts +0 -180
  56. package/src/codegen/run-codegen.test.ts +0 -230
  57. package/src/codegen/run-codegen.ts +0 -252
  58. package/src/codegen/types.ts +0 -10
  59. package/src/codegen/write-file-if-changed.test.ts +0 -42
  60. package/src/codegen/write-file-if-changed.ts +0 -20
  61. package/src/codegen-config/build-config.ts +0 -15
  62. package/src/codegen-config/codegen-config.test.ts +0 -36
  63. package/src/codegen-config/index.ts +0 -37
  64. package/src/codegen-config/type-names.ts +0 -20
  65. package/src/codegen-config/write-config.ts +0 -8
  66. package/src/engine.ts +0 -47
  67. package/src/fetch-artifact.ts +0 -16
  68. package/src/format-core.ts +0 -91
  69. package/src/i18n-handle.ts +0 -202
  70. package/src/icu/extract-variables.test.ts +0 -199
  71. package/src/icu/extract-variables.ts +0 -227
  72. package/src/icu/parse-template.ts +0 -31
  73. package/src/index.ts +0 -58
  74. package/src/project-locales.test.ts +0 -280
  75. package/src/project-locales.ts +0 -211
  76. package/src/resolve-locale.test.ts +0 -91
  77. package/src/resolve-locale.ts +0 -88
  78. package/src/scope-multi.ts +0 -105
  79. package/src/scope-types.ts +0 -35
  80. package/src/scope.test.ts +0 -172
  81. package/src/serialized-state.test.ts +0 -114
  82. package/src/serialized-state.ts +0 -28
  83. package/src/setup/setup-i18n.test.ts +0 -85
  84. package/src/setup/setup-i18n.ts +0 -156
  85. package/src/types.ts +0 -65
  86. package/src/validation/create-args-schema.ts +0 -76
  87. package/src/validation/create-normalized-schema.ts +0 -52
  88. package/src/validation/errors.ts +0 -43
  89. package/src/validation/index.ts +0 -202
  90. package/src/validation/normalize.ts +0 -299
  91. package/src/validation/to-dictionary.ts +0 -67
  92. package/src/validation/types.ts +0 -82
  93. package/src/validation/validate-normalized.ts +0 -133
  94. package/src/validation/validation.test.ts +0 -562
@@ -1,594 +0,0 @@
1
- import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { describe, expect, it, afterEach } from "vitest";
5
- import {
6
- getDictionaryFormat,
7
- prepareDictionaryEntries,
8
- readDictionaryFile,
9
- resolveAreaJsonPath,
10
- resolveCompiledJsonPath,
11
- resolveSplitJsonPath,
12
- splitDictionaryByDeliveryArea,
13
- splitDictionaryByLocale,
14
- } from "./read-dictionary.js";
15
-
16
- describe("read-dictionary", () => {
17
- let tempDir: string;
18
-
19
- afterEach(() => {
20
- if (tempDir) {
21
- rmSync(tempDir, { recursive: true, force: true });
22
- }
23
- });
24
-
25
- it("detects dictionary formats from file extension", () => {
26
- expect(getDictionaryFormat("translations/default.json")).toBe("json");
27
- expect(getDictionaryFormat("translations/default.yaml")).toBe("yaml");
28
- expect(getDictionaryFormat("translations/default.yml")).toBe("yaml");
29
- expect(getDictionaryFormat("translations/default.toml")).toBeNull();
30
- });
31
-
32
- it("resolves compiled json path under the generated output directory", () => {
33
- expect(
34
- resolveCompiledJsonPath("src/i18n/translations/billing.yaml", "src/i18n/generated")
35
- ).toBe("src/i18n/generated/translations/billing.json");
36
- expect(resolveCompiledJsonPath("translations/billing.yml", "generated")).toBe(
37
- "generated/translations/billing.json"
38
- );
39
- });
40
-
41
- it("resolves split json path per locale under the generated output directory", () => {
42
- expect(
43
- resolveSplitJsonPath("src/i18n/translations/user.json", "it", "src/i18n/generated")
44
- ).toBe("src/i18n/generated/translations/user.it.json");
45
- expect(resolveSplitJsonPath("translations/billing.yaml", "en", "generated")).toBe(
46
- "generated/translations/billing.en.json"
47
- );
48
- });
49
-
50
- it("resolves area json path under the generated output directory", () => {
51
- expect(
52
- resolveAreaJsonPath("src/i18n/translations/billing.json", "eu", "src/i18n/generated")
53
- ).toBe("src/i18n/generated/translations/billing.eu.json");
54
- expect(resolveAreaJsonPath("translations/default.yaml", "us", "generated")).toBe(
55
- "generated/translations/default.us.json"
56
- );
57
- });
58
-
59
- it("reads multiline yaml dictionaries", () => {
60
- tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-read-dict-"));
61
- const yamlPath = join(tempDir, "welcome.yaml");
62
- writeFileSync(
63
- yamlPath,
64
- `welcome:
65
- en: |
66
- Line one
67
- Line two
68
- it: Ciao {name}
69
- `
70
- );
71
-
72
- const dictionary = readDictionaryFile(yamlPath);
73
- expect(dictionary.welcome?.en).toBe("Line one\nLine two\n");
74
- expect(dictionary.welcome?.it).toBe("Ciao {name}");
75
- });
76
-
77
- it("rejects keys with characters outside letters, digits, and underscore", () => {
78
- tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-read-dict-"));
79
- const jsonPath = join(tempDir, "default.json");
80
- writeFileSync(jsonPath, JSON.stringify({ "app.title": { en: "My App" } }));
81
-
82
- expect(() => readDictionaryFile(jsonPath)).toThrow(
83
- "[Codegen Error] Dictionary file " +
84
- jsonPath +
85
- ': invalid key "app.title" (allowed: letters, digits, underscore; must not start with a digit).'
86
- );
87
- });
88
-
89
- it("rejects keys starting with a digit", () => {
90
- tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-read-dict-"));
91
- const jsonPath = join(tempDir, "default.json");
92
- writeFileSync(jsonPath, JSON.stringify({ "1key": { en: "One" } }));
93
-
94
- expect(() => readDictionaryFile(jsonPath)).toThrow('invalid key "1key"');
95
- });
96
-
97
- it("accepts keys made of letters, digits, and underscore", () => {
98
- tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-read-dict-"));
99
- const jsonPath = join(tempDir, "default.json");
100
- writeFileSync(
101
- jsonPath,
102
- JSON.stringify({ _private: { en: "ok" }, welcome_2: { en: "ok" }, CamelCase: { en: "ok" } })
103
- );
104
-
105
- expect(() => readDictionaryFile(jsonPath)).not.toThrow();
106
- });
107
-
108
- it("compiles yaml sources into per-locale split files", () => {
109
- tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-read-dict-"));
110
- mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
111
- mkdirSync(join(tempDir, "src/i18n/generated"), { recursive: true });
112
- writeFileSync(
113
- join(tempDir, "src/i18n/translations/billing.yaml"),
114
- `invoice_summary:
115
- en: You have {count} invoices
116
- `
117
- );
118
-
119
- const result = prepareDictionaryEntries(
120
- tempDir,
121
- [{ namespace: "billing", filePath: "src/i18n/translations/billing.yaml" }],
122
- "src/i18n/generated",
123
- {
124
- delivery: "split-by-locale",
125
- requestLocales: ["en"],
126
- dictionariesByNamespace: {
127
- billing: readDictionaryFile(join(tempDir, "src/i18n/translations/billing.yaml")),
128
- },
129
- }
130
- );
131
-
132
- expect(result.resolvedEntries).toEqual([
133
- { namespace: "billing", filePath: "src/i18n/translations/billing.yaml" },
134
- ]);
135
- expect(result.splitPathsByNamespace).toEqual({
136
- billing: {
137
- en: "src/i18n/generated/translations/billing.en.json",
138
- },
139
- });
140
- expect(result.compiledFiles).toEqual([
141
- "src/i18n/translations/billing.yaml → src/i18n/generated/translations/billing.en.json",
142
- ]);
143
-
144
- const compiled = JSON.parse(
145
- readFileSync(join(tempDir, "src/i18n/generated/translations/billing.en.json"), "utf8")
146
- ) as Record<string, Record<string, string>>;
147
- expect(compiled.invoice_summary?.en).toBe("You have {count} invoices");
148
- });
149
-
150
- it("splits json sources into per-locale files", () => {
151
- tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-read-dict-"));
152
- mkdirSync(join(tempDir, "translations"), { recursive: true });
153
- writeFileSync(
154
- join(tempDir, "translations/default.json"),
155
- JSON.stringify({ welcome: { en: "Welcome {name}!" } })
156
- );
157
-
158
- const result = prepareDictionaryEntries(
159
- tempDir,
160
- [{ namespace: "default", filePath: "translations/default.json" }],
161
- "generated",
162
- {
163
- delivery: "split-by-locale",
164
- requestLocales: ["en"],
165
- dictionariesByNamespace: {
166
- default: readDictionaryFile(join(tempDir, "translations/default.json")),
167
- },
168
- }
169
- );
170
-
171
- expect(result.resolvedEntries).toEqual([
172
- { namespace: "default", filePath: "translations/default.json" },
173
- ]);
174
- expect(result.splitPathsByNamespace).toEqual({
175
- default: {
176
- en: "generated/translations/default.en.json",
177
- },
178
- });
179
- expect(result.compiledFiles).toEqual([
180
- "translations/default.json → generated/translations/default.en.json",
181
- ]);
182
- });
183
-
184
- it("splits multiple namespaces into per-locale files", () => {
185
- tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-read-dict-"));
186
- mkdirSync(join(tempDir, "translations"), { recursive: true });
187
- mkdirSync(join(tempDir, "generated"), { recursive: true });
188
- writeFileSync(
189
- join(tempDir, "translations/user.json"),
190
- JSON.stringify({
191
- profile_title: { en: "Your profile", it: "Il tuo profilo" },
192
- })
193
- );
194
- writeFileSync(
195
- join(tempDir, "translations/billing.json"),
196
- JSON.stringify({
197
- invoice_summary: { en: "You have {count} invoices", it: "Hai {count} fatture" },
198
- })
199
- );
200
-
201
- const result = prepareDictionaryEntries(
202
- tempDir,
203
- [
204
- { namespace: "user", filePath: "translations/user.json" },
205
- { namespace: "billing", filePath: "translations/billing.json" },
206
- ],
207
- "generated",
208
- {
209
- dictionariesByNamespace: {
210
- user: readDictionaryFile(join(tempDir, "translations/user.json")),
211
- billing: readDictionaryFile(join(tempDir, "translations/billing.json")),
212
- },
213
- delivery: "split-by-locale",
214
- requestLocales: ["en", "it"],
215
- }
216
- );
217
-
218
- expect(result.splitPathsByNamespace).toEqual({
219
- user: {
220
- en: "generated/translations/user.en.json",
221
- it: "generated/translations/user.it.json",
222
- },
223
- billing: {
224
- en: "generated/translations/billing.en.json",
225
- it: "generated/translations/billing.it.json",
226
- },
227
- });
228
-
229
- const userEn = JSON.parse(
230
- readFileSync(join(tempDir, "generated/translations/user.en.json"), "utf8")
231
- ) as Record<string, Record<string, string>>;
232
- expect(userEn.profile_title).toEqual({ en: "Your profile" });
233
-
234
- const billingIt = JSON.parse(
235
- readFileSync(join(tempDir, "generated/translations/billing.it.json"), "utf8")
236
- ) as Record<string, Record<string, string>>;
237
- expect(billingIt.invoice_summary).toEqual({ it: "Hai {count} fatture" });
238
- });
239
-
240
- it("splits json sources into per-locale files under generated without touching the source", () => {
241
- tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-read-dict-"));
242
- mkdirSync(join(tempDir, "translations"), { recursive: true });
243
- mkdirSync(join(tempDir, "generated"), { recursive: true });
244
- const sourcePath = join(tempDir, "translations/user.json");
245
- writeFileSync(
246
- sourcePath,
247
- JSON.stringify({
248
- profile_title: { en: "Your profile", it: "Il tuo profilo" },
249
- greeting: { en: "Hello {name}!", it: "Ciao {name}!" },
250
- })
251
- );
252
-
253
- const result = prepareDictionaryEntries(
254
- tempDir,
255
- [{ namespace: "user", filePath: "translations/user.json" }],
256
- "generated",
257
- {
258
- dictionariesByNamespace: { user: readDictionaryFile(sourcePath) },
259
- delivery: "split-by-locale",
260
- requestLocales: ["en", "it"],
261
- }
262
- );
263
-
264
- expect(result.resolvedEntries).toEqual([
265
- { namespace: "user", filePath: "translations/user.json" },
266
- ]);
267
- expect(result.splitPathsByNamespace).toEqual({
268
- user: {
269
- en: "generated/translations/user.en.json",
270
- it: "generated/translations/user.it.json",
271
- },
272
- });
273
- expect(result.compiledFiles).toEqual([
274
- "translations/user.json → generated/translations/user.en.json",
275
- "translations/user.json → generated/translations/user.it.json",
276
- ]);
277
-
278
- const sourceAfter = JSON.parse(readFileSync(sourcePath, "utf8")) as Record<
279
- string,
280
- Record<string, string>
281
- >;
282
- expect(sourceAfter.profile_title).toEqual({ en: "Your profile", it: "Il tuo profilo" });
283
-
284
- const enSplit = JSON.parse(
285
- readFileSync(join(tempDir, "generated/translations/user.en.json"), "utf8")
286
- ) as Record<string, Record<string, string>>;
287
- expect(enSplit).toEqual({
288
- profile_title: { en: "Your profile" },
289
- greeting: { en: "Hello {name}!" },
290
- });
291
-
292
- const itSplit = JSON.parse(
293
- readFileSync(join(tempDir, "generated/translations/user.it.json"), "utf8")
294
- ) as Record<string, Record<string, string>>;
295
- expect(itSplit).toEqual({
296
- profile_title: { it: "Il tuo profilo" },
297
- greeting: { it: "Ciao {name}!" },
298
- });
299
- });
300
-
301
- it("splits yaml sources into per-locale json without emitting a canonical intermediate", () => {
302
- tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-read-dict-"));
303
- mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
304
- mkdirSync(join(tempDir, "src/i18n/generated"), { recursive: true });
305
- writeFileSync(
306
- join(tempDir, "src/i18n/translations/billing.yaml"),
307
- `invoice_summary:
308
- en: You have {count} invoices
309
- it: Hai {count} fatture
310
- `
311
- );
312
-
313
- const result = prepareDictionaryEntries(
314
- tempDir,
315
- [{ namespace: "billing", filePath: "src/i18n/translations/billing.yaml" }],
316
- "src/i18n/generated",
317
- {
318
- dictionariesByNamespace: {
319
- billing: readDictionaryFile(join(tempDir, "src/i18n/translations/billing.yaml")),
320
- },
321
- delivery: "split-by-locale",
322
- requestLocales: ["en", "it"],
323
- }
324
- );
325
-
326
- expect(result.resolvedEntries).toEqual([
327
- { namespace: "billing", filePath: "src/i18n/translations/billing.yaml" },
328
- ]);
329
- expect(result.splitPathsByNamespace.billing).toEqual({
330
- en: "src/i18n/generated/translations/billing.en.json",
331
- it: "src/i18n/generated/translations/billing.it.json",
332
- });
333
-
334
- const canonicalPath = join(tempDir, "src/i18n/generated/translations/billing.json");
335
- expect(() => readFileSync(canonicalPath, "utf8")).toThrow();
336
-
337
- const enSplit = JSON.parse(
338
- readFileSync(join(tempDir, "src/i18n/generated/translations/billing.en.json"), "utf8")
339
- ) as Record<string, Record<string, string>>;
340
- expect(enSplit.invoice_summary?.en).toBe("You have {count} invoices");
341
- });
342
-
343
- it("applies localeFallback when splitting dictionaries", () => {
344
- tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-read-dict-"));
345
- mkdirSync(join(tempDir, "translations"), { recursive: true });
346
- mkdirSync(join(tempDir, "generated"), { recursive: true });
347
- writeFileSync(
348
- join(tempDir, "translations/default.json"),
349
- JSON.stringify({
350
- login_button: { en: "Login", it: "Accedi" },
351
- })
352
- );
353
-
354
- const localeFallback = {
355
- en: null,
356
- "de-DE": "en",
357
- "de-CH": "de-DE",
358
- it: "en",
359
- } as const;
360
-
361
- prepareDictionaryEntries(
362
- tempDir,
363
- [{ namespace: "default", filePath: "translations/default.json" }],
364
- "generated",
365
- {
366
- dictionariesByNamespace: {
367
- default: readDictionaryFile(join(tempDir, "translations/default.json")),
368
- },
369
- delivery: "split-by-locale",
370
- requestLocales: ["de-CH"],
371
- localeFallback,
372
- }
373
- );
374
-
375
- const deChSplit = JSON.parse(
376
- readFileSync(join(tempDir, "generated/translations/default.de-CH.json"), "utf8")
377
- ) as Record<string, Record<string, string>>;
378
- expect(deChSplit).toEqual({
379
- login_button: { "de-CH": "Login" },
380
- });
381
- });
382
-
383
- it("uses the provided parsed dictionaries instead of re-reading sources", () => {
384
- tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-read-dict-"));
385
- mkdirSync(join(tempDir, "translations"), { recursive: true });
386
- mkdirSync(join(tempDir, "generated"), { recursive: true });
387
- writeFileSync(
388
- join(tempDir, "translations/default.json"),
389
- JSON.stringify({ welcome: { en: "On disk" } })
390
- );
391
-
392
- prepareDictionaryEntries(
393
- tempDir,
394
- [{ namespace: "default", filePath: "translations/default.json" }],
395
- "generated",
396
- {
397
- dictionariesByNamespace: { default: { welcome: { en: "In memory" } } },
398
- delivery: "split-by-locale",
399
- requestLocales: ["en"],
400
- }
401
- );
402
-
403
- const enSplit = JSON.parse(
404
- readFileSync(join(tempDir, "generated/translations/default.en.json"), "utf8")
405
- ) as Record<string, Record<string, string>>;
406
- expect(enSplit.welcome).toEqual({ en: "In memory" });
407
- });
408
-
409
- it("throws when a parsed dictionary is missing for a namespace", () => {
410
- tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-read-dict-"));
411
- mkdirSync(join(tempDir, "translations"), { recursive: true });
412
- writeFileSync(
413
- join(tempDir, "translations/default.json"),
414
- JSON.stringify({ welcome: { en: "Welcome" } })
415
- );
416
-
417
- expect(() =>
418
- prepareDictionaryEntries(
419
- tempDir,
420
- [{ namespace: "default", filePath: "translations/default.json" }],
421
- "generated",
422
- {
423
- delivery: "split-by-locale",
424
- requestLocales: ["en"],
425
- dictionariesByNamespace: {},
426
- }
427
- )
428
- ).toThrow('[Codegen Error] Missing parsed dictionary for namespace "default".');
429
- });
430
-
431
- it("projects each locale independently via splitDictionaryByLocale", () => {
432
- const dictionary = {
433
- login_button: { en: "Login", it: "Accedi" },
434
- welcome: { en: "Welcome {name}!", it: "Benvenuto {name}!" },
435
- };
436
- const localeFallback = {
437
- en: null,
438
- "de-CH": "en",
439
- } as const;
440
-
441
- expect(splitDictionaryByLocale(dictionary, ["en", "it"])).toEqual({
442
- en: {
443
- login_button: { en: "Login" },
444
- welcome: { en: "Welcome {name}!" },
445
- },
446
- it: {
447
- login_button: { it: "Accedi" },
448
- welcome: { it: "Benvenuto {name}!" },
449
- },
450
- });
451
-
452
- expect(splitDictionaryByLocale(dictionary, ["de-CH"], localeFallback)).toEqual({
453
- "de-CH": {
454
- login_button: { "de-CH": "Login" },
455
- welcome: { "de-CH": "Welcome {name}!" },
456
- },
457
- });
458
- });
459
-
460
- it("splits dictionaries into per-area files with hybrid projection", () => {
461
- tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-read-dict-"));
462
- mkdirSync(join(tempDir, "translations"), { recursive: true });
463
- mkdirSync(join(tempDir, "generated"), { recursive: true });
464
- writeFileSync(
465
- join(tempDir, "translations/default.json"),
466
- JSON.stringify({
467
- some_key: { it: "Ciao", fr: "Hallo", "en-US": "Hello" },
468
- some_other_key: { "en-US": "Computer", fr: "Ordinateur" },
469
- })
470
- );
471
-
472
- const deliveryArtifacts = {
473
- eu: ["it", "fr"],
474
- us: ["en-US"],
475
- };
476
- const localeFallback = {
477
- "en-US": null,
478
- it: "en-US",
479
- fr: null,
480
- } as const;
481
-
482
- const result = prepareDictionaryEntries(
483
- tempDir,
484
- [{ namespace: "default", filePath: "translations/default.json" }],
485
- "generated",
486
- {
487
- dictionariesByNamespace: {
488
- default: readDictionaryFile(join(tempDir, "translations/default.json")),
489
- },
490
- delivery: "custom",
491
- deliveryArtifacts,
492
- localeFallback,
493
- }
494
- );
495
-
496
- expect(result.splitPathsByNamespace).toEqual({
497
- default: {
498
- eu: "generated/translations/default.eu.json",
499
- us: "generated/translations/default.us.json",
500
- },
501
- });
502
- expect(result.compiledFiles).toEqual([
503
- "translations/default.json → generated/translations/default.eu.json",
504
- "translations/default.json → generated/translations/default.us.json",
505
- ]);
506
-
507
- const euArea = JSON.parse(
508
- readFileSync(join(tempDir, "generated/translations/default.eu.json"), "utf8")
509
- ) as Record<string, Record<string, string>>;
510
- expect(euArea).toEqual({
511
- some_key: { it: "Ciao", fr: "Hallo" },
512
- some_other_key: { it: "Computer", fr: "Ordinateur" },
513
- });
514
-
515
- const usArea = JSON.parse(
516
- readFileSync(join(tempDir, "generated/translations/default.us.json"), "utf8")
517
- ) as Record<string, Record<string, string>>;
518
- expect(usArea).toEqual({
519
- some_key: { "en-US": "Hello" },
520
- some_other_key: { "en-US": "Computer" },
521
- });
522
- });
523
-
524
- it("splits yaml sources into per-area json without emitting a canonical intermediate", () => {
525
- tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-read-dict-"));
526
- mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
527
- mkdirSync(join(tempDir, "src/i18n/generated"), { recursive: true });
528
- writeFileSync(
529
- join(tempDir, "src/i18n/translations/billing.yaml"),
530
- `invoice_summary:
531
- en-US: You have {count} invoices
532
- it: Hai {count} fatture
533
- `
534
- );
535
-
536
- const result = prepareDictionaryEntries(
537
- tempDir,
538
- [{ namespace: "billing", filePath: "src/i18n/translations/billing.yaml" }],
539
- "src/i18n/generated",
540
- {
541
- dictionariesByNamespace: {
542
- billing: readDictionaryFile(join(tempDir, "src/i18n/translations/billing.yaml")),
543
- },
544
- delivery: "custom",
545
- deliveryArtifacts: { us: ["en-US"], eu: ["it"] },
546
- localeFallback: { "en-US": null, it: "en-US" },
547
- }
548
- );
549
-
550
- expect(result.resolvedEntries).toEqual([
551
- { namespace: "billing", filePath: "src/i18n/translations/billing.yaml" },
552
- ]);
553
- expect(result.splitPathsByNamespace.billing).toEqual({
554
- eu: "src/i18n/generated/translations/billing.eu.json",
555
- us: "src/i18n/generated/translations/billing.us.json",
556
- });
557
-
558
- expect(() =>
559
- readFileSync(join(tempDir, "src/i18n/generated/translations/billing.json"), "utf8")
560
- ).toThrow();
561
-
562
- const euArea = JSON.parse(
563
- readFileSync(join(tempDir, "src/i18n/generated/translations/billing.eu.json"), "utf8")
564
- ) as Record<string, Record<string, string>>;
565
- expect(euArea.invoice_summary).toEqual({ it: "Hai {count} fatture" });
566
- });
567
-
568
- it("projects each delivery area via splitDictionaryByDeliveryArea", () => {
569
- const dictionary = {
570
- some_key: { it: "Ciao", fr: "Hallo", "en-US": "Hello" },
571
- some_other_key: { "en-US": "Computer", fr: "Ordinateur" },
572
- };
573
- const deliveryArtifacts = {
574
- eu: ["it", "fr"],
575
- us: ["en-US"],
576
- };
577
- const localeFallback = {
578
- "en-US": null,
579
- it: "en-US",
580
- fr: null,
581
- } as const;
582
-
583
- expect(splitDictionaryByDeliveryArea(dictionary, deliveryArtifacts, localeFallback)).toEqual({
584
- eu: {
585
- some_key: { it: "Ciao", fr: "Hallo" },
586
- some_other_key: { it: "Computer", fr: "Ordinateur" },
587
- },
588
- us: {
589
- some_key: { "en-US": "Hello" },
590
- some_other_key: { "en-US": "Computer" },
591
- },
592
- });
593
- });
594
- });