@inlang/sdk 0.26.5 → 0.28.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 (60) hide show
  1. package/dist/adapter/solidAdapter.test.js +52 -4
  2. package/dist/api.d.ts +5 -0
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/createMessageLintReportsQuery.d.ts +1 -1
  5. package/dist/createMessageLintReportsQuery.d.ts.map +1 -1
  6. package/dist/createMessageLintReportsQuery.js +30 -22
  7. package/dist/createMessagesQuery.d.ts.map +1 -1
  8. package/dist/createMessagesQuery.js +24 -1
  9. package/dist/createMessagesQuery.test.js +46 -2
  10. package/dist/createNodeishFsWithAbsolutePaths.d.ts +3 -3
  11. package/dist/createNodeishFsWithAbsolutePaths.d.ts.map +1 -1
  12. package/dist/createNodeishFsWithAbsolutePaths.js +9 -1
  13. package/dist/createNodeishFsWithAbsolutePaths.test.js +7 -1
  14. package/dist/createNodeishFsWithWatcher.d.ts +3 -3
  15. package/dist/createNodeishFsWithWatcher.d.ts.map +1 -1
  16. package/dist/createNodeishFsWithWatcher.js +3 -0
  17. package/dist/errors.d.ts +14 -0
  18. package/dist/errors.d.ts.map +1 -1
  19. package/dist/errors.js +12 -0
  20. package/dist/loadProject.d.ts.map +1 -1
  21. package/dist/loadProject.js +474 -39
  22. package/dist/loadProject.test.js +93 -14
  23. package/dist/messages/variant.test.js +3 -0
  24. package/dist/resolve-modules/plugins/resolvePlugins.test.js +4 -2
  25. package/dist/storage/helper.d.ts +5 -0
  26. package/dist/storage/helper.d.ts.map +1 -0
  27. package/dist/storage/helper.js +35 -0
  28. package/dist/storage/human-id/human-readable-id.d.ts +3 -0
  29. package/dist/storage/human-id/human-readable-id.d.ts.map +1 -0
  30. package/dist/storage/human-id/human-readable-id.js +20 -0
  31. package/dist/storage/human-id/words.d.ts +5 -0
  32. package/dist/storage/human-id/words.d.ts.map +1 -0
  33. package/dist/storage/human-id/words.js +1032 -0
  34. package/dist/storage/human-id/words.test.d.ts +2 -0
  35. package/dist/storage/human-id/words.test.d.ts.map +1 -0
  36. package/dist/storage/human-id/words.test.js +25 -0
  37. package/dist/test-utilities/createMessage.d.ts +1 -0
  38. package/dist/test-utilities/createMessage.d.ts.map +1 -1
  39. package/dist/test-utilities/createMessage.js +1 -0
  40. package/dist/test-utilities/createMessage.test.js +70 -55
  41. package/package.json +16 -7
  42. package/src/adapter/solidAdapter.test.ts +76 -4
  43. package/src/api.ts +6 -0
  44. package/src/createMessageLintReportsQuery.ts +47 -35
  45. package/src/createMessagesQuery.test.ts +54 -2
  46. package/src/createMessagesQuery.ts +30 -1
  47. package/src/createNodeishFsWithAbsolutePaths.test.ts +10 -3
  48. package/src/createNodeishFsWithAbsolutePaths.ts +14 -5
  49. package/src/createNodeishFsWithWatcher.ts +6 -3
  50. package/src/errors.ts +20 -0
  51. package/src/loadProject.test.ts +108 -14
  52. package/src/loadProject.ts +657 -60
  53. package/src/messages/variant.test.ts +3 -0
  54. package/src/resolve-modules/plugins/resolvePlugins.test.ts +4 -2
  55. package/src/storage/helper.ts +48 -0
  56. package/src/storage/human-id/human-readable-id.ts +27 -0
  57. package/src/storage/human-id/words.test.ts +27 -0
  58. package/src/storage/human-id/words.ts +1035 -0
  59. package/src/test-utilities/createMessage.test.ts +72 -54
  60. package/src/test-utilities/createMessage.ts +1 -0
