@messagevisor/catalog 0.5.0 → 0.6.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/dist/assets/index-DJ8oQlZp.js +73 -0
- package/dist/index.html +1 -1
- package/lib/node/index.d.ts +3 -0
- package/lib/node/index.js +330 -152
- package/lib/node/index.js.map +1 -1
- package/package.json +2 -2
- package/src/node/index.spec.ts +238 -47
- package/src/node/index.ts +267 -26
- package/src/pages/EntityDetailPage.tsx +15 -4
- package/src/types.ts +1 -0
- package/dist/assets/index-BJS9aW0t.js +0 -73
package/src/node/index.spec.ts
CHANGED
|
@@ -46,6 +46,25 @@ async function pathExists(root: string, relativePath: string) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function stripAnsi(value: string) {
|
|
50
|
+
return value.replace(/\x1b\[[0-9;]*m/g, "").replace(/%s/g, "");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function captureConsoleLog(callback: () => Promise<void>) {
|
|
54
|
+
const logs: string[] = [];
|
|
55
|
+
const spy = jest.spyOn(console, "log").mockImplementation((...args: unknown[]) => {
|
|
56
|
+
logs.push(args.map(String).join(" "));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
await callback();
|
|
61
|
+
} finally {
|
|
62
|
+
spy.mockRestore();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return stripAnsi(logs.join("\n"));
|
|
66
|
+
}
|
|
67
|
+
|
|
49
68
|
async function createProject() {
|
|
50
69
|
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "messagevisor-catalog-"));
|
|
51
70
|
const interpolationModulePath = path.join(
|
|
@@ -230,14 +249,6 @@ describe("catalog", function () {
|
|
|
230
249
|
const manifest = await readJson<any>(root, "catalog-out/data/manifest.json");
|
|
231
250
|
const index = await readJson<any>(root, "catalog-out/data/root/index.json");
|
|
232
251
|
const locale = await readJson<any>(root, "catalog-out/data/root/entities/locale/en-US.json");
|
|
233
|
-
const localeDuplicates = await readJson<any>(
|
|
234
|
-
root,
|
|
235
|
-
"catalog-out/data/root/duplicates/locales/en-US.json",
|
|
236
|
-
);
|
|
237
|
-
const emptyLocaleDuplicates = await readJson<any>(
|
|
238
|
-
root,
|
|
239
|
-
"catalog-out/data/root/duplicates/locales/nl.json",
|
|
240
|
-
);
|
|
241
252
|
const message = await readJson<any>(
|
|
242
253
|
root,
|
|
243
254
|
"catalog-out/data/root/entities/message/common.welcome.json",
|
|
@@ -253,11 +264,14 @@ describe("catalog", function () {
|
|
|
253
264
|
expect(manifest.sets).toBe(false);
|
|
254
265
|
expect(manifest.router).toBe("browser");
|
|
255
266
|
expect(manifest.dev).toBeUndefined();
|
|
256
|
-
expect(manifest.features).toEqual({ translationSearch: false });
|
|
267
|
+
expect(manifest.features).toEqual({ translationSearch: false, duplicates: false });
|
|
257
268
|
expect(manifest.paths.root).toBe("data/root/index.json");
|
|
258
269
|
await expect(pathExists(root, "catalog-out/data/root/translations/77656c.json")).resolves.toBe(
|
|
259
270
|
false,
|
|
260
271
|
);
|
|
272
|
+
await expect(
|
|
273
|
+
pathExists(root, "catalog-out/data/root/duplicates/locales/en-US.json"),
|
|
274
|
+
).resolves.toBe(false);
|
|
261
275
|
expect(index.counts.message).toBe(2);
|
|
262
276
|
expect(
|
|
263
277
|
index.entities.message.find((entry: any) => entry.key === "common.welcome").targets,
|
|
@@ -321,31 +335,6 @@ describe("catalog", function () {
|
|
|
321
335
|
]),
|
|
322
336
|
);
|
|
323
337
|
expect(locale.targetFormats.web.number.money.minimumFractionDigits).toBe(2);
|
|
324
|
-
expect(localeDuplicates).toEqual({
|
|
325
|
-
locale: "en-US",
|
|
326
|
-
summary: {
|
|
327
|
-
duplicateValues: 1,
|
|
328
|
-
duplicateMessageKeys: 2,
|
|
329
|
-
},
|
|
330
|
-
duplicateValues: [
|
|
331
|
-
{
|
|
332
|
-
value: "Welcome",
|
|
333
|
-
messageKeys: ["common.draft", "common.welcome"],
|
|
334
|
-
sources: [
|
|
335
|
-
{ messageKey: "common.draft", locale: "en" },
|
|
336
|
-
{ messageKey: "common.welcome", locale: "en" },
|
|
337
|
-
],
|
|
338
|
-
},
|
|
339
|
-
],
|
|
340
|
-
});
|
|
341
|
-
expect(emptyLocaleDuplicates).toEqual({
|
|
342
|
-
locale: "nl",
|
|
343
|
-
summary: {
|
|
344
|
-
duplicateValues: 0,
|
|
345
|
-
duplicateMessageKeys: 0,
|
|
346
|
-
},
|
|
347
|
-
duplicateValues: [],
|
|
348
|
-
});
|
|
349
338
|
expect(target.formatRowsByLocale["en-US"]).toEqual(
|
|
350
339
|
expect.arrayContaining([
|
|
351
340
|
expect.objectContaining({
|
|
@@ -403,6 +392,56 @@ describe("catalog", function () {
|
|
|
403
392
|
expect(history.entries).toEqual([]);
|
|
404
393
|
});
|
|
405
394
|
|
|
395
|
+
it("exports locale duplicate reports only when opted in", async function () {
|
|
396
|
+
const root = await createProject();
|
|
397
|
+
roots.push(root);
|
|
398
|
+
const projectConfig = getProjectConfig(root);
|
|
399
|
+
const datasource = new Datasource(projectConfig, root);
|
|
400
|
+
|
|
401
|
+
await catalogApi.exportCatalog(root, projectConfig, datasource, {
|
|
402
|
+
outDir: "catalog-out",
|
|
403
|
+
copyAssets: false,
|
|
404
|
+
withDuplicates: true,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const manifest = await readJson<any>(root, "catalog-out/data/manifest.json");
|
|
408
|
+
const localeDuplicates = await readJson<any>(
|
|
409
|
+
root,
|
|
410
|
+
"catalog-out/data/root/duplicates/locales/en-US.json",
|
|
411
|
+
);
|
|
412
|
+
const emptyLocaleDuplicates = await readJson<any>(
|
|
413
|
+
root,
|
|
414
|
+
"catalog-out/data/root/duplicates/locales/nl.json",
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
expect(manifest.features).toEqual({ translationSearch: false, duplicates: true });
|
|
418
|
+
expect(localeDuplicates).toEqual({
|
|
419
|
+
locale: "en-US",
|
|
420
|
+
summary: {
|
|
421
|
+
duplicateValues: 1,
|
|
422
|
+
duplicateMessageKeys: 2,
|
|
423
|
+
},
|
|
424
|
+
duplicateValues: [
|
|
425
|
+
{
|
|
426
|
+
value: "Welcome",
|
|
427
|
+
messageKeys: ["common.draft", "common.welcome"],
|
|
428
|
+
sources: [
|
|
429
|
+
{ messageKey: "common.draft", locale: "en" },
|
|
430
|
+
{ messageKey: "common.welcome", locale: "en" },
|
|
431
|
+
],
|
|
432
|
+
},
|
|
433
|
+
],
|
|
434
|
+
});
|
|
435
|
+
expect(emptyLocaleDuplicates).toEqual({
|
|
436
|
+
locale: "nl",
|
|
437
|
+
summary: {
|
|
438
|
+
duplicateValues: 0,
|
|
439
|
+
duplicateMessageKeys: 0,
|
|
440
|
+
},
|
|
441
|
+
duplicateValues: [],
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
406
445
|
it("exports translation search shards only when opted in", async function () {
|
|
407
446
|
const root = await createProject();
|
|
408
447
|
roots.push(root);
|
|
@@ -418,11 +457,63 @@ describe("catalog", function () {
|
|
|
418
457
|
const manifest = await readJson<any>(root, "catalog-out/data/manifest.json");
|
|
419
458
|
const shard = await readJson<any>(root, "catalog-out/data/root/translations/77656c.json");
|
|
420
459
|
|
|
421
|
-
expect(manifest.features).toEqual({ translationSearch: true });
|
|
460
|
+
expect(manifest.features).toEqual({ translationSearch: true, duplicates: false });
|
|
422
461
|
expect(shard["common.welcome"]).toEqual(expect.arrayContaining(["welcome", "welcome pro"]));
|
|
423
462
|
expect(shard["common.draft"]).toEqual(["welcome"]);
|
|
424
463
|
});
|
|
425
464
|
|
|
465
|
+
it("prints progress output for default catalog export", async function () {
|
|
466
|
+
const root = await createProject();
|
|
467
|
+
roots.push(root);
|
|
468
|
+
const projectConfig = getProjectConfig(root);
|
|
469
|
+
const datasource = new Datasource(projectConfig, root);
|
|
470
|
+
|
|
471
|
+
const output = await captureConsoleLog(async () => {
|
|
472
|
+
await catalogApi.exportCatalog(root, projectConfig, datasource, {
|
|
473
|
+
outDir: "catalog-out",
|
|
474
|
+
copyAssets: false,
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
expect(output).toContain("Generating Messagevisor catalog");
|
|
479
|
+
expect(output).toContain("Output: catalog-out");
|
|
480
|
+
expect(output).toContain("Router: browser");
|
|
481
|
+
expect(output).toContain("Features: none");
|
|
482
|
+
expect(output).toContain("Preparing output directory");
|
|
483
|
+
expect(output).toContain("Reading Git history");
|
|
484
|
+
expect(output).toContain("Discovering project sets");
|
|
485
|
+
expect(output).toContain("Writing project history");
|
|
486
|
+
expect(output).toContain("Root catalog");
|
|
487
|
+
expect(output).toContain("Processing entities");
|
|
488
|
+
expect(output).toContain("Writing messages");
|
|
489
|
+
expect(output).toContain("Writing manifest");
|
|
490
|
+
expect(output).toContain("Catalog exported to catalog-out");
|
|
491
|
+
expect(output).toContain("Time:");
|
|
492
|
+
expect(output).not.toContain("Scanning duplicate translations");
|
|
493
|
+
expect(output).not.toContain("Building translation search shards");
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("prints optional catalog progress only when feature work is enabled", async function () {
|
|
497
|
+
const root = await createProject();
|
|
498
|
+
roots.push(root);
|
|
499
|
+
const projectConfig = getProjectConfig(root);
|
|
500
|
+
const datasource = new Datasource(projectConfig, root);
|
|
501
|
+
|
|
502
|
+
const output = await captureConsoleLog(async () => {
|
|
503
|
+
await catalogApi.exportCatalog(root, projectConfig, datasource, {
|
|
504
|
+
outDir: "catalog-out",
|
|
505
|
+
copyAssets: false,
|
|
506
|
+
withDuplicates: true,
|
|
507
|
+
withTranslationSearch: true,
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
expect(output).toContain("Features: translation search, duplicates");
|
|
512
|
+
expect(output).toContain("Scanning duplicate translations");
|
|
513
|
+
expect(output).toContain("Writing duplicate reports");
|
|
514
|
+
expect(output).toContain("Building translation search shards");
|
|
515
|
+
});
|
|
516
|
+
|
|
426
517
|
it("streams Git history into project, entity, and last-modified catalog data", async function () {
|
|
427
518
|
const root = await createProject();
|
|
428
519
|
roots.push(root);
|
|
@@ -700,22 +791,41 @@ describe("catalog", function () {
|
|
|
700
791
|
const admin = await readJson<any>(root, "catalog-out/data/sets/admin/index.json");
|
|
701
792
|
|
|
702
793
|
expect(manifest.sets).toBe(true);
|
|
703
|
-
expect(manifest.features).toEqual({ translationSearch: false });
|
|
794
|
+
expect(manifest.features).toEqual({ translationSearch: false, duplicates: false });
|
|
704
795
|
expect(manifest.setKeys).toEqual(["admin", "storefront"]);
|
|
705
796
|
await expect(
|
|
706
797
|
pathExists(root, "catalog-out/data/sets/storefront/translations/73746f.json"),
|
|
707
798
|
).resolves.toBe(false);
|
|
799
|
+
await expect(
|
|
800
|
+
pathExists(root, "catalog-out/data/sets/storefront/duplicates/locales/en.json"),
|
|
801
|
+
).resolves.toBe(false);
|
|
802
|
+
|
|
803
|
+
expect(storefront.counts.message).toBe(2);
|
|
804
|
+
expect(admin.counts.message).toBe(2);
|
|
805
|
+
await expect(
|
|
806
|
+
readJson<any>(root, "catalog-out/data/sets/storefront/entities/message/common.welcome.json"),
|
|
807
|
+
).resolves.toMatchObject({
|
|
808
|
+
key: "common.welcome",
|
|
809
|
+
entity: { translations: { en: "storefront" } },
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
await catalogApi.exportCatalog(root, projectConfig, datasource, {
|
|
813
|
+
outDir: "catalog-with-duplicates",
|
|
814
|
+
copyAssets: false,
|
|
815
|
+
withDuplicates: true,
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
const optInManifest = await readJson<any>(root, "catalog-with-duplicates/data/manifest.json");
|
|
708
819
|
const storefrontDuplicates = await readJson<any>(
|
|
709
820
|
root,
|
|
710
|
-
"catalog-
|
|
821
|
+
"catalog-with-duplicates/data/sets/storefront/duplicates/locales/en.json",
|
|
711
822
|
);
|
|
712
823
|
const adminDuplicates = await readJson<any>(
|
|
713
824
|
root,
|
|
714
|
-
"catalog-
|
|
825
|
+
"catalog-with-duplicates/data/sets/admin/duplicates/locales/en.json",
|
|
715
826
|
);
|
|
716
827
|
|
|
717
|
-
expect(
|
|
718
|
-
expect(admin.counts.message).toBe(2);
|
|
828
|
+
expect(optInManifest.features).toEqual({ translationSearch: false, duplicates: true });
|
|
719
829
|
expect(storefrontDuplicates.duplicateValues).toEqual([
|
|
720
830
|
{
|
|
721
831
|
value: "storefront",
|
|
@@ -736,12 +846,38 @@ describe("catalog", function () {
|
|
|
736
846
|
],
|
|
737
847
|
},
|
|
738
848
|
]);
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
it("prints set names while exporting set project catalogs", async function () {
|
|
852
|
+
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "messagevisor-catalog-"));
|
|
853
|
+
roots.push(root);
|
|
854
|
+
|
|
855
|
+
await writeFile(root, "messagevisor.config.js", "module.exports = { sets: true };\n");
|
|
856
|
+
|
|
857
|
+
for (const set of ["storefront", "admin"]) {
|
|
858
|
+
await writeFile(root, `sets/${set}/locales/en.yml`, "description: English\n");
|
|
859
|
+
await writeFile(
|
|
860
|
+
root,
|
|
861
|
+
`sets/${set}/messages/common/welcome.yml`,
|
|
862
|
+
`description: Welcome\ntranslations:\n en: ${set}\n`,
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const projectConfig = getProjectConfig(root);
|
|
867
|
+
const datasource = new Datasource(projectConfig, root);
|
|
868
|
+
|
|
869
|
+
const output = await captureConsoleLog(async () => {
|
|
870
|
+
await catalogApi.exportCatalog(root, projectConfig, datasource, {
|
|
871
|
+
outDir: "catalog-out",
|
|
872
|
+
copyAssets: false,
|
|
873
|
+
});
|
|
744
874
|
});
|
|
875
|
+
|
|
876
|
+
expect(output).toContain("Sets: enabled");
|
|
877
|
+
expect(output).toContain("Discovering project sets");
|
|
878
|
+
expect(output).toContain('Set "admin"');
|
|
879
|
+
expect(output).toContain('Set "storefront"');
|
|
880
|
+
expect(output).toContain("Processing entities");
|
|
745
881
|
});
|
|
746
882
|
|
|
747
883
|
it("exports set translation search shards when opted in", async function () {
|
|
@@ -778,7 +914,7 @@ describe("catalog", function () {
|
|
|
778
914
|
"catalog-out/data/sets/admin/translations/61646d.json",
|
|
779
915
|
);
|
|
780
916
|
|
|
781
|
-
expect(manifest.features).toEqual({ translationSearch: true });
|
|
917
|
+
expect(manifest.features).toEqual({ translationSearch: true, duplicates: false });
|
|
782
918
|
expect(storefrontShard).toEqual({ "common.welcome": ["storefront"] });
|
|
783
919
|
expect(adminShard).toEqual({ "common.welcome": ["admin"] });
|
|
784
920
|
});
|
|
@@ -988,6 +1124,19 @@ describe("catalog plugin", function () {
|
|
|
988
1124
|
);
|
|
989
1125
|
});
|
|
990
1126
|
|
|
1127
|
+
it("forwards duplicates option for dev catalog mode", async function () {
|
|
1128
|
+
const { handler } = createPlugin();
|
|
1129
|
+
|
|
1130
|
+
await handler({ _: ["catalog"], withDuplicates: true });
|
|
1131
|
+
|
|
1132
|
+
expect(exportMock).toHaveBeenLastCalledWith(
|
|
1133
|
+
expect.any(String),
|
|
1134
|
+
expect.any(Object),
|
|
1135
|
+
expect.any(Object),
|
|
1136
|
+
expect.objectContaining({ withDuplicates: true, dev: true }),
|
|
1137
|
+
);
|
|
1138
|
+
});
|
|
1139
|
+
|
|
991
1140
|
it("forwards translation search option for export subcommand", async function () {
|
|
992
1141
|
const { handler } = createPlugin();
|
|
993
1142
|
|
|
@@ -1005,16 +1154,45 @@ describe("catalog plugin", function () {
|
|
|
1005
1154
|
);
|
|
1006
1155
|
});
|
|
1007
1156
|
|
|
1157
|
+
it("forwards duplicates option for export subcommand", async function () {
|
|
1158
|
+
const { handler } = createPlugin();
|
|
1159
|
+
|
|
1160
|
+
await handler({
|
|
1161
|
+
_: ["catalog", "export"],
|
|
1162
|
+
subcommand: "export",
|
|
1163
|
+
"with-duplicates": true,
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
expect(exportMock).toHaveBeenLastCalledWith(
|
|
1167
|
+
expect.any(String),
|
|
1168
|
+
expect.any(Object),
|
|
1169
|
+
expect.any(Object),
|
|
1170
|
+
expect.objectContaining({ withDuplicates: true }),
|
|
1171
|
+
);
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1008
1174
|
it("forwards long and short port options for serve subcommand", async function () {
|
|
1009
1175
|
const { handler } = createPlugin();
|
|
1010
1176
|
|
|
1011
|
-
await handler({
|
|
1177
|
+
await handler({
|
|
1178
|
+
_: ["catalog", "serve"],
|
|
1179
|
+
subcommand: "serve",
|
|
1180
|
+
port: 3103,
|
|
1181
|
+
"with-duplicates": true,
|
|
1182
|
+
"with-translation-search": true,
|
|
1183
|
+
});
|
|
1012
1184
|
expect(serveMock).toHaveBeenLastCalledWith(
|
|
1013
1185
|
expect.any(String),
|
|
1014
1186
|
expect.any(Object),
|
|
1015
1187
|
expect.any(Object),
|
|
1016
1188
|
expect.not.objectContaining({ withTranslationSearch: true }),
|
|
1017
1189
|
);
|
|
1190
|
+
expect(serveMock).toHaveBeenLastCalledWith(
|
|
1191
|
+
expect.any(String),
|
|
1192
|
+
expect.any(Object),
|
|
1193
|
+
expect.any(Object),
|
|
1194
|
+
expect.not.objectContaining({ withDuplicates: true }),
|
|
1195
|
+
);
|
|
1018
1196
|
expect(serveMock).toHaveBeenLastCalledWith(
|
|
1019
1197
|
expect.any(String),
|
|
1020
1198
|
expect.any(Object),
|
|
@@ -1043,4 +1221,17 @@ describe("catalog plugin", function () {
|
|
|
1043
1221
|
expect.objectContaining({ port: undefined }),
|
|
1044
1222
|
);
|
|
1045
1223
|
});
|
|
1224
|
+
|
|
1225
|
+
it("does not print catalog generation progress for serve subcommand", async function () {
|
|
1226
|
+
const { handler } = createPlugin();
|
|
1227
|
+
|
|
1228
|
+
const output = await captureConsoleLog(async () => {
|
|
1229
|
+
await handler({ _: ["catalog", "serve"], subcommand: "serve" });
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
expect(output).not.toContain("Generating Messagevisor catalog");
|
|
1233
|
+
expect(output).not.toContain("Processing entities");
|
|
1234
|
+
expect(serveMock).toHaveBeenCalledTimes(1);
|
|
1235
|
+
expect(exportMock).not.toHaveBeenCalled();
|
|
1236
|
+
});
|
|
1046
1237
|
});
|