@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,444 @@
|
|
|
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 {
|
|
5
|
+
MessagePatternsForLanguageTagDoNotExistError,
|
|
6
|
+
MessageVariantAlreadyExistsError,
|
|
7
|
+
MessageVariantDoesNotExistError,
|
|
8
|
+
} from "./errors.js"
|
|
9
|
+
import type { Message, Variant } from "../versionedInterfaces.js"
|
|
10
|
+
|
|
11
|
+
describe("getVariant", () => {
|
|
12
|
+
test("should return the correct variant of a message", () => {
|
|
13
|
+
const mockMessage: Message = getMockMessage()
|
|
14
|
+
|
|
15
|
+
const variant = getVariant(mockMessage, {
|
|
16
|
+
where: {
|
|
17
|
+
languageTag: "en",
|
|
18
|
+
selectors: { gender: "female", guestOther: "1" },
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
expect(variant?.pattern[0]).toStrictEqual({
|
|
23
|
+
type: "Text",
|
|
24
|
+
value: "{$hostName} invites {$guestName} to her party.",
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test("should return the correct fallback because the exact match does not exist", () => {
|
|
29
|
+
const mockMessage: Message = getMockMessage()
|
|
30
|
+
|
|
31
|
+
const variant = getVariant(mockMessage, {
|
|
32
|
+
where: {
|
|
33
|
+
languageTag: "en",
|
|
34
|
+
selectors: { gender: "female", guestOther: "0" },
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
expect(variant?.pattern[0]).toStrictEqual({
|
|
38
|
+
type: "Text",
|
|
39
|
+
value: "{$hostName} invites {$guestName} and {$guestsOther} other people to her party.",
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test("it should return undefined (but never throw an error) if selectors is an empty array", () => {
|
|
44
|
+
const mockMessage: Message = {
|
|
45
|
+
id: "mockMessage",
|
|
46
|
+
selectors: [],
|
|
47
|
+
variants: [
|
|
48
|
+
{
|
|
49
|
+
languageTag: "en",
|
|
50
|
+
pattern: [{ type: "Text", value: "Gender male" }],
|
|
51
|
+
match: {
|
|
52
|
+
gender: "male",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
languageTag: "de",
|
|
57
|
+
pattern: [{ type: "Text", value: "Veraltete Übersetzung" }],
|
|
58
|
+
match: {},
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
}
|
|
62
|
+
const variant = getVariant(mockMessage, {
|
|
63
|
+
where: {
|
|
64
|
+
languageTag: "fr",
|
|
65
|
+
selectors: {},
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
expect(variant).toBeUndefined()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test("the matcher should resolve not existing selectors to '*'", () => {
|
|
72
|
+
const mockMessage: Message = getMockMessage()
|
|
73
|
+
|
|
74
|
+
const variant = getVariant(mockMessage, {
|
|
75
|
+
where: {
|
|
76
|
+
languageTag: "en",
|
|
77
|
+
selectors: { guestOther: "0" },
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
expect(variant?.pattern[0]).toStrictEqual({
|
|
81
|
+
type: "Text",
|
|
82
|
+
value: "{$hostName} does not give a party.",
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test("empty selector should result in '*','*'", () => {
|
|
87
|
+
const mockMessage: Message = getMockMessage()
|
|
88
|
+
|
|
89
|
+
const variant = getVariant(mockMessage, {
|
|
90
|
+
where: {
|
|
91
|
+
languageTag: "en",
|
|
92
|
+
selectors: {},
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
expect(variant?.pattern[0]).toStrictEqual({
|
|
96
|
+
type: "Text",
|
|
97
|
+
value: "{$hostName} invites {$guestName} and {$guestsOther} other people to their party.",
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test("non existing selector values should be resolved", () => {
|
|
102
|
+
const mockMessage: Message = getMockMessage()
|
|
103
|
+
|
|
104
|
+
const variant = getVariant(mockMessage, {
|
|
105
|
+
where: {
|
|
106
|
+
languageTag: "en",
|
|
107
|
+
selectors: { gender: "trans", guestOther: "2" },
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
expect(variant?.pattern[0]).toStrictEqual({
|
|
111
|
+
type: "Text",
|
|
112
|
+
value: "{$hostName} invites {$guestName} and one other person to their party.",
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const variant2 = getVariant(mockMessage, {
|
|
116
|
+
where: {
|
|
117
|
+
languageTag: "en",
|
|
118
|
+
selectors: { gender: "male", guestOther: "8" },
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
expect(variant2?.pattern[0]).toStrictEqual({
|
|
122
|
+
type: "Text",
|
|
123
|
+
value: "{$hostName} invites {$guestName} and {$guestsOther} other people to his party.",
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test("should return undefined of no variant matches", () => {
|
|
128
|
+
const mockMessage: Message = getMockMessage()
|
|
129
|
+
mockMessage.variants = [
|
|
130
|
+
...mockMessage.variants!.filter(
|
|
131
|
+
(v) => v.languageTag === "en" && (v.match.gender !== "*" || v.match.guestOther !== "*"),
|
|
132
|
+
),
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
const variant = getVariant(mockMessage, {
|
|
136
|
+
where: {
|
|
137
|
+
languageTag: "en",
|
|
138
|
+
selectors: {},
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
expect(variant).toBeUndefined()
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test("should return undefined if a variant for specific language does not exist", () => {
|
|
145
|
+
const mockMessage: Message = getMockMessage()
|
|
146
|
+
|
|
147
|
+
const variant = getVariant(mockMessage, {
|
|
148
|
+
where: {
|
|
149
|
+
languageTag: "de",
|
|
150
|
+
selectors: { gender: "female", guestOther: "1" },
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
expect(variant).toBeUndefined()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test("should return the catch all variant if no selector defined", () => {
|
|
157
|
+
const mockMessage: Message = {} as any
|
|
158
|
+
mockMessage.variants = [
|
|
159
|
+
{
|
|
160
|
+
languageTag: "en",
|
|
161
|
+
match: {},
|
|
162
|
+
pattern: [
|
|
163
|
+
{
|
|
164
|
+
type: "Text",
|
|
165
|
+
value: "test",
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
const variant = getVariant(mockMessage, {
|
|
172
|
+
where: {
|
|
173
|
+
languageTag: "en",
|
|
174
|
+
selectors: {},
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
// should return the female variant
|
|
178
|
+
expect(variant?.pattern[0]).toStrictEqual({
|
|
179
|
+
type: "Text",
|
|
180
|
+
value: "test",
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
describe("createVariant", () => {
|
|
186
|
+
test("should create a variant for a message", () => {
|
|
187
|
+
const mockMessage: Message = getMockMessage()
|
|
188
|
+
|
|
189
|
+
const newVariant: Variant = {
|
|
190
|
+
languageTag: "en",
|
|
191
|
+
match: { gender: "female", guestOther: "0" },
|
|
192
|
+
pattern: [],
|
|
193
|
+
}
|
|
194
|
+
const message = createVariant(mockMessage, {
|
|
195
|
+
data: newVariant,
|
|
196
|
+
})
|
|
197
|
+
// should return the female variant
|
|
198
|
+
expect(
|
|
199
|
+
message.data!.variants.find(
|
|
200
|
+
(v) => v.languageTag === "en" && v.match.gender === "female" && v.match.guestOther === "0",
|
|
201
|
+
)?.pattern,
|
|
202
|
+
).toStrictEqual([])
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test("should create a variant, also if matcher are not full defined", () => {
|
|
206
|
+
const mockMessage: Message = getMockMessage()
|
|
207
|
+
mockMessage.variants = [
|
|
208
|
+
...mockMessage.variants!.filter(
|
|
209
|
+
(v) => v.languageTag === "en" && (v.match.gender !== "*" || v.match.guestOther !== "*"),
|
|
210
|
+
),
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
const message = createVariant(mockMessage, {
|
|
214
|
+
data: {
|
|
215
|
+
languageTag: "en",
|
|
216
|
+
match: {},
|
|
217
|
+
pattern: [],
|
|
218
|
+
},
|
|
219
|
+
})
|
|
220
|
+
// should return the female variant
|
|
221
|
+
expect(
|
|
222
|
+
message.data!.variants.find(
|
|
223
|
+
(v) => v.languageTag === "en" && v.match.gender === "*" && v.match.guestOther === "*",
|
|
224
|
+
)?.pattern,
|
|
225
|
+
).toStrictEqual([])
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
test("should return error if variant matches", () => {
|
|
229
|
+
const mockMessage: Message = getMockMessage()
|
|
230
|
+
|
|
231
|
+
const variant = createVariant(mockMessage, {
|
|
232
|
+
data: {
|
|
233
|
+
languageTag: "en",
|
|
234
|
+
match: { gender: "male", guestOther: "1" },
|
|
235
|
+
pattern: [],
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
// should return the female variant
|
|
239
|
+
expect(variant.data).toBeUndefined()
|
|
240
|
+
expect(variant.error).toBeInstanceOf(MessageVariantAlreadyExistsError)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
test("should not return error if set of variants for specific language does not exist", () => {
|
|
244
|
+
const mockMessage: Message = getMockMessage()
|
|
245
|
+
|
|
246
|
+
const variant = createVariant(mockMessage, {
|
|
247
|
+
data: {
|
|
248
|
+
languageTag: "de",
|
|
249
|
+
match: { gender: "female", guestOther: "1" },
|
|
250
|
+
pattern: [],
|
|
251
|
+
},
|
|
252
|
+
})
|
|
253
|
+
// should return the female variant
|
|
254
|
+
expect(variant.data).toBeDefined()
|
|
255
|
+
expect(variant.error).toBeUndefined()
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
describe("updateVariant", () => {
|
|
260
|
+
test("should update a variant of a message", () => {
|
|
261
|
+
const mockMessage: Message = getMockMessage()
|
|
262
|
+
|
|
263
|
+
const message = updateVariantPattern(mockMessage, {
|
|
264
|
+
where: {
|
|
265
|
+
languageTag: "en",
|
|
266
|
+
selectors: { gender: "female", guestOther: "1" },
|
|
267
|
+
},
|
|
268
|
+
data: [],
|
|
269
|
+
})
|
|
270
|
+
// should return the female variant
|
|
271
|
+
expect(
|
|
272
|
+
message.data!.variants.find(
|
|
273
|
+
(v) => v.languageTag === "en" && v.match.gender === "female" && v.match.guestOther === "1",
|
|
274
|
+
)?.pattern,
|
|
275
|
+
).toStrictEqual([])
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
test("should update a variant, also if matcher are not full defined", () => {
|
|
279
|
+
const mockMessage: Message = getMockMessage()
|
|
280
|
+
|
|
281
|
+
const message = updateVariantPattern(mockMessage, {
|
|
282
|
+
where: {
|
|
283
|
+
languageTag: "en",
|
|
284
|
+
selectors: {},
|
|
285
|
+
},
|
|
286
|
+
data: [],
|
|
287
|
+
})
|
|
288
|
+
// should return the female variant
|
|
289
|
+
expect(
|
|
290
|
+
message.data!.variants.find(
|
|
291
|
+
(v) => v.languageTag === "en" && v.match.gender === "*" && v.match.guestOther === "*",
|
|
292
|
+
)?.pattern,
|
|
293
|
+
).toStrictEqual([])
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
test("should return error if no variant matches", () => {
|
|
297
|
+
const mockMessage: Message = getMockMessage()
|
|
298
|
+
|
|
299
|
+
mockMessage.variants = [
|
|
300
|
+
...mockMessage.variants!.filter(
|
|
301
|
+
(v) => v.languageTag === "en" && (v.match.gender !== "*" || v.match.guestOther !== "*"),
|
|
302
|
+
),
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
const variant = updateVariantPattern(mockMessage, {
|
|
306
|
+
where: {
|
|
307
|
+
languageTag: "en",
|
|
308
|
+
selectors: {},
|
|
309
|
+
},
|
|
310
|
+
data: [],
|
|
311
|
+
})
|
|
312
|
+
// should return the female variant
|
|
313
|
+
expect(variant.data).toBeUndefined()
|
|
314
|
+
expect(variant.error).toBeInstanceOf(MessageVariantDoesNotExistError)
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
test("should return error if set of variants for specific language does not exist", () => {
|
|
318
|
+
const mockMessage: Message = getMockMessage()
|
|
319
|
+
|
|
320
|
+
const variant = updateVariantPattern(mockMessage, {
|
|
321
|
+
where: {
|
|
322
|
+
languageTag: "de",
|
|
323
|
+
selectors: {},
|
|
324
|
+
},
|
|
325
|
+
data: [],
|
|
326
|
+
})
|
|
327
|
+
// should return the female variant
|
|
328
|
+
expect(variant.data).toBeUndefined()
|
|
329
|
+
expect(variant.error).toBeInstanceOf(MessagePatternsForLanguageTagDoNotExistError)
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
const getMockMessage = (): Message => {
|
|
334
|
+
return {
|
|
335
|
+
id: "first-message",
|
|
336
|
+
selectors: [
|
|
337
|
+
{ type: "VariableReference", name: "gender" },
|
|
338
|
+
{ type: "VariableReference", name: "guestOther" },
|
|
339
|
+
],
|
|
340
|
+
variants: [
|
|
341
|
+
{
|
|
342
|
+
languageTag: "en",
|
|
343
|
+
match: { gender: "female", guestOther: "1" },
|
|
344
|
+
pattern: [
|
|
345
|
+
{
|
|
346
|
+
type: "Text",
|
|
347
|
+
value: "{$hostName} invites {$guestName} to her party.",
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
languageTag: "en",
|
|
353
|
+
match: { gender: "female", guestOther: "2" },
|
|
354
|
+
pattern: [
|
|
355
|
+
{
|
|
356
|
+
type: "Text",
|
|
357
|
+
value: "{$hostName} invites {$guestName} and one other person to her party.",
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
languageTag: "en",
|
|
363
|
+
match: { gender: "female", guestOther: "*" },
|
|
364
|
+
pattern: [
|
|
365
|
+
{
|
|
366
|
+
type: "Text",
|
|
367
|
+
value: "{$hostName} invites {$guestName} and {$guestsOther} other people to her party.",
|
|
368
|
+
},
|
|
369
|
+
],
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
languageTag: "en",
|
|
373
|
+
match: { gender: "male", guestOther: "1" },
|
|
374
|
+
pattern: [
|
|
375
|
+
{
|
|
376
|
+
type: "Text",
|
|
377
|
+
value: "{$hostName} invites {$guestName} to his party.",
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
languageTag: "en",
|
|
383
|
+
match: { gender: "male", guestOther: "2" },
|
|
384
|
+
pattern: [
|
|
385
|
+
{
|
|
386
|
+
type: "Text",
|
|
387
|
+
value: "{$hostName} invites {$guestName} and one other person to his party.",
|
|
388
|
+
},
|
|
389
|
+
],
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
languageTag: "en",
|
|
393
|
+
match: { gender: "male", guestOther: "*" },
|
|
394
|
+
pattern: [
|
|
395
|
+
{
|
|
396
|
+
type: "Text",
|
|
397
|
+
value: "{$hostName} invites {$guestName} and {$guestsOther} other people to his party.",
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
languageTag: "en",
|
|
403
|
+
match: { gender: "*", guestOther: "0" },
|
|
404
|
+
pattern: [
|
|
405
|
+
{
|
|
406
|
+
type: "Text",
|
|
407
|
+
value: "{$hostName} does not give a party.",
|
|
408
|
+
},
|
|
409
|
+
],
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
languageTag: "en",
|
|
413
|
+
match: { gender: "*", guestOther: "1" },
|
|
414
|
+
pattern: [
|
|
415
|
+
{
|
|
416
|
+
type: "Text",
|
|
417
|
+
value: "{$hostName} invites {$guestName} to their party.",
|
|
418
|
+
},
|
|
419
|
+
],
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
languageTag: "en",
|
|
423
|
+
match: { gender: "*", guestOther: "2" },
|
|
424
|
+
pattern: [
|
|
425
|
+
{
|
|
426
|
+
type: "Text",
|
|
427
|
+
value: "{$hostName} invites {$guestName} and one other person to their party.",
|
|
428
|
+
},
|
|
429
|
+
],
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
languageTag: "en",
|
|
433
|
+
match: { gender: "*", guestOther: "*" },
|
|
434
|
+
pattern: [
|
|
435
|
+
{
|
|
436
|
+
type: "Text",
|
|
437
|
+
value:
|
|
438
|
+
"{$hostName} invites {$guestName} and {$guestsOther} other people to their party.",
|
|
439
|
+
},
|
|
440
|
+
],
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
}
|
|
444
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import type { LanguageTag, Message, Variant } from "../versionedInterfaces.js"
|
|
2
|
+
import type { Result } from "@inlang/result"
|
|
3
|
+
import {
|
|
4
|
+
MessagePatternsForLanguageTagDoNotExistError,
|
|
5
|
+
MessageVariantAlreadyExistsError,
|
|
6
|
+
MessageVariantDoesNotExistError,
|
|
7
|
+
} from "./errors.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Tries to match the most specific variant of a message.
|
|
11
|
+
*
|
|
12
|
+
* The selectors determine the specificity of a variant. If no selectors are provided,
|
|
13
|
+
* or if the selectors do not match any variant, the catch all variant is returned
|
|
14
|
+
* (if it exists).
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const variant = getVariant(message, { where: { languageTag: "en", selectors: { gender: "male" }}});
|
|
18
|
+
*/
|
|
19
|
+
export function getVariant(
|
|
20
|
+
message: Message,
|
|
21
|
+
args: {
|
|
22
|
+
where: {
|
|
23
|
+
languageTag: LanguageTag
|
|
24
|
+
selectors?: Variant["match"]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
): Variant | undefined {
|
|
28
|
+
const variant = matchMostSpecificVariant(message, args.where.languageTag, args.where.selectors)
|
|
29
|
+
if (variant) {
|
|
30
|
+
//! do not return a reference to the message in a resource
|
|
31
|
+
//! modifications to the returned message will leak into the
|
|
32
|
+
//! resource which is considered to be immutable.
|
|
33
|
+
return structuredClone(variant)
|
|
34
|
+
}
|
|
35
|
+
return undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create a variant for a message
|
|
40
|
+
*
|
|
41
|
+
* All actions are immutable.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const message = createVariant(message, { languageTag: "en", data: variant })
|
|
45
|
+
*/
|
|
46
|
+
export function createVariant(
|
|
47
|
+
message: Message,
|
|
48
|
+
args: {
|
|
49
|
+
data: Variant
|
|
50
|
+
},
|
|
51
|
+
): Result<Message, MessageVariantAlreadyExistsError> {
|
|
52
|
+
const copy = structuredClone(message)
|
|
53
|
+
|
|
54
|
+
// check if variant already exists
|
|
55
|
+
if (matchVariant(copy, args.data.languageTag, args.data.match)) {
|
|
56
|
+
return { error: new MessageVariantAlreadyExistsError(message.id, args.data.languageTag) }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// need to resolve selectors to match length and order of message selectors
|
|
60
|
+
copy.variants.push({
|
|
61
|
+
...args.data,
|
|
62
|
+
match: resolveSelector(copy.selectors, args.data.match),
|
|
63
|
+
})
|
|
64
|
+
return { data: copy }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Update a variant of a message
|
|
69
|
+
*
|
|
70
|
+
* All actions are immutable.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* const message = updateVariant(message, { languageTag: "en", selectors: { gender: "male" }, pattern: []})
|
|
74
|
+
*/
|
|
75
|
+
export function updateVariantPattern(
|
|
76
|
+
message: Message,
|
|
77
|
+
args: {
|
|
78
|
+
where: {
|
|
79
|
+
languageTag: LanguageTag
|
|
80
|
+
selectors: Record<string, string>
|
|
81
|
+
}
|
|
82
|
+
data: Variant["pattern"]
|
|
83
|
+
},
|
|
84
|
+
): Result<Message, MessageVariantDoesNotExistError | MessagePatternsForLanguageTagDoNotExistError> {
|
|
85
|
+
const copy = structuredClone(message)
|
|
86
|
+
|
|
87
|
+
const containsLanguageTag = message.variants.some(
|
|
88
|
+
(variant) => variant.languageTag === args.where.languageTag,
|
|
89
|
+
)
|
|
90
|
+
if (!containsLanguageTag) {
|
|
91
|
+
return {
|
|
92
|
+
error: new MessagePatternsForLanguageTagDoNotExistError(message.id, args.where.languageTag),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const variant = matchVariant(copy, args.where.languageTag, args.where.selectors)
|
|
97
|
+
if (variant === undefined) {
|
|
98
|
+
return { error: new MessageVariantDoesNotExistError(message.id, args.where.languageTag) }
|
|
99
|
+
}
|
|
100
|
+
if (variant) {
|
|
101
|
+
variant.pattern = args.data
|
|
102
|
+
return { data: copy }
|
|
103
|
+
}
|
|
104
|
+
return { error: new MessageVariantDoesNotExistError(message.id, args.where.languageTag) }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Returns the specific variant defined by selectors or undefined
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* const variant = matchVariant(message, languageTag: "en", selectors: { gender: "male" })
|
|
112
|
+
*/
|
|
113
|
+
const matchVariant = (
|
|
114
|
+
message: Message,
|
|
115
|
+
languageTag: LanguageTag,
|
|
116
|
+
selectors: Record<string, string>,
|
|
117
|
+
): Variant | undefined => {
|
|
118
|
+
// resolve preferenceSelectors to match length and order of message selectors
|
|
119
|
+
const resolvedSelectors = resolveSelector(message.selectors, selectors)
|
|
120
|
+
|
|
121
|
+
const languageVariants = message.variants.filter((variant) => variant.languageTag === languageTag)
|
|
122
|
+
if (languageVariants.length === 0) return undefined
|
|
123
|
+
|
|
124
|
+
for (const variant of languageVariants) {
|
|
125
|
+
let isMatch = true
|
|
126
|
+
//check if vaiant is a match
|
|
127
|
+
for (const [key, value] of Object.entries(variant.match)) {
|
|
128
|
+
if (resolvedSelectors[key] !== value) {
|
|
129
|
+
isMatch = false
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (isMatch) {
|
|
133
|
+
return variant
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return undefined
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Returns the most specific variant of a message.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* const variant = matchMostSpecificVariant(message, languageTag: "en", selectors: { gender: "male" })
|
|
144
|
+
*/
|
|
145
|
+
const matchMostSpecificVariant = (
|
|
146
|
+
message: Message,
|
|
147
|
+
languageTag: LanguageTag,
|
|
148
|
+
selectors?: Record<string, string>,
|
|
149
|
+
): Variant | undefined => {
|
|
150
|
+
// make selector undefined if empty object
|
|
151
|
+
selectors = JSON.stringify(selectors) === "{}" ? undefined : selectors
|
|
152
|
+
|
|
153
|
+
// resolve preferenceSelectors to match length and order of message selectors
|
|
154
|
+
const resolvedSelectors = resolveSelector(message.selectors, selectors)
|
|
155
|
+
const index: Record<string, any> = {}
|
|
156
|
+
|
|
157
|
+
const languageVariants = message.variants.filter((variant) => variant.languageTag === languageTag)
|
|
158
|
+
if (languageVariants.length === 0) return undefined
|
|
159
|
+
|
|
160
|
+
for (const variant of languageVariants) {
|
|
161
|
+
let isMatch = true
|
|
162
|
+
|
|
163
|
+
//check if variant is a match
|
|
164
|
+
for (const [key, value] of Object.entries(variant.match)) {
|
|
165
|
+
if (resolvedSelectors[key] !== value && value !== "*") {
|
|
166
|
+
isMatch = false
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (isMatch && selectors) {
|
|
170
|
+
// add variant to nested index
|
|
171
|
+
// eslint-disable-next-line no-inner-declarations
|
|
172
|
+
function recursiveAddToIndex(
|
|
173
|
+
currentIndex: Record<string, any>,
|
|
174
|
+
currentKeys: Message["selectors"],
|
|
175
|
+
variant: Variant,
|
|
176
|
+
) {
|
|
177
|
+
if (currentKeys[0]?.name) {
|
|
178
|
+
const key = variant.match[currentKeys[0].name]
|
|
179
|
+
if (key) {
|
|
180
|
+
if (currentKeys.length === 1) {
|
|
181
|
+
currentIndex[key] = variant
|
|
182
|
+
} else {
|
|
183
|
+
if (!currentIndex[key]) {
|
|
184
|
+
currentIndex[key] = {}
|
|
185
|
+
}
|
|
186
|
+
recursiveAddToIndex(currentIndex[key], currentKeys.slice(1), variant)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
recursiveAddToIndex(index, message.selectors, variant)
|
|
192
|
+
} else if (isMatch && !selectors) {
|
|
193
|
+
return variant
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
//find the most specific variant
|
|
198
|
+
const findOptimalMatch = (
|
|
199
|
+
index: Record<string, any>,
|
|
200
|
+
selectors: string[],
|
|
201
|
+
): Variant | undefined => {
|
|
202
|
+
const keys = Object.keys(index)
|
|
203
|
+
|
|
204
|
+
for (const key of keys) {
|
|
205
|
+
if (key === selectors[0] || key === "*") {
|
|
206
|
+
const nextOptimal = selectors.slice(1)
|
|
207
|
+
|
|
208
|
+
if (nextOptimal.length === 0) {
|
|
209
|
+
return (index[key] as Variant) || undefined
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const match = findOptimalMatch(index[key] as Record<string, any>, nextOptimal)
|
|
213
|
+
|
|
214
|
+
if (match !== undefined) {
|
|
215
|
+
return match
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return undefined
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return findOptimalMatch(index, Object.values(resolvedSelectors))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Returns resolved selector.
|
|
227
|
+
* -> Adds all possible selectors, if not defined it adds '*'. Order is determined by messageSelectors
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* const variant = resolveSelector(["gender","count"], selector: {count: "2"})
|
|
231
|
+
*/
|
|
232
|
+
const resolveSelector = (
|
|
233
|
+
messageSelectors: Message["selectors"],
|
|
234
|
+
selectors?: Record<string, string>,
|
|
235
|
+
): Record<string, string> => {
|
|
236
|
+
const resolvedSelectors: Record<string, string> = {}
|
|
237
|
+
if (!selectors) return {}
|
|
238
|
+
for (const messageSelector of messageSelectors) {
|
|
239
|
+
resolvedSelectors[messageSelector.name] = selectors[messageSelector.name] ?? "*"
|
|
240
|
+
}
|
|
241
|
+
return resolvedSelectors
|
|
242
|
+
}
|