@inlang/sdk 0.1.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 +25 -0
- package/dist/adapter/solidAdapter.d.ts +32 -0
- package/dist/adapter/solidAdapter.d.ts.map +1 -0
- package/dist/adapter/solidAdapter.js +39 -0
- package/dist/adapter/solidAdapter.test.d.ts +2 -0
- package/dist/adapter/solidAdapter.test.d.ts.map +1 -0
- package/dist/adapter/solidAdapter.test.js +284 -0
- package/dist/api.d.ts +88 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +1 -0
- package/dist/createMessageLintReportsQuery.d.ts +9 -0
- package/dist/createMessageLintReportsQuery.d.ts.map +1 -0
- package/dist/createMessageLintReportsQuery.js +48 -0
- package/dist/createMessagesQuery.d.ts +7 -0
- package/dist/createMessagesQuery.d.ts.map +1 -0
- package/dist/createMessagesQuery.js +57 -0
- package/dist/createMessagesQuery.test.d.ts +2 -0
- package/dist/createMessagesQuery.test.d.ts.map +1 -0
- package/dist/createMessagesQuery.test.js +304 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +39 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/lint/index.d.ts +3 -0
- package/dist/lint/index.d.ts.map +1 -0
- package/dist/lint/index.js +2 -0
- package/dist/lint/message/errors.d.ts +7 -0
- package/dist/lint/message/errors.d.ts.map +1 -0
- package/dist/lint/message/errors.js +9 -0
- package/dist/lint/message/lintMessages.d.ts +17 -0
- package/dist/lint/message/lintMessages.d.ts.map +1 -0
- package/dist/lint/message/lintMessages.js +12 -0
- package/dist/lint/message/lintMessages.test.d.ts +2 -0
- package/dist/lint/message/lintMessages.test.d.ts.map +1 -0
- package/dist/lint/message/lintMessages.test.js +105 -0
- package/dist/lint/message/lintSingleMessage.d.ts +23 -0
- package/dist/lint/message/lintSingleMessage.d.ts.map +1 -0
- package/dist/lint/message/lintSingleMessage.js +36 -0
- package/dist/lint/message/lintSingleMessage.test.d.ts +2 -0
- package/dist/lint/message/lintSingleMessage.test.d.ts.map +1 -0
- package/dist/lint/message/lintSingleMessage.test.js +155 -0
- package/dist/messages/errors.d.ts +13 -0
- package/dist/messages/errors.d.ts.map +1 -0
- package/dist/messages/errors.js +18 -0
- package/dist/messages/index.d.ts +3 -0
- package/dist/messages/index.d.ts.map +1 -0
- package/dist/messages/index.js +2 -0
- package/dist/messages/variant.d.ts +46 -0
- package/dist/messages/variant.d.ts.map +1 -0
- package/dist/messages/variant.js +177 -0
- package/dist/messages/variant.test.d.ts +2 -0
- package/dist/messages/variant.test.d.ts.map +1 -0
- package/dist/messages/variant.test.js +379 -0
- package/dist/openInlangProject.d.ts +18 -0
- package/dist/openInlangProject.d.ts.map +1 -0
- package/dist/openInlangProject.js +226 -0
- package/dist/openInlangProject.test.d.ts +2 -0
- package/dist/openInlangProject.test.d.ts.map +1 -0
- package/dist/openInlangProject.test.js +627 -0
- package/dist/parseConfig.d.ts +8 -0
- package/dist/parseConfig.d.ts.map +1 -0
- package/dist/parseConfig.js +26 -0
- package/dist/reactivity/map.d.ts +66 -0
- package/dist/reactivity/map.d.ts.map +1 -0
- package/dist/reactivity/map.js +143 -0
- package/dist/reactivity/solid.d.ts +12 -0
- package/dist/reactivity/solid.d.ts.map +1 -0
- package/dist/reactivity/solid.js +13 -0
- package/dist/reactivity/trigger.d.ts +11 -0
- package/dist/reactivity/trigger.d.ts.map +1 -0
- package/dist/reactivity/trigger.js +46 -0
- package/dist/resolve-modules/errors.d.ts +34 -0
- package/dist/resolve-modules/errors.d.ts.map +1 -0
- package/dist/resolve-modules/errors.js +35 -0
- package/dist/resolve-modules/import.d.ts +35 -0
- package/dist/resolve-modules/import.d.ts.map +1 -0
- package/dist/resolve-modules/import.js +40 -0
- package/dist/resolve-modules/import.test.d.ts +2 -0
- package/dist/resolve-modules/import.test.d.ts.map +1 -0
- package/dist/resolve-modules/import.test.js +45 -0
- package/dist/resolve-modules/index.d.ts +3 -0
- package/dist/resolve-modules/index.d.ts.map +1 -0
- package/dist/resolve-modules/index.js +2 -0
- package/dist/resolve-modules/message-lint-rules/errors.d.ts +8 -0
- package/dist/resolve-modules/message-lint-rules/errors.d.ts.map +1 -0
- package/dist/resolve-modules/message-lint-rules/errors.js +8 -0
- package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.d.ts +9 -0
- package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.d.ts.map +1 -0
- package/dist/resolve-modules/message-lint-rules/resolveMessageLintRules.js +21 -0
- package/dist/resolve-modules/plugins/errors.d.ts +28 -0
- package/dist/resolve-modules/plugins/errors.d.ts.map +1 -0
- package/dist/resolve-modules/plugins/errors.js +44 -0
- package/dist/resolve-modules/plugins/resolvePlugins.d.ts +3 -0
- package/dist/resolve-modules/plugins/resolvePlugins.d.ts.map +1 -0
- package/dist/resolve-modules/plugins/resolvePlugins.js +108 -0
- package/dist/resolve-modules/plugins/resolvePlugins.test.d.ts +2 -0
- package/dist/resolve-modules/plugins/resolvePlugins.test.d.ts.map +1 -0
- package/dist/resolve-modules/plugins/resolvePlugins.test.js +289 -0
- package/dist/resolve-modules/plugins/types.d.ts +60 -0
- package/dist/resolve-modules/plugins/types.d.ts.map +1 -0
- package/dist/resolve-modules/plugins/types.js +1 -0
- package/dist/resolve-modules/plugins/types.test.d.ts +2 -0
- package/dist/resolve-modules/plugins/types.test.d.ts.map +1 -0
- package/dist/resolve-modules/plugins/types.test.js +49 -0
- package/dist/resolve-modules/resolveModules.d.ts +3 -0
- package/dist/resolve-modules/resolveModules.d.ts.map +1 -0
- package/dist/resolve-modules/resolveModules.js +70 -0
- package/dist/resolve-modules/resolveModules.test.d.ts +2 -0
- package/dist/resolve-modules/resolveModules.test.d.ts.map +1 -0
- package/dist/resolve-modules/resolveModules.test.js +143 -0
- package/dist/resolve-modules/types.d.ts +62 -0
- package/dist/resolve-modules/types.d.ts.map +1 -0
- package/dist/resolve-modules/types.js +1 -0
- package/dist/test-utilities/createMessage.d.ts +17 -0
- package/dist/test-utilities/createMessage.d.ts.map +1 -0
- package/dist/test-utilities/createMessage.js +16 -0
- package/dist/test-utilities/createMessage.test.d.ts +2 -0
- package/dist/test-utilities/createMessage.test.d.ts.map +1 -0
- package/dist/test-utilities/createMessage.test.js +91 -0
- package/dist/test-utilities/index.d.ts +2 -0
- package/dist/test-utilities/index.d.ts.map +1 -0
- package/dist/test-utilities/index.js +1 -0
- package/dist/versionedInterfaces.d.ts +8 -0
- package/dist/versionedInterfaces.d.ts.map +1 -0
- package/dist/versionedInterfaces.js +8 -0
- package/package.json +58 -0
- package/src/adapter/solidAdapter.test.ts +363 -0
- package/src/adapter/solidAdapter.ts +77 -0
- package/src/api.ts +86 -0
- package/src/createMessageLintReportsQuery.ts +77 -0
- package/src/createMessagesQuery.test.ts +435 -0
- package/src/createMessagesQuery.ts +64 -0
- package/src/errors.ts +46 -0
- package/src/index.ts +29 -0
- package/src/lint/index.ts +2 -0
- package/src/lint/message/errors.ts +9 -0
- package/src/lint/message/lintMessages.test.ts +122 -0
- package/src/lint/message/lintMessages.ts +33 -0
- package/src/lint/message/lintSingleMessage.test.ts +183 -0
- package/src/lint/message/lintSingleMessage.ts +62 -0
- package/src/messages/errors.ts +25 -0
- package/src/messages/index.ts +2 -0
- package/src/messages/variant.test.ts +444 -0
- package/src/messages/variant.ts +242 -0
- package/src/openInlangProject.test.ts +734 -0
- package/src/openInlangProject.ts +337 -0
- package/src/parseConfig.ts +33 -0
- package/src/reactivity/map.ts +135 -0
- package/src/reactivity/solid.ts +36 -0
- package/src/reactivity/trigger.ts +46 -0
- package/src/resolve-modules/errors.ts +39 -0
- package/src/resolve-modules/import.test.ts +58 -0
- package/src/resolve-modules/import.ts +69 -0
- package/src/resolve-modules/index.ts +2 -0
- package/src/resolve-modules/message-lint-rules/errors.ts +9 -0
- package/src/resolve-modules/message-lint-rules/resolveMessageLintRules.ts +24 -0
- package/src/resolve-modules/plugins/errors.ts +57 -0
- package/src/resolve-modules/plugins/resolvePlugins.test.ts +340 -0
- package/src/resolve-modules/plugins/resolvePlugins.ts +170 -0
- package/src/resolve-modules/plugins/types.test.ts +57 -0
- package/src/resolve-modules/plugins/types.ts +77 -0
- package/src/resolve-modules/resolveModules.test.ts +176 -0
- package/src/resolve-modules/resolveModules.ts +97 -0
- package/src/resolve-modules/types.ts +71 -0
- package/src/test-utilities/createMessage.test.ts +100 -0
- package/src/test-utilities/createMessage.ts +20 -0
- package/src/test-utilities/index.ts +1 -0
- package/src/versionedInterfaces.ts +9 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
+
import { createVariant, getVariant, updateVariantPattern } from "./variant.js";
|
|
3
|
+
import { describe, test, expect } from "vitest";
|
|
4
|
+
import { MessagePatternsForLanguageTagDoNotExistError, MessageVariantAlreadyExistsError, MessageVariantDoesNotExistError, } from "./errors.js";
|
|
5
|
+
describe("getVariant", () => {
|
|
6
|
+
test("should return the correct variant of a message", () => {
|
|
7
|
+
const mockMessage = getMockMessage();
|
|
8
|
+
const variant = getVariant(mockMessage, {
|
|
9
|
+
where: {
|
|
10
|
+
languageTag: "en",
|
|
11
|
+
selectors: { gender: "female", guestOther: "1" },
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
expect(variant?.pattern[0]).toStrictEqual({
|
|
15
|
+
type: "Text",
|
|
16
|
+
value: "{$hostName} invites {$guestName} to her party.",
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
test("should return the correct fallback because the exact match does not exist", () => {
|
|
20
|
+
const mockMessage = getMockMessage();
|
|
21
|
+
const variant = getVariant(mockMessage, {
|
|
22
|
+
where: {
|
|
23
|
+
languageTag: "en",
|
|
24
|
+
selectors: { gender: "female", guestOther: "0" },
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
expect(variant?.pattern[0]).toStrictEqual({
|
|
28
|
+
type: "Text",
|
|
29
|
+
value: "{$hostName} invites {$guestName} and {$guestsOther} other people to her party.",
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
test("it should return undefined (but never throw an error) if selectors is an empty array", () => {
|
|
33
|
+
const mockMessage = {
|
|
34
|
+
id: "mockMessage",
|
|
35
|
+
selectors: [],
|
|
36
|
+
variants: [
|
|
37
|
+
{
|
|
38
|
+
languageTag: "en",
|
|
39
|
+
pattern: [{ type: "Text", value: "Gender male" }],
|
|
40
|
+
match: {
|
|
41
|
+
gender: "male",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
languageTag: "de",
|
|
46
|
+
pattern: [{ type: "Text", value: "Veraltete Übersetzung" }],
|
|
47
|
+
match: {},
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
const variant = getVariant(mockMessage, {
|
|
52
|
+
where: {
|
|
53
|
+
languageTag: "fr",
|
|
54
|
+
selectors: {},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
expect(variant).toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
test("the matcher should resolve not existing selectors to '*'", () => {
|
|
60
|
+
const mockMessage = getMockMessage();
|
|
61
|
+
const variant = getVariant(mockMessage, {
|
|
62
|
+
where: {
|
|
63
|
+
languageTag: "en",
|
|
64
|
+
selectors: { guestOther: "0" },
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
expect(variant?.pattern[0]).toStrictEqual({
|
|
68
|
+
type: "Text",
|
|
69
|
+
value: "{$hostName} does not give a party.",
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
test("empty selector should result in '*','*'", () => {
|
|
73
|
+
const mockMessage = getMockMessage();
|
|
74
|
+
const variant = getVariant(mockMessage, {
|
|
75
|
+
where: {
|
|
76
|
+
languageTag: "en",
|
|
77
|
+
selectors: {},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
expect(variant?.pattern[0]).toStrictEqual({
|
|
81
|
+
type: "Text",
|
|
82
|
+
value: "{$hostName} invites {$guestName} and {$guestsOther} other people to their party.",
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
test("non existing selector values should be resolved", () => {
|
|
86
|
+
const mockMessage = getMockMessage();
|
|
87
|
+
const variant = getVariant(mockMessage, {
|
|
88
|
+
where: {
|
|
89
|
+
languageTag: "en",
|
|
90
|
+
selectors: { gender: "trans", guestOther: "2" },
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
expect(variant?.pattern[0]).toStrictEqual({
|
|
94
|
+
type: "Text",
|
|
95
|
+
value: "{$hostName} invites {$guestName} and one other person to their party.",
|
|
96
|
+
});
|
|
97
|
+
const variant2 = getVariant(mockMessage, {
|
|
98
|
+
where: {
|
|
99
|
+
languageTag: "en",
|
|
100
|
+
selectors: { gender: "male", guestOther: "8" },
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
expect(variant2?.pattern[0]).toStrictEqual({
|
|
104
|
+
type: "Text",
|
|
105
|
+
value: "{$hostName} invites {$guestName} and {$guestsOther} other people to his party.",
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
test("should return undefined of no variant matches", () => {
|
|
109
|
+
const mockMessage = getMockMessage();
|
|
110
|
+
mockMessage.variants = [
|
|
111
|
+
...mockMessage.variants.filter((v) => v.languageTag === "en" && (v.match.gender !== "*" || v.match.guestOther !== "*")),
|
|
112
|
+
];
|
|
113
|
+
const variant = getVariant(mockMessage, {
|
|
114
|
+
where: {
|
|
115
|
+
languageTag: "en",
|
|
116
|
+
selectors: {},
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
expect(variant).toBeUndefined();
|
|
120
|
+
});
|
|
121
|
+
test("should return undefined if a variant for specific language does not exist", () => {
|
|
122
|
+
const mockMessage = getMockMessage();
|
|
123
|
+
const variant = getVariant(mockMessage, {
|
|
124
|
+
where: {
|
|
125
|
+
languageTag: "de",
|
|
126
|
+
selectors: { gender: "female", guestOther: "1" },
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
expect(variant).toBeUndefined();
|
|
130
|
+
});
|
|
131
|
+
test("should return the catch all variant if no selector defined", () => {
|
|
132
|
+
const mockMessage = {};
|
|
133
|
+
mockMessage.variants = [
|
|
134
|
+
{
|
|
135
|
+
languageTag: "en",
|
|
136
|
+
match: {},
|
|
137
|
+
pattern: [
|
|
138
|
+
{
|
|
139
|
+
type: "Text",
|
|
140
|
+
value: "test",
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
const variant = getVariant(mockMessage, {
|
|
146
|
+
where: {
|
|
147
|
+
languageTag: "en",
|
|
148
|
+
selectors: {},
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
// should return the female variant
|
|
152
|
+
expect(variant?.pattern[0]).toStrictEqual({
|
|
153
|
+
type: "Text",
|
|
154
|
+
value: "test",
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
describe("createVariant", () => {
|
|
159
|
+
test("should create a variant for a message", () => {
|
|
160
|
+
const mockMessage = getMockMessage();
|
|
161
|
+
const newVariant = {
|
|
162
|
+
languageTag: "en",
|
|
163
|
+
match: { gender: "female", guestOther: "0" },
|
|
164
|
+
pattern: [],
|
|
165
|
+
};
|
|
166
|
+
const message = createVariant(mockMessage, {
|
|
167
|
+
data: newVariant,
|
|
168
|
+
});
|
|
169
|
+
// should return the female variant
|
|
170
|
+
expect(message.data.variants.find((v) => v.languageTag === "en" && v.match.gender === "female" && v.match.guestOther === "0")?.pattern).toStrictEqual([]);
|
|
171
|
+
});
|
|
172
|
+
test("should create a variant, also if matcher are not full defined", () => {
|
|
173
|
+
const mockMessage = getMockMessage();
|
|
174
|
+
mockMessage.variants = [
|
|
175
|
+
...mockMessage.variants.filter((v) => v.languageTag === "en" && (v.match.gender !== "*" || v.match.guestOther !== "*")),
|
|
176
|
+
];
|
|
177
|
+
const message = createVariant(mockMessage, {
|
|
178
|
+
data: {
|
|
179
|
+
languageTag: "en",
|
|
180
|
+
match: {},
|
|
181
|
+
pattern: [],
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
// should return the female variant
|
|
185
|
+
expect(message.data.variants.find((v) => v.languageTag === "en" && v.match.gender === "*" && v.match.guestOther === "*")?.pattern).toStrictEqual([]);
|
|
186
|
+
});
|
|
187
|
+
test("should return error if variant matches", () => {
|
|
188
|
+
const mockMessage = getMockMessage();
|
|
189
|
+
const variant = createVariant(mockMessage, {
|
|
190
|
+
data: {
|
|
191
|
+
languageTag: "en",
|
|
192
|
+
match: { gender: "male", guestOther: "1" },
|
|
193
|
+
pattern: [],
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
// should return the female variant
|
|
197
|
+
expect(variant.data).toBeUndefined();
|
|
198
|
+
expect(variant.error).toBeInstanceOf(MessageVariantAlreadyExistsError);
|
|
199
|
+
});
|
|
200
|
+
test("should not return error if set of variants for specific language does not exist", () => {
|
|
201
|
+
const mockMessage = getMockMessage();
|
|
202
|
+
const variant = createVariant(mockMessage, {
|
|
203
|
+
data: {
|
|
204
|
+
languageTag: "de",
|
|
205
|
+
match: { gender: "female", guestOther: "1" },
|
|
206
|
+
pattern: [],
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
// should return the female variant
|
|
210
|
+
expect(variant.data).toBeDefined();
|
|
211
|
+
expect(variant.error).toBeUndefined();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
describe("updateVariant", () => {
|
|
215
|
+
test("should update a variant of a message", () => {
|
|
216
|
+
const mockMessage = getMockMessage();
|
|
217
|
+
const message = updateVariantPattern(mockMessage, {
|
|
218
|
+
where: {
|
|
219
|
+
languageTag: "en",
|
|
220
|
+
selectors: { gender: "female", guestOther: "1" },
|
|
221
|
+
},
|
|
222
|
+
data: [],
|
|
223
|
+
});
|
|
224
|
+
// should return the female variant
|
|
225
|
+
expect(message.data.variants.find((v) => v.languageTag === "en" && v.match.gender === "female" && v.match.guestOther === "1")?.pattern).toStrictEqual([]);
|
|
226
|
+
});
|
|
227
|
+
test("should update a variant, also if matcher are not full defined", () => {
|
|
228
|
+
const mockMessage = getMockMessage();
|
|
229
|
+
const message = updateVariantPattern(mockMessage, {
|
|
230
|
+
where: {
|
|
231
|
+
languageTag: "en",
|
|
232
|
+
selectors: {},
|
|
233
|
+
},
|
|
234
|
+
data: [],
|
|
235
|
+
});
|
|
236
|
+
// should return the female variant
|
|
237
|
+
expect(message.data.variants.find((v) => v.languageTag === "en" && v.match.gender === "*" && v.match.guestOther === "*")?.pattern).toStrictEqual([]);
|
|
238
|
+
});
|
|
239
|
+
test("should return error if no variant matches", () => {
|
|
240
|
+
const mockMessage = getMockMessage();
|
|
241
|
+
mockMessage.variants = [
|
|
242
|
+
...mockMessage.variants.filter((v) => v.languageTag === "en" && (v.match.gender !== "*" || v.match.guestOther !== "*")),
|
|
243
|
+
];
|
|
244
|
+
const variant = updateVariantPattern(mockMessage, {
|
|
245
|
+
where: {
|
|
246
|
+
languageTag: "en",
|
|
247
|
+
selectors: {},
|
|
248
|
+
},
|
|
249
|
+
data: [],
|
|
250
|
+
});
|
|
251
|
+
// should return the female variant
|
|
252
|
+
expect(variant.data).toBeUndefined();
|
|
253
|
+
expect(variant.error).toBeInstanceOf(MessageVariantDoesNotExistError);
|
|
254
|
+
});
|
|
255
|
+
test("should return error if set of variants for specific language does not exist", () => {
|
|
256
|
+
const mockMessage = getMockMessage();
|
|
257
|
+
const variant = updateVariantPattern(mockMessage, {
|
|
258
|
+
where: {
|
|
259
|
+
languageTag: "de",
|
|
260
|
+
selectors: {},
|
|
261
|
+
},
|
|
262
|
+
data: [],
|
|
263
|
+
});
|
|
264
|
+
// should return the female variant
|
|
265
|
+
expect(variant.data).toBeUndefined();
|
|
266
|
+
expect(variant.error).toBeInstanceOf(MessagePatternsForLanguageTagDoNotExistError);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
const getMockMessage = () => {
|
|
270
|
+
return {
|
|
271
|
+
id: "first-message",
|
|
272
|
+
selectors: [
|
|
273
|
+
{ type: "VariableReference", name: "gender" },
|
|
274
|
+
{ type: "VariableReference", name: "guestOther" },
|
|
275
|
+
],
|
|
276
|
+
variants: [
|
|
277
|
+
{
|
|
278
|
+
languageTag: "en",
|
|
279
|
+
match: { gender: "female", guestOther: "1" },
|
|
280
|
+
pattern: [
|
|
281
|
+
{
|
|
282
|
+
type: "Text",
|
|
283
|
+
value: "{$hostName} invites {$guestName} to her party.",
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
languageTag: "en",
|
|
289
|
+
match: { gender: "female", guestOther: "2" },
|
|
290
|
+
pattern: [
|
|
291
|
+
{
|
|
292
|
+
type: "Text",
|
|
293
|
+
value: "{$hostName} invites {$guestName} and one other person to her party.",
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
languageTag: "en",
|
|
299
|
+
match: { gender: "female", guestOther: "*" },
|
|
300
|
+
pattern: [
|
|
301
|
+
{
|
|
302
|
+
type: "Text",
|
|
303
|
+
value: "{$hostName} invites {$guestName} and {$guestsOther} other people to her party.",
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
languageTag: "en",
|
|
309
|
+
match: { gender: "male", guestOther: "1" },
|
|
310
|
+
pattern: [
|
|
311
|
+
{
|
|
312
|
+
type: "Text",
|
|
313
|
+
value: "{$hostName} invites {$guestName} to his party.",
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
languageTag: "en",
|
|
319
|
+
match: { gender: "male", guestOther: "2" },
|
|
320
|
+
pattern: [
|
|
321
|
+
{
|
|
322
|
+
type: "Text",
|
|
323
|
+
value: "{$hostName} invites {$guestName} and one other person to his party.",
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
languageTag: "en",
|
|
329
|
+
match: { gender: "male", guestOther: "*" },
|
|
330
|
+
pattern: [
|
|
331
|
+
{
|
|
332
|
+
type: "Text",
|
|
333
|
+
value: "{$hostName} invites {$guestName} and {$guestsOther} other people to his party.",
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
languageTag: "en",
|
|
339
|
+
match: { gender: "*", guestOther: "0" },
|
|
340
|
+
pattern: [
|
|
341
|
+
{
|
|
342
|
+
type: "Text",
|
|
343
|
+
value: "{$hostName} does not give a party.",
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
languageTag: "en",
|
|
349
|
+
match: { gender: "*", guestOther: "1" },
|
|
350
|
+
pattern: [
|
|
351
|
+
{
|
|
352
|
+
type: "Text",
|
|
353
|
+
value: "{$hostName} invites {$guestName} to their party.",
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
languageTag: "en",
|
|
359
|
+
match: { gender: "*", guestOther: "2" },
|
|
360
|
+
pattern: [
|
|
361
|
+
{
|
|
362
|
+
type: "Text",
|
|
363
|
+
value: "{$hostName} invites {$guestName} and one other person to their party.",
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
languageTag: "en",
|
|
369
|
+
match: { gender: "*", guestOther: "*" },
|
|
370
|
+
pattern: [
|
|
371
|
+
{
|
|
372
|
+
type: "Text",
|
|
373
|
+
value: "{$hostName} invites {$guestName} and {$guestsOther} other people to their party.",
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
};
|
|
379
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { InlangProject, Subscribable } from "./api.js";
|
|
2
|
+
import { type ImportFunction } from "./resolve-modules/index.js";
|
|
3
|
+
import { type NodeishFilesystemSubset } from "./versionedInterfaces.js";
|
|
4
|
+
/**
|
|
5
|
+
* Creates an inlang instance.
|
|
6
|
+
*
|
|
7
|
+
* - Use `_import` to pass a custom import function for testing,
|
|
8
|
+
* and supporting legacy resolvedModules such as CJS.
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
export declare const openInlangProject: (args: {
|
|
12
|
+
projectFilePath: string;
|
|
13
|
+
nodeishFs: NodeishFilesystemSubset;
|
|
14
|
+
_import?: ImportFunction | undefined;
|
|
15
|
+
_capture?: ((id: string, props: Record<string, unknown>) => void) | undefined;
|
|
16
|
+
}) => Promise<InlangProject>;
|
|
17
|
+
export declare function createSubscribable<T>(signal: () => T): Subscribable<T>;
|
|
18
|
+
//# sourceMappingURL=openInlangProject.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openInlangProject.d.ts","sourceRoot":"","sources":["../src/openInlangProject.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,aAAa,EAGb,YAAY,EACZ,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,KAAK,cAAc,EAAkB,MAAM,4BAA4B,CAAA;AAehF,OAAO,EAA0B,KAAK,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAK/F;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB;qBACZ,MAAM;eACZ,uBAAuB;;qBAElB,MAAM,SAAS,OAAO,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI;MAC5D,QAAQ,aAAa,CAwLxB,CAAA;AAyGD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAQtE"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { resolveModules } from "./resolve-modules/index.js";
|
|
2
|
+
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
3
|
+
import { Value } from "@sinclair/typebox/value";
|
|
4
|
+
import { ProjectFilePathNotFoundError, ProjectFileJSONSyntaxError, InvalidConfigError, NoPluginProvidesLoadOrSaveMessagesError, PluginLoadMessagesError, PluginSaveMessagesError, } from "./errors.js";
|
|
5
|
+
import { createRoot, createSignal, createEffect } from "./reactivity/solid.js";
|
|
6
|
+
import { createMessagesQuery } from "./createMessagesQuery.js";
|
|
7
|
+
import { debounce } from "throttle-debounce";
|
|
8
|
+
import { createMessageLintReportsQuery } from "./createMessageLintReportsQuery.js";
|
|
9
|
+
import { ProjectConfig, Message } from "./versionedInterfaces.js";
|
|
10
|
+
import { tryCatch } from "@inlang/result";
|
|
11
|
+
const ConfigCompiler = TypeCompiler.Compile(ProjectConfig);
|
|
12
|
+
/**
|
|
13
|
+
* Creates an inlang instance.
|
|
14
|
+
*
|
|
15
|
+
* - Use `_import` to pass a custom import function for testing,
|
|
16
|
+
* and supporting legacy resolvedModules such as CJS.
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
export const openInlangProject = async (args) => {
|
|
20
|
+
return await createRoot(async () => {
|
|
21
|
+
const [initialized, markInitAsComplete, markInitAsFailed] = createAwaitable();
|
|
22
|
+
// -- config ------------------------------------------------------------
|
|
23
|
+
const [config, _setConfig] = createSignal();
|
|
24
|
+
createEffect(() => {
|
|
25
|
+
loadConfig({ projectFilePath: args.projectFilePath, nodeishFs: args.nodeishFs })
|
|
26
|
+
.then((config) => {
|
|
27
|
+
setConfig(config);
|
|
28
|
+
args._capture?.("SDK used config", config);
|
|
29
|
+
})
|
|
30
|
+
.catch((err) => {
|
|
31
|
+
markInitAsFailed(err);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
// TODO: create FS watcher and update config on change
|
|
35
|
+
const writeConfigToDisk = skipFirst((config) => _writeConfigToDisk({ nodeishFs: args.nodeishFs, config }));
|
|
36
|
+
const setConfig = (config) => {
|
|
37
|
+
try {
|
|
38
|
+
const validatedConfig = validateConfig(config);
|
|
39
|
+
_setConfig(validatedConfig);
|
|
40
|
+
writeConfigToDisk(validatedConfig);
|
|
41
|
+
return { data: undefined };
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error instanceof InvalidConfigError) {
|
|
45
|
+
return { error };
|
|
46
|
+
}
|
|
47
|
+
throw new Error("unhandled");
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
// -- resolvedModules -----------------------------------------------------------
|
|
51
|
+
const [resolvedModules, setResolvedModules] = createSignal();
|
|
52
|
+
createEffect(() => {
|
|
53
|
+
const conf = config();
|
|
54
|
+
if (!conf)
|
|
55
|
+
return;
|
|
56
|
+
loadModules({ config: conf, nodeishFs: args.nodeishFs, _import: args._import })
|
|
57
|
+
.then((resolvedModules) => {
|
|
58
|
+
// TODO move to resolveModules
|
|
59
|
+
if (!resolvedModules.resolvedPluginApi.loadMessages ||
|
|
60
|
+
!resolvedModules.resolvedPluginApi.saveMessages) {
|
|
61
|
+
throw new NoPluginProvidesLoadOrSaveMessagesError();
|
|
62
|
+
}
|
|
63
|
+
setResolvedModules(resolvedModules);
|
|
64
|
+
// TODO: handle `detectedLanguageTags`
|
|
65
|
+
})
|
|
66
|
+
.catch((err) => markInitAsFailed(err));
|
|
67
|
+
});
|
|
68
|
+
// -- messages ----------------------------------------------------------
|
|
69
|
+
let configValue;
|
|
70
|
+
createEffect(() => (configValue = config())); // workaround to not run effects twice (e.g. config change + modules change) (I'm sure there exists a solid way of doing this, but I haven't found it yet)
|
|
71
|
+
const [messages, setMessages] = createSignal();
|
|
72
|
+
createEffect(() => {
|
|
73
|
+
const conf = config();
|
|
74
|
+
if (!conf)
|
|
75
|
+
return;
|
|
76
|
+
const _resolvedModules = resolvedModules();
|
|
77
|
+
if (!_resolvedModules)
|
|
78
|
+
return;
|
|
79
|
+
if (!_resolvedModules.resolvedPluginApi.loadMessages) {
|
|
80
|
+
markInitAsFailed(undefined);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
makeTrulyAsync(_resolvedModules.resolvedPluginApi.loadMessages({
|
|
84
|
+
languageTags: configValue.languageTags,
|
|
85
|
+
sourceLanguageTag: configValue.sourceLanguageTag,
|
|
86
|
+
}))
|
|
87
|
+
.then((messages) => {
|
|
88
|
+
setMessages(messages);
|
|
89
|
+
markInitAsComplete();
|
|
90
|
+
})
|
|
91
|
+
.catch((err) => markInitAsFailed(new PluginLoadMessagesError("Error in load messages", { cause: err })));
|
|
92
|
+
});
|
|
93
|
+
// -- installed items ----------------------------------------------------
|
|
94
|
+
const installedMessageLintRules = () => {
|
|
95
|
+
if (!resolvedModules())
|
|
96
|
+
return [];
|
|
97
|
+
return resolvedModules().messageLintRules.map((rule) => ({
|
|
98
|
+
meta: rule.meta,
|
|
99
|
+
module: resolvedModules()?.meta.find((m) => m.id.includes(rule.meta.id))?.module ??
|
|
100
|
+
"Unknown module. You stumbled on a bug in inlang's source code. Please open an issue.",
|
|
101
|
+
// default to warning, see https://github.com/inlang/inlang/issues/1254
|
|
102
|
+
lintLevel: configValue.settings["project.messageLintRuleLevels"]?.[rule.meta.id] ?? "warning",
|
|
103
|
+
}));
|
|
104
|
+
};
|
|
105
|
+
const installedPlugins = () => {
|
|
106
|
+
if (!resolvedModules())
|
|
107
|
+
return [];
|
|
108
|
+
return resolvedModules().plugins.map((plugin) => ({
|
|
109
|
+
meta: plugin.meta,
|
|
110
|
+
module: resolvedModules()?.meta.find((m) => m.id.includes(plugin.meta.id))?.module ??
|
|
111
|
+
"Unknown module. You stumbled on a bug in inlang's source code. Please open an issue.",
|
|
112
|
+
}));
|
|
113
|
+
};
|
|
114
|
+
// -- app ---------------------------------------------------------------
|
|
115
|
+
const initializeError = await initialized.catch((error) => error);
|
|
116
|
+
const messagesQuery = createMessagesQuery(() => messages() || []);
|
|
117
|
+
const lintReportsQuery = createMessageLintReportsQuery(messages, config, installedMessageLintRules, resolvedModules);
|
|
118
|
+
const debouncedSave = skipFirst(debounce(500, async (newMessages) => {
|
|
119
|
+
try {
|
|
120
|
+
await resolvedModules().resolvedPluginApi.saveMessages({ messages: newMessages });
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
throw new PluginSaveMessagesError("Error in saving messages", {
|
|
124
|
+
cause: err,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (newMessages.length !== 0 &&
|
|
128
|
+
JSON.stringify(newMessages) !== JSON.stringify(messages())) {
|
|
129
|
+
setMessages(newMessages);
|
|
130
|
+
}
|
|
131
|
+
}, { atBegin: false }));
|
|
132
|
+
createEffect(() => {
|
|
133
|
+
debouncedSave(messagesQuery.getAll());
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
installed: {
|
|
137
|
+
plugins: createSubscribable(() => installedPlugins()),
|
|
138
|
+
messageLintRules: createSubscribable(() => installedMessageLintRules()),
|
|
139
|
+
},
|
|
140
|
+
errors: createSubscribable(() => [
|
|
141
|
+
...(initializeError ? [initializeError] : []),
|
|
142
|
+
...(resolvedModules() ? resolvedModules().errors : []),
|
|
143
|
+
// have a query error exposed
|
|
144
|
+
//...(lintErrors() ?? []),
|
|
145
|
+
]),
|
|
146
|
+
config: createSubscribable(() => config()),
|
|
147
|
+
setConfig,
|
|
148
|
+
customApi: createSubscribable(() => resolvedModules()?.resolvedPluginApi.customApi || {}),
|
|
149
|
+
query: {
|
|
150
|
+
messages: messagesQuery,
|
|
151
|
+
messageLintReports: lintReportsQuery,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
//const x = {} as InlangProject
|
|
157
|
+
// ------------------------------------------------------------------------------------------------
|
|
158
|
+
const loadConfig = async (args) => {
|
|
159
|
+
const { data: configFile, error: configFileError } = await tryCatch(async () => await args.nodeishFs.readFile(args.projectFilePath, { encoding: "utf-8" }));
|
|
160
|
+
if (configFileError)
|
|
161
|
+
throw new ProjectFilePathNotFoundError(`Could not locate config file in (${args.projectFilePath}).`, {
|
|
162
|
+
cause: configFileError,
|
|
163
|
+
});
|
|
164
|
+
const { data: parsedConfig, error: parseConfigError } = tryCatch(() => JSON.parse(configFile));
|
|
165
|
+
if (parseConfigError)
|
|
166
|
+
throw new ProjectFileJSONSyntaxError(`The config is not a valid JSON file.`, {
|
|
167
|
+
cause: parseConfigError,
|
|
168
|
+
});
|
|
169
|
+
return validateConfig(parsedConfig);
|
|
170
|
+
};
|
|
171
|
+
const validateConfig = (config) => {
|
|
172
|
+
const typeErrors = [...ConfigCompiler.Errors(config)];
|
|
173
|
+
if (typeErrors.length > 0) {
|
|
174
|
+
throw new InvalidConfigError(`The config is invalid according to the schema.`, {
|
|
175
|
+
cause: typeErrors,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
// @ts-ignore - fix after refactor
|
|
179
|
+
return Value.Cast(ProjectConfig, config);
|
|
180
|
+
};
|
|
181
|
+
const _writeConfigToDisk = async (args) => {
|
|
182
|
+
const { data: serializedConfig, error: serializeConfigError } = tryCatch(() =>
|
|
183
|
+
// TODO: this will probably not match the original formatting
|
|
184
|
+
JSON.stringify(args.config, undefined, 2));
|
|
185
|
+
if (serializeConfigError)
|
|
186
|
+
throw serializeConfigError;
|
|
187
|
+
const { error: writeConfigError } = await tryCatch(async () => args.nodeishFs.writeFile("./project.inlang.json", serializedConfig));
|
|
188
|
+
if (writeConfigError)
|
|
189
|
+
throw writeConfigError;
|
|
190
|
+
};
|
|
191
|
+
// ------------------------------------------------------------------------------------------------
|
|
192
|
+
const loadModules = async (args) => resolveModules({
|
|
193
|
+
config: args.config,
|
|
194
|
+
nodeishFs: args.nodeishFs,
|
|
195
|
+
_import: args._import,
|
|
196
|
+
});
|
|
197
|
+
const createAwaitable = () => {
|
|
198
|
+
let resolve;
|
|
199
|
+
let reject;
|
|
200
|
+
const promise = new Promise((res, rej) => {
|
|
201
|
+
resolve = res;
|
|
202
|
+
reject = rej;
|
|
203
|
+
});
|
|
204
|
+
return [promise, resolve, reject];
|
|
205
|
+
};
|
|
206
|
+
const makeTrulyAsync = (fn) => (async () => fn)();
|
|
207
|
+
// Skip initial call, eg. to skip setup of a createEffect
|
|
208
|
+
function skipFirst(func) {
|
|
209
|
+
let initial = false;
|
|
210
|
+
return function (...args) {
|
|
211
|
+
if (initial) {
|
|
212
|
+
// @ts-ignore
|
|
213
|
+
return func.apply(this, args);
|
|
214
|
+
}
|
|
215
|
+
initial = true;
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
export function createSubscribable(signal) {
|
|
219
|
+
return Object.assign(signal, {
|
|
220
|
+
subscribe: (callback) => {
|
|
221
|
+
createEffect(() => {
|
|
222
|
+
callback(signal());
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openInlangProject.test.d.ts","sourceRoot":"","sources":["../src/openInlangProject.test.ts"],"names":[],"mappings":""}
|