@inlang/sdk 0.26.4 → 0.27.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 +3 -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 +472 -39
  22. package/dist/loadProject.test.js +91 -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 +12 -8
  42. package/src/adapter/solidAdapter.test.ts +76 -4
  43. package/src/api.ts +4 -0
  44. package/src/createMessageLintReportsQuery.ts +46 -34
  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 +106 -14
  52. package/src/loadProject.ts +655 -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
  {
@@ -449,7 +491,7 @@ describe("functionality", () => {
449
491
  id: "plugin.project.i18next",
450
492
  description: { en: "Mock plugin description" },
451
493
  displayName: { en: "Mock Plugin" },
452
- loadMessages: () => [{ id: "some-message", selectors: [], variants: [] }],
494
+ loadMessages: () => [{ id: "some-message", alias: {}, selectors: [], variants: [] }],
453
495
  saveMessages: () => undefined,
454
496
  };
455
497
  const repo = await mockRepo();
@@ -492,7 +534,7 @@ describe("functionality", () => {
492
534
  id: "plugin.project.i18next",
493
535
  description: { en: "Mock plugin description" },
494
536
  displayName: { en: "Mock Plugin" },
495
- loadMessages: () => [{ id: "some-message", selectors: [], variants: [] }],
537
+ loadMessages: () => [{ id: "some-message", alias: {}, selectors: [], variants: [] }],
496
538
  saveMessages: () => undefined,
497
539
  };
498
540
  const repo = await mockRepo();
@@ -563,6 +605,20 @@ describe("functionality", () => {
563
605
  expect(Object.values(project.query.messages.getAll())).toEqual(exampleMessages);
564
606
  });
565
607
  });
608
+ describe("messages with aliases", () => {
609
+ it("should return the messages", async () => {
610
+ const repo = await mockRepo();
611
+ const fs = repo.nodeishFs;
612
+ await fs.mkdir("/user/project.inlang", { recursive: true });
613
+ await fs.writeFile("/user/project.inlang/settings.json", JSON.stringify({ ...settings, experimental: { aliases: true } }));
614
+ const project = await loadProject({
615
+ projectPath: "/user/project.inlang",
616
+ repo,
617
+ _import,
618
+ });
619
+ expect(Object.values(project.query.messages.getAll())).toEqual(exampleAliasedMessages);
620
+ });
621
+ });
566
622
  describe("query", () => {
567
623
  it("should call saveMessages() on updates", async () => {
568
624
  const repo = await mockRepo();
@@ -600,6 +656,7 @@ describe("functionality", () => {
600
656
  where: { id: "a" },
601
657
  data: {
602
658
  id: "a",
659
+ alias: {},
603
660
  selectors: [],
604
661
  variants: [
605
662
  {
@@ -629,6 +686,7 @@ describe("functionality", () => {
629
686
  where: { id: "b" },
630
687
  data: {
631
688
  id: "b",
689
+ alias: {},
632
690
  selectors: [],
633
691
  variants: [
634
692
  {
@@ -654,31 +712,33 @@ describe("functionality", () => {
654
712
  ],
655
713
  },
656
714
  });
657
- await new Promise((resolve) => setTimeout(resolve, 510));
715
+ // lets wait for the next tick
716
+ await new Promise((resolve) => setTimeout(resolve, 100));
658
717
  expect(mockSaveFn.mock.calls.length).toBe(1);
659
718
  expect(mockSaveFn.mock.calls[0][0].settings).toStrictEqual(settings);
660
719
  expect(Object.values(mockSaveFn.mock.calls[0][0].messages)).toStrictEqual([
661
720
  {
662
721
  id: "a",
722
+ alias: {},
663
723
  selectors: [],
664
724
  variants: [
665
725
  {
666
- languageTag: "en",
726
+ languageTag: "de",
667
727
  match: [],
668
728
  pattern: [
669
729
  {
670
730
  type: "Text",
671
- value: "a en",
731
+ value: "a de",
672
732
  },
673
733
  ],
674
734
  },
675
735
  {
676
- languageTag: "de",
736
+ languageTag: "en",
677
737
  match: [],
678
738
  pattern: [
679
739
  {
680
740
  type: "Text",
681
- value: "a de",
741
+ value: "a en",
682
742
  },
683
743
  ],
684
744
  },
@@ -686,25 +746,26 @@ describe("functionality", () => {
686
746
  },
687
747
  {
688
748
  id: "b",
749
+ alias: {},
689
750
  selectors: [],
690
751
  variants: [
691
752
  {
692
- languageTag: "en",
753
+ languageTag: "de",
693
754
  match: [],
694
755
  pattern: [
695
756
  {
696
757
  type: "Text",
697
- value: "b en",
758
+ value: "b de",
698
759
  },
699
760
  ],
700
761
  },
701
762
  {
702
- languageTag: "de",
763
+ languageTag: "en",
703
764
  match: [],
704
765
  pattern: [
705
766
  {
706
767
  type: "Text",
707
- value: "b de",
768
+ value: "b en",
708
769
  },
709
770
  ],
710
771
  },
@@ -766,6 +827,14 @@ describe("functionality", () => {
766
827
  await new Promise((resolve) => setTimeout(resolve, 510));
767
828
  expect(mockSaveFn.mock.calls.length).toBe(1);
768
829
  expect(mockSaveFn.mock.calls[0][0].messages).toHaveLength(4);
830
+ project.query.messages.create({ data: createMessage("fifth", { en: "fifth message" }) });
831
+ await new Promise((resolve) => setTimeout(resolve, 510));
832
+ expect(mockSaveFn.mock.calls.length).toBe(2);
833
+ expect(mockSaveFn.mock.calls[1][0].messages).toHaveLength(5);
834
+ project.query.messages.delete({ where: { id: "fourth" } });
835
+ await new Promise((resolve) => setTimeout(resolve, 510));
836
+ expect(mockSaveFn.mock.calls.length).toBe(3);
837
+ expect(mockSaveFn.mock.calls[2][0].messages).toHaveLength(4);
769
838
  });
770
839
  });
771
840
  describe("lint", () => {
@@ -868,14 +937,22 @@ describe("functionality", () => {
868
937
  project.query.messages.getAll.subscribe(() => {
869
938
  counter = counter + 1;
870
939
  });
940
+ // subscribe fires once
871
941
  expect(counter).toBe(1);
872
- // change file
942
+ // saving the file without changing should not trigger a message query
873
943
  await fs.writeFile("./messages.json", JSON.stringify(messages));
874
- await new Promise((resolve) => setTimeout(resolve, 0));
944
+ await new Promise((resolve) => setTimeout(resolve, 200)); // file event will lock a file and be handled sequentially - give it time to pickup the change
945
+ // we didn't change the message we write into message.json - shouldn't change the messages
946
+ expect(counter).toBe(1);
947
+ // saving the file without changing should trigger a change
948
+ messages.data[0].variants[0].pattern[0].value = "changed";
949
+ await fs.writeFile("./messages.json", JSON.stringify(messages));
950
+ 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
951
  expect(counter).toBe(2);
952
+ messages.data[0].variants[0].pattern[0].value = "changed3";
876
953
  // change file
877
954
  await fs.writeFile("./messages.json", JSON.stringify(messages));
878
- await new Promise((resolve) => setTimeout(resolve, 0));
955
+ 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
956
  expect(counter).toBe(3);
880
957
  });
881
958
  });
@@ -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"}