@@ -45,6 +45,7 @@ const mockPlugin = {
45
45
  const exampleMessages = [
46
46
  {
47
47
  id: "a",
48
+ alias: {},
48
49
  selectors: [],
49
50
  variants: [
50
51
  {
@@ -61,6 +62,47 @@ const exampleMessages = [
61
62
  },
62
63
  {
63
64
  id: "b",
65
+ alias: {},
66
+ selectors: [],
67
+ variants: [
68
+ {
69
+ languageTag: "en",
70
+ match: [],
71
+ pattern: [
72
+ {
73
+ type: "Text",
74
+ value: "test",
75
+ },
76
+ ],
77
+ },
78
+ ],
79
+ },
80
+ ];
81
+ const exampleAliasedMessages = [
82
+ {
83
+ id: "raw_tapir_pause_grateful",
84
+ alias: {
85
+ default: "a",
86
+ },
87
+ selectors: [],
88
+ variants: [
89
+ {
90
+ languageTag: "en",
91
+ match: [],
92
+ pattern: [
93
+ {
94
+ type: "Text",
95
+ value: "test",
96
+ },
97
+ ],
98
+ },
99
+ ],
100
+ },
101
+ {
102
+ id: "dizzy_halibut_dial_vaguely",
103
+ alias: {
104
+ default: "b",
105
+ },
64
106
  selectors: [],
65
107
  variants: [
66
108
  {
@@ -405,6 +447,7 @@ describe("functionality", () => {
405
447
  description: mockPlugin.description,
406
448
  displayName: mockPlugin.displayName,
407
449
  module: settings.modules[0],
450
+ settingsSchema: mockPlugin.settingsSchema,
408
451
  });
409
452
  expect(project.installed.messageLintRules()[0]).toEqual({
410
453
  id: mockMessageLintRule.id,
@@ -412,6 +455,7 @@ describe("functionality", () => {
412
455
  displayName: mockMessageLintRule.displayName,
413
456
  module: settings.modules[1],
414
457
  level: "warning",
458
+ settingsSchema: mockMessageLintRule.settingsSchema,
415
459
  });
416
460
  });
417
461
  it("should apply 'warning' as default lint level to lint rules that have no lint level defined in the settings", async () => {
@@ -449,7 +493,7 @@ describe("functionality", () => {
449
493
  id: "plugin.project.i18next",
450
494
  description: { en: "Mock plugin description" },
451
495
  displayName: { en: "Mock Plugin" },
452
- loadMessages: () => [{ id: "some-message", selectors: [], variants: [] }],
496
+ loadMessages: () => [{ id: "some-message", alias: {}, selectors: [], variants: [] }],
453
497
  saveMessages: () => undefined,
454
498
  };
455
499
  const repo = await mockRepo();
@@ -492,7 +536,7 @@ describe("functionality", () => {
492
536
  id: "plugin.project.i18next",
493
537
  description: { en: "Mock plugin description" },
494
538
  displayName: { en: "Mock Plugin" },
495
- loadMessages: () => [{ id: "some-message", selectors: [], variants: [] }],
539
+ loadMessages: () => [{ id: "some-message", alias: {}, selectors: [], variants: [] }],
496
540
  saveMessages: () => undefined,
497
541
  };
498
542
  const repo = await mockRepo();
@@ -563,6 +607,20 @@ describe("functionality", () => {
563
607
  expect(Object.values(project.query.messages.getAll())).toEqual(exampleMessages);
564
608
  });
565
609
  });
610
+ describe("messages with aliases", () => {
611
+ it("should return the messages", async () => {
612
+ const repo = await mockRepo();
613
+ const fs = repo.nodeishFs;
614
+ await fs.mkdir("/user/project.inlang", { recursive: true });
615
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify({ ...settings, experimental: { aliases: true } }));
616
+ const project = await loadProject({
617
+ projectPath: "/user/project.inlang",
618
+ repo,
619
+ _import,
620
+ });
621
+ expect(Object.values(project.query.messages.getAll())).toEqual(exampleAliasedMessages);
622
+ });
623
+ });
566
624
  describe("query", () => {
567
625
  it("should call saveMessages() on updates", async () => {
568
626
  const repo = await mockRepo();
@@ -600,6 +658,7 @@ describe("functionality", () => {
600
658
  where: { id: "a" },
601
659
  data: {
602
660
  id: "a",
661
+ alias: {},
603
662
  selectors: [],
604
663
  variants: [
605
664
  {
@@ -629,6 +688,7 @@ describe("functionality", () => {
629
688
  where: { id: "b" },
630
689
  data: {
631
690
  id: "b",
691
+ alias: {},
632
692
  selectors: [],
633
693
  variants: [
634
694
  {
@@ -654,31 +714,33 @@ describe("functionality", () => {
654
714
  ],
655
715
  },
656
716
  });
657
- await new Promise((resolve) => setTimeout(resolve, 510));
717
+ // lets wait for the next tick
718
+ await new Promise((resolve) => setTimeout(resolve, 100));
658
719
  expect(mockSaveFn.mock.calls.length).toBe(1);
659
720
  expect(mockSaveFn.mock.calls[0][0].settings).toStrictEqual(settings);
660
721
  expect(Object.values(mockSaveFn.mock.calls[0][0].messages)).toStrictEqual([
661
722
  {
662
723
  id: "a",
724
+ alias: {},
663
725
  selectors: [],
664
726
  variants: [
665
727
  {
666
- languageTag: "en",
728
+ languageTag: "de",
667
729
  match: [],
668
730
  pattern: [
669
731
  {
670
732
  type: "Text",
671
- value: "a en",
733
+ value: "a de",
672
734
  },
673
735
  ],
674
736
  },
675
737
  {
676
- languageTag: "de",
738
+ languageTag: "en",
677
739
  match: [],
678
740
  pattern: [
679
741
  {
680
742
  type: "Text",
681
- value: "a de",
743
+ value: "a en",
682
744
  },
683
745
  ],
684
746
  },
@@ -686,25 +748,26 @@ describe("functionality", () => {
686
748
  },
687
749
  {
688
750
  id: "b",
751
+ alias: {},
689
752
  selectors: [],
690
753
  variants: [
691
754
  {
692
- languageTag: "en",
755
+ languageTag: "de",
693
756
  match: [],
694
757
  pattern: [
695
758
  {
696
759
  type: "Text",
697
- value: "b en",
760
+ value: "b de",
698
761
  },
699
762
  ],
700
763
  },
701
764
  {
702
- languageTag: "de",
765
+ languageTag: "en",
703
766
  match: [],
704
767
  pattern: [
705
768
  {
706
769
  type: "Text",
707
- value: "b de",
770
+ value: "b en",
708
771
  },
709
772
  ],
710
773
  },
@@ -766,6 +829,14 @@ describe("functionality", () => {
766
829
  await new Promise((resolve) => setTimeout(resolve, 510));
767
830
  expect(mockSaveFn.mock.calls.length).toBe(1);
768
831
  expect(mockSaveFn.mock.calls[0][0].messages).toHaveLength(4);
832
+ project.query.messages.create({ data: createMessage("fifth", { en: "fifth message" }) });
833
+ await new Promise((resolve) => setTimeout(resolve, 510));
834
+ expect(mockSaveFn.mock.calls.length).toBe(2);
835
+ expect(mockSaveFn.mock.calls[1][0].messages).toHaveLength(5);
836
+ project.query.messages.delete({ where: { id: "fourth" } });
837
+ await new Promise((resolve) => setTimeout(resolve, 510));
838
+ expect(mockSaveFn.mock.calls.length).toBe(3);
839
+ expect(mockSaveFn.mock.calls[2][0].messages).toHaveLength(4);
769
840
  });
770
841
  });
771
842
  describe("lint", () => {
@@ -868,14 +939,22 @@ describe("functionality", () => {
868
939
  project.query.messages.getAll.subscribe(() => {
869
940
  counter = counter + 1;
870
941
  });
942
+ // subscribe fires once
871
943
  expect(counter).toBe(1);
872
- // change file
944
+ // saving the file without changing should not trigger a message query
873
945
  await fs.writeFile("./messages.json", JSON.stringify(messages));
874
- await new Promise((resolve) => setTimeout(resolve, 0));
946
+ await new Promise((resolve) => setTimeout(resolve, 200)); // file event will lock a file and be handled sequentially - give it time to pickup the change
947
+ // we didn't change the message we write into message.json - shouldn't change the messages
948
+ expect(counter).toBe(1);
949
+ // saving the file without changing should trigger a change
950
+ messages.data[0].variants[0].pattern[0].value = "changed";
951
+ await fs.writeFile("./messages.json", JSON.stringify(messages));
952
+ await new Promise((resolve) => setTimeout(resolve, 200)); // file event will lock a file and be handled sequentially - give it time to pickup the change
875
953
  expect(counter).toBe(2);
954
+ messages.data[0].variants[0].pattern[0].value = "changed3";
876
955
  // change file
877
956
  await fs.writeFile("./messages.json", JSON.stringify(messages));
878
- await new Promise((resolve) => setTimeout(resolve, 0));
957
+ await new Promise((resolve) => setTimeout(resolve, 200)); // file event will lock a file and be handled sequentially - give it time to pickup the change
879
958
  expect(counter).toBe(3);
880
959
  });
881
960
  });
@@ -32,6 +32,7 @@ describe("getVariant", () => {
32
32
  test("it should not throw error if selector is empty and match", () => {
33
33
  const mockMessage = {
34
34
  id: "mockMessage",
35
+ alias: {},
35
36
  selectors: [],
36
37
  variants: [
37
38
  {
@@ -57,6 +58,7 @@ describe("getVariant", () => {
57
58
  test("it should not throw error if selector is empty, return undefined", () => {
58
59
  const mockMessage = {
59
60
  id: "mockMessage",
61
+ alias: {},
60
62
  selectors: [],
61
63
  variants: [
62
64
  {
@@ -326,6 +328,7 @@ describe("updateVariant", () => {
326
328
  const getMockMessage = () => {
327
329
  return {
328
330
  id: "first-message",
331
+ alias: {},
329
332
  selectors: [
330
333
  { type: "VariableReference", name: "gender" },
331
334
  { type: "VariableReference", name: "guestOther" },
@@ -57,7 +57,9 @@ describe("loadMessages", () => {
57
57
  id: "plugin.namespace.placeholder",
58
58
  description: { en: "My plugin description" },
59
59
  displayName: { en: "My plugin" },
60
- loadMessages: async () => [{ id: "test", expressions: [], selectors: [], variants: [] }],
60
+ loadMessages: async () => [
61
+ { id: "test", alias: {}, expressions: [], selectors: [], variants: [] },
62
+ ],
61
63
  };
62
64
  const resolved = await resolvePlugins({
63
65
  plugins: [mockPlugin],
@@ -67,7 +69,7 @@ describe("loadMessages", () => {
67
69
  expect(await resolved.data.loadMessages({
68
70
  settings: {},
69
71
  nodeishFs: {},
70
- })).toEqual([{ id: "test", expressions: [], selectors: [], variants: [] }]);
72
+ })).toEqual([{ id: "test", alias: {}, expressions: [], selectors: [], variants: [] }]);
71
73
  });
72
74
  it("should collect an error if function is defined twice in multiple plugins", async () => {
73
75
  const mockPlugin = {
@@ -0,0 +1,5 @@
1
+ import { Message } from "../versionedInterfaces.js";
2
+ export declare function getMessageIdFromPath(path: string): string | undefined;
3
+ export declare function getPathFromMessageId(id: string): string;
4
+ export declare function stringifyMessage(message: Message): string;
5
+ //# sourceMappingURL=helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../../src/storage/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAW,MAAM,2BAA2B,CAAA;AAI5D,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,sBAahD;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,UAG9C;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,UAuBhD"}
@@ -0,0 +1,35 @@
1
+ import { Message, Variant } from "../versionedInterfaces.js";
2
+ const fileExtension = ".json";
3
+ export function getMessageIdFromPath(path) {
4
+ if (!path.endsWith(fileExtension)) {
5
+ return;
6
+ }
7
+ const cleanedPath = path.replace(/\/$/, ""); // This regex matches a trailing slash and replaces it with an empty string
8
+ const messageFileName = cleanedPath.split("/").join("_"); // we split by the first leading namespace or _ separator - make sure slashes don't exit in the id
9
+ // const messageFileName = pathParts.at(-1)!
10
+ const lastDotIndex = messageFileName.lastIndexOf(".");
11
+ // Extract until the last dot (excluding the dot)
12
+ return messageFileName.slice(0, Math.max(0, lastDotIndex));
13
+ }
14
+ export function getPathFromMessageId(id) {
15
+ const path = id.replace("_", "/") + fileExtension;
16
+ return path;
17
+ }
18
+ export function stringifyMessage(message) {
19
+ // create a new object do specify key output order
20
+ const messageWithSortedKeys = {};
21
+ for (const key of Object.keys(message).sort()) {
22
+ messageWithSortedKeys[key] = message[key];
23
+ }
24
+ // lets order variants as well
25
+ messageWithSortedKeys["variants"] = messageWithSortedKeys["variants"].sort((variantA, variantB) => {
26
+ // First, compare by language
27
+ const languageComparison = variantA.languageTag.localeCompare(variantB.languageTag);
28
+ // If languages are the same, compare by match
29
+ if (languageComparison === 0) {
30
+ return variantA.match.join("-").localeCompare(variantB.match.join("-"));
31
+ }
32
+ return languageComparison;
33
+ });
34
+ return JSON.stringify(messageWithSortedKeys, undefined, 4);
35
+ }
@@ -0,0 +1,3 @@
1
+ export declare function randomHumanId(): string;
2
+ export declare function humanIdHash(value: string, offset?: number): string;
3
+ //# sourceMappingURL=human-readable-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"human-readable-id.d.ts","sourceRoot":"","sources":["../../../src/storage/human-id/human-readable-id.ts"],"names":[],"mappings":"AAIA,wBAAgB,aAAa,WAI5B;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAAU,UAgB5D"}
@@ -0,0 +1,20 @@
1
+ // we use murmur for best distribution https://medium.com/miro-engineering/choosing-a-hash-function-to-solve-a-data-sharding-problem-c656259e2b54
2
+ import murmurhash3 from "murmurhash3js";
3
+ import { adjectives, animals, adverbs, verbs } from "./words.js";
4
+ export function randomHumanId() {
5
+ return `${adjectives[Math.floor(Math.random() * 256)]}_${adjectives[Math.floor(Math.random() * 256)]}_${animals[Math.floor(Math.random() * 256)]}_${verbs[Math.floor(Math.random() * 256)]}`;
6
+ }
7
+ export function humanIdHash(value, offset = 0) {
8
+ // Seed value can be any arbitrary value
9
+ const seed = 42;
10
+ // Use MurmurHash3 to produce a 32-bit hash
11
+ const hash32 = murmurhash3.x86.hash32(value, seed);
12
+ // Add 1 and take modulo 2^32 to fit within 32 bits
13
+ const hash32WithOffset = (hash32 + offset) >>> 0;
14
+ // Extract four 8-bit parts
15
+ const part1 = (hash32WithOffset >>> 24) & 0xff;
16
+ const part2 = (hash32WithOffset >>> 16) & 0xff;
17
+ const part3 = (hash32WithOffset >>> 8) & 0xff;
18
+ const part4 = hash32WithOffset & 0xff;
19
+ return `${adjectives[part1]}_${animals[part2]}_${verbs[part3]}_${adverbs[part4]}`;
20
+ }
@@ -0,0 +1,5 @@
1
+ export declare const animals: string[];
2
+ export declare const adjectives: string[];
3
+ export declare const adverbs: string[];
4
+ export declare const verbs: string[];
5
+ //# sourceMappingURL=words.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"words.d.ts","sourceRoot":"","sources":["../../../src/storage/human-id/words.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,UAiQnB,CAAA;AAED,eAAO,MAAM,UAAU,UAiQtB,CAAA;AAED,eAAO,MAAM,OAAO,UAiQnB,CAAA;AAED,eAAO,MAAM,KAAK,UAiQjB,CAAA"}