@inlang/sdk 0.34.4 → 0.34.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +21 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/createMessageLintReportsQuery.d.ts +1 -1
- package/dist/createMessageLintReportsQuery.d.ts.map +1 -1
- package/dist/createMessageLintReportsQuery.js +43 -58
- package/dist/createMessagesQuery.d.ts.map +1 -1
- package/dist/createMessagesQuery.js +38 -9
- package/dist/loadProject.d.ts.map +1 -1
- package/dist/v2/index.d.ts +2 -0
- package/dist/v2/index.d.ts.map +1 -0
- package/dist/v2/index.js +1 -0
- package/dist/v2/types.d.ts +411 -0
- package/dist/v2/types.d.ts.map +1 -0
- package/dist/v2/types.js +69 -0
- package/package.json +7 -6
- package/src/api.ts +24 -0
- package/src/createMessageLintReportsQuery.ts +45 -67
- package/src/createMessagesQuery.ts +60 -4
- package/src/loadProject.ts +0 -1
- package/src/v2/index.ts +1 -0
- package/src/v2/types.ts +142 -0
|
@@ -3,23 +3,19 @@ import type {
|
|
|
3
3
|
InstalledMessageLintRule,
|
|
4
4
|
MessageLintReportsQueryApi,
|
|
5
5
|
MessageQueryApi,
|
|
6
|
+
MessageQueryDelegate,
|
|
6
7
|
} from "./api.js"
|
|
7
8
|
import type { ProjectSettings } from "@inlang/project-settings"
|
|
8
9
|
import type { resolveModules } from "./resolve-modules/index.js"
|
|
9
10
|
import type { MessageLintReport, Message } from "./versionedInterfaces.js"
|
|
10
11
|
import { lintSingleMessage } from "./lint/index.js"
|
|
11
|
-
import { createRoot, createEffect } from "./reactivity/solid.js"
|
|
12
|
-
|
|
13
|
-
import { throttle } from "throttle-debounce"
|
|
14
|
-
import _debug from "debug"
|
|
15
|
-
const debug = _debug("sdk:lintReports")
|
|
16
12
|
|
|
17
13
|
function sleep(ms: number) {
|
|
18
14
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
19
15
|
}
|
|
20
16
|
|
|
21
17
|
/**
|
|
22
|
-
* Creates a reactive query API for
|
|
18
|
+
* Creates a ~~reactive~~ query API for lint reports.
|
|
23
19
|
*/
|
|
24
20
|
export function createMessageLintReportsQuery(
|
|
25
21
|
messagesQuery: MessageQueryApi,
|
|
@@ -42,72 +38,54 @@ export function createMessageLintReportsQuery(
|
|
|
42
38
|
}
|
|
43
39
|
}
|
|
44
40
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
debug(`createMessageLintReportsQuery ${rulesArray?.length} rules, ${messages.length} messages`)
|
|
50
|
-
|
|
51
|
-
// TODO: don't throttle when no debug
|
|
52
|
-
let lintMessageCount = 0
|
|
53
|
-
const throttledLogLintMessage = throttle(2000, (messageId) => {
|
|
54
|
-
debug(`lintSingleMessage: ${lintMessageCount} id: ${messageId}`)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
createEffect(() => {
|
|
58
|
-
const currentMessageIds = new Set(messagesQuery.includedMessageIds())
|
|
59
|
-
|
|
60
|
-
const deletedTrackedMessages = [...trackedMessages].filter(
|
|
61
|
-
(tracked) => !currentMessageIds.has(tracked[0])
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
if (rulesArray) {
|
|
65
|
-
for (const messageId of currentMessageIds) {
|
|
66
|
-
if (!trackedMessages.has(messageId)) {
|
|
67
|
-
createRoot((dispose) => {
|
|
68
|
-
createEffect(() => {
|
|
69
|
-
const message = messagesQuery.get({ where: { id: messageId } })
|
|
70
|
-
if (!message) {
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
|
-
if (!trackedMessages?.has(messageId)) {
|
|
74
|
-
// initial effect execution - add dispose function
|
|
75
|
-
trackedMessages?.set(messageId, dispose)
|
|
76
|
-
}
|
|
41
|
+
const lintMessage = (message: Message, messages: Message[]) => {
|
|
42
|
+
if (!rulesArray) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
77
45
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
index.set(messageId, report.data)
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
})
|
|
92
|
-
})
|
|
93
|
-
}
|
|
46
|
+
// TODO unhandled promise rejection (as before the refactor) but won't tackle this in this pr
|
|
47
|
+
lintSingleMessage({
|
|
48
|
+
rules: rulesArray,
|
|
49
|
+
settings: settingsObject(),
|
|
50
|
+
messages: messages,
|
|
51
|
+
message: message,
|
|
52
|
+
}).then((report) => {
|
|
53
|
+
if (report.errors.length === 0 && index.get(message.id) !== report.data) {
|
|
54
|
+
// console.log("lintSingleMessage", messageId, report.data.length)
|
|
55
|
+
index.set(message.id, report.data)
|
|
94
56
|
}
|
|
57
|
+
})
|
|
58
|
+
}
|
|
95
59
|
|
|
96
|
-
|
|
97
|
-
|
|
60
|
+
const messages = messagesQuery.getAll() as Message[]
|
|
61
|
+
// load report for all messages once
|
|
62
|
+
for (const message of messages) {
|
|
63
|
+
// NOTE: this potentually creates thousands of promisses we could create a promise that batches linting
|
|
64
|
+
lintMessage(message, messages)
|
|
65
|
+
}
|
|
98
66
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
67
|
+
const messageQueryChangeDelegate: MessageQueryDelegate = {
|
|
68
|
+
onCleanup: () => {
|
|
69
|
+
// NOTE: we could cancel all running lint rules - but results get overritten anyway
|
|
70
|
+
index.clear()
|
|
71
|
+
},
|
|
72
|
+
onLoaded: (messages: Message[]) => {
|
|
73
|
+
for (const message of messages) {
|
|
74
|
+
lintMessage(message, messages)
|
|
108
75
|
}
|
|
109
|
-
}
|
|
110
|
-
|
|
76
|
+
},
|
|
77
|
+
onMessageCreate: (messageId: string, message: Message) => {
|
|
78
|
+
lintMessage(message, messages)
|
|
79
|
+
},
|
|
80
|
+
onMessageUpdate: (messageId: string, message: Message) => {
|
|
81
|
+
lintMessage(message, messages)
|
|
82
|
+
},
|
|
83
|
+
onMessageDelete: (messageId: string) => {
|
|
84
|
+
index.delete(messageId)
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
messagesQuery.setDelegate(messageQueryChangeDelegate)
|
|
111
89
|
|
|
112
90
|
return {
|
|
113
91
|
getAll: async () => {
|
|
@@ -2,7 +2,7 @@ import type { Message } from "@inlang/message"
|
|
|
2
2
|
import { ReactiveMap } from "./reactivity/map.js"
|
|
3
3
|
import { createEffect } from "./reactivity/solid.js"
|
|
4
4
|
import { createSubscribable } from "./loadProject.js"
|
|
5
|
-
import type { InlangProject, MessageQueryApi } from "./api.js"
|
|
5
|
+
import type { InlangProject, MessageQueryApi, MessageQueryDelegate } from "./api.js"
|
|
6
6
|
import type { ResolvedPluginApi } from "./resolve-modules/plugins/types.js"
|
|
7
7
|
import type { resolveModules } from "./resolve-modules/resolveModules.js"
|
|
8
8
|
import { createNodeishFsWithWatcher } from "./createNodeishFsWithWatcher.js"
|
|
@@ -17,6 +17,10 @@ import { PluginLoadMessagesError, PluginSaveMessagesError } from "./errors.js"
|
|
|
17
17
|
import { humanIdHash } from "./storage/human-id/human-readable-id.js"
|
|
18
18
|
const debug = _debug("sdk:createMessagesQuery")
|
|
19
19
|
|
|
20
|
+
function sleep(ms: number) {
|
|
21
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
type MessageState = {
|
|
21
25
|
messageDirtyFlags: {
|
|
22
26
|
[messageId: string]: boolean
|
|
@@ -62,6 +66,12 @@ export function createMessagesQuery({
|
|
|
62
66
|
// filepath for the lock folder
|
|
63
67
|
const messageLockDirPath = projectPath + "/messagelock"
|
|
64
68
|
|
|
69
|
+
let delegate: MessageQueryDelegate | undefined = undefined
|
|
70
|
+
|
|
71
|
+
const setDelegate = (newDelegate: MessageQueryDelegate) => {
|
|
72
|
+
delegate = newDelegate
|
|
73
|
+
}
|
|
74
|
+
|
|
65
75
|
// Map default alias to message
|
|
66
76
|
// Assumes that aliases are only created and deleted, not updated
|
|
67
77
|
// TODO #2346 - handle updates to aliases
|
|
@@ -98,6 +108,7 @@ export function createMessagesQuery({
|
|
|
98
108
|
onCleanup(() => {
|
|
99
109
|
// stop listening on fs events
|
|
100
110
|
abortController.abort()
|
|
111
|
+
delegate?.onCleanup()
|
|
101
112
|
})
|
|
102
113
|
|
|
103
114
|
const fsWithWatcher = createNodeishFsWithWatcher({
|
|
@@ -111,6 +122,7 @@ export function createMessagesQuery({
|
|
|
111
122
|
messageLockDirPath,
|
|
112
123
|
messageStates,
|
|
113
124
|
index,
|
|
125
|
+
delegate,
|
|
114
126
|
_settings, // NOTE we bang here - we don't expect the settings to become null during the livetime of a project
|
|
115
127
|
resolvedPluginApi
|
|
116
128
|
)
|
|
@@ -133,6 +145,7 @@ export function createMessagesQuery({
|
|
|
133
145
|
messageLockDirPath,
|
|
134
146
|
messageStates,
|
|
135
147
|
index,
|
|
148
|
+
delegate,
|
|
136
149
|
_settings, // NOTE we bang here - we don't expect the settings to become null during the livetime of a project
|
|
137
150
|
resolvedPluginApi
|
|
138
151
|
)
|
|
@@ -142,6 +155,7 @@ export function createMessagesQuery({
|
|
|
142
155
|
})
|
|
143
156
|
.then(() => {
|
|
144
157
|
onInitialMessageLoadResult()
|
|
158
|
+
delegate?.onLoaded([...index.values()])
|
|
145
159
|
})
|
|
146
160
|
})
|
|
147
161
|
|
|
@@ -165,6 +179,7 @@ export function createMessagesQuery({
|
|
|
165
179
|
messageLockDirPath,
|
|
166
180
|
messageStates,
|
|
167
181
|
index,
|
|
182
|
+
delegate,
|
|
168
183
|
_settings, // NOTE we bang here - we don't expect the settings to become null during the livetime of a project
|
|
169
184
|
resolvedPluginApi
|
|
170
185
|
)
|
|
@@ -181,6 +196,7 @@ export function createMessagesQuery({
|
|
|
181
196
|
}
|
|
182
197
|
|
|
183
198
|
return {
|
|
199
|
+
setDelegate,
|
|
184
200
|
create: ({ data }): boolean => {
|
|
185
201
|
if (index.has(data.id)) return false
|
|
186
202
|
index.set(data.id, data)
|
|
@@ -189,6 +205,7 @@ export function createMessagesQuery({
|
|
|
189
205
|
}
|
|
190
206
|
|
|
191
207
|
messageStates.messageDirtyFlags[data.id] = true
|
|
208
|
+
delegate?.onMessageCreate(data.id, index.get(data.id))
|
|
192
209
|
scheduleSave()
|
|
193
210
|
return true
|
|
194
211
|
},
|
|
@@ -215,6 +232,7 @@ export function createMessagesQuery({
|
|
|
215
232
|
if (message === undefined) return false
|
|
216
233
|
index.set(where.id, { ...message, ...data })
|
|
217
234
|
messageStates.messageDirtyFlags[where.id] = true
|
|
235
|
+
delegate?.onMessageCreate(where.id, index.get(data.id))
|
|
218
236
|
scheduleSave()
|
|
219
237
|
return true
|
|
220
238
|
},
|
|
@@ -225,10 +243,13 @@ export function createMessagesQuery({
|
|
|
225
243
|
if ("default" in data.alias) {
|
|
226
244
|
defaultAliasIndex.set(data.alias.default, data)
|
|
227
245
|
}
|
|
246
|
+
messageStates.messageDirtyFlags[where.id] = true
|
|
247
|
+
delegate?.onMessageCreate(data.id, index.get(data.id))
|
|
228
248
|
} else {
|
|
229
249
|
index.set(where.id, { ...message, ...data })
|
|
250
|
+
messageStates.messageDirtyFlags[where.id] = true
|
|
251
|
+
delegate?.onMessageUpdate(data.id, index.get(data.id))
|
|
230
252
|
}
|
|
231
|
-
messageStates.messageDirtyFlags[where.id] = true
|
|
232
253
|
scheduleSave()
|
|
233
254
|
return true
|
|
234
255
|
},
|
|
@@ -240,6 +261,7 @@ export function createMessagesQuery({
|
|
|
240
261
|
}
|
|
241
262
|
index.delete(where.id)
|
|
242
263
|
messageStates.messageDirtyFlags[where.id] = true
|
|
264
|
+
delegate?.onMessageDelete(where.id)
|
|
243
265
|
scheduleSave()
|
|
244
266
|
return true
|
|
245
267
|
},
|
|
@@ -253,6 +275,8 @@ export function createMessagesQuery({
|
|
|
253
275
|
// - saving a message in two different languages would lead to a write in de.json first
|
|
254
276
|
// - This will leads to a load of the messages and since en.json has not been saved yet the english variant in the message would get overritten with the old state again
|
|
255
277
|
|
|
278
|
+
const maxMessagesPerTick = 500
|
|
279
|
+
|
|
256
280
|
/**
|
|
257
281
|
* Messsage that loads messages from a plugin - this method synchronizes with the saveMessage funciton.
|
|
258
282
|
* If a save is in progress loading will wait until saving is done. If another load kicks in during this load it will queue the
|
|
@@ -271,6 +295,7 @@ async function loadMessagesViaPlugin(
|
|
|
271
295
|
lockDirPath: string,
|
|
272
296
|
messageState: MessageState,
|
|
273
297
|
messages: Map<string, Message>,
|
|
298
|
+
delegate: MessageQueryDelegate | undefined,
|
|
274
299
|
settingsValue: ProjectSettings,
|
|
275
300
|
resolvedPluginApi: ResolvedPluginApi
|
|
276
301
|
) {
|
|
@@ -298,6 +323,8 @@ async function loadMessagesViaPlugin(
|
|
|
298
323
|
})
|
|
299
324
|
)
|
|
300
325
|
|
|
326
|
+
let loadedMessageCount = 0
|
|
327
|
+
|
|
301
328
|
for (const loadedMessage of loadedMessages) {
|
|
302
329
|
const loadedMessageClone = structuredClone(loadedMessage)
|
|
303
330
|
|
|
@@ -339,6 +366,8 @@ async function loadMessagesViaPlugin(
|
|
|
339
366
|
messages.set(loadedMessageClone.id, loadedMessageClone)
|
|
340
367
|
// NOTE could use hash instead of the whole object JSON to save memory...
|
|
341
368
|
messageState.messageLoadHash[loadedMessageClone.id] = importedEnecoded
|
|
369
|
+
delegate?.onMessageUpdate(loadedMessageClone.id, loadedMessageClone)
|
|
370
|
+
loadedMessageCount++
|
|
342
371
|
} else {
|
|
343
372
|
// message with the given alias does not exist so far
|
|
344
373
|
loadedMessageClone.alias = {} as any
|
|
@@ -365,6 +394,15 @@ async function loadMessagesViaPlugin(
|
|
|
365
394
|
// we don't have to check - done before hand if (messages.has(loadedMessageClone.id)) return false
|
|
366
395
|
messages.set(loadedMessageClone.id, loadedMessageClone)
|
|
367
396
|
messageState.messageLoadHash[loadedMessageClone.id] = importedEnecoded
|
|
397
|
+
delegate?.onMessageUpdate(loadedMessageClone.id, loadedMessageClone)
|
|
398
|
+
loadedMessageCount++
|
|
399
|
+
}
|
|
400
|
+
if (loadedMessageCount > maxMessagesPerTick) {
|
|
401
|
+
// move loading of the next messages to the next ticks to allow solid to cleanup resources
|
|
402
|
+
// solid needs some time to settle and clean up
|
|
403
|
+
// https://github.com/solidjs-community/solid-primitives/blob/9ca76a47ffa2172770e075a90695cf933da0ff48/packages/trigger/src/index.ts#L64
|
|
404
|
+
await sleep(0)
|
|
405
|
+
loadedMessageCount = 0
|
|
368
406
|
}
|
|
369
407
|
}
|
|
370
408
|
await releaseLock(fs as NodeishFilesystem, lockDirPath, "loadMessage", lockTime)
|
|
@@ -388,7 +426,15 @@ async function loadMessagesViaPlugin(
|
|
|
388
426
|
messageState.sheduledLoadMessagesViaPlugin = undefined
|
|
389
427
|
|
|
390
428
|
// recall load unawaited to allow stack to pop
|
|
391
|
-
loadMessagesViaPlugin(
|
|
429
|
+
loadMessagesViaPlugin(
|
|
430
|
+
fs,
|
|
431
|
+
lockDirPath,
|
|
432
|
+
messageState,
|
|
433
|
+
messages,
|
|
434
|
+
delegate,
|
|
435
|
+
settingsValue,
|
|
436
|
+
resolvedPluginApi
|
|
437
|
+
)
|
|
392
438
|
.then(() => {
|
|
393
439
|
// resolve the scheduled load message promise
|
|
394
440
|
executingScheduledMessages.resolve()
|
|
@@ -405,6 +451,7 @@ async function saveMessagesViaPlugin(
|
|
|
405
451
|
lockDirPath: string,
|
|
406
452
|
messageState: MessageState,
|
|
407
453
|
messages: Map<string, Message>,
|
|
454
|
+
delegate: MessageQueryDelegate | undefined,
|
|
408
455
|
settingsValue: ProjectSettings,
|
|
409
456
|
resolvedPluginApi: ResolvedPluginApi
|
|
410
457
|
): Promise<void> {
|
|
@@ -491,6 +538,7 @@ async function saveMessagesViaPlugin(
|
|
|
491
538
|
lockDirPath,
|
|
492
539
|
messageState,
|
|
493
540
|
messages,
|
|
541
|
+
delegate,
|
|
494
542
|
settingsValue,
|
|
495
543
|
resolvedPluginApi
|
|
496
544
|
)
|
|
@@ -530,7 +578,15 @@ async function saveMessagesViaPlugin(
|
|
|
530
578
|
const executingSheduledSaveMessages = messageState.sheduledSaveMessages
|
|
531
579
|
messageState.sheduledSaveMessages = undefined
|
|
532
580
|
|
|
533
|
-
saveMessagesViaPlugin(
|
|
581
|
+
saveMessagesViaPlugin(
|
|
582
|
+
fs,
|
|
583
|
+
lockDirPath,
|
|
584
|
+
messageState,
|
|
585
|
+
messages,
|
|
586
|
+
delegate,
|
|
587
|
+
settingsValue,
|
|
588
|
+
resolvedPluginApi
|
|
589
|
+
)
|
|
534
590
|
.then(() => {
|
|
535
591
|
executingSheduledSaveMessages.resolve()
|
|
536
592
|
})
|
package/src/loadProject.ts
CHANGED
package/src/v2/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type * from "./types.js"
|
package/src/v2/types.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Type, type Static } from "@sinclair/typebox"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Follows the IETF BCP 47 language tag schema.
|
|
5
|
+
*
|
|
6
|
+
* @see https://www.ietf.org/rfc/bcp/bcp47.txt
|
|
7
|
+
* @see https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
|
|
8
|
+
*/
|
|
9
|
+
export type LanguageTag = Static<typeof LanguageTag>
|
|
10
|
+
/**
|
|
11
|
+
* Follows the IETF BCP 47 language tag schema with modifications.
|
|
12
|
+
* @see REAMDE.md file for more information on the validation.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export const pattern =
|
|
16
|
+
"^((?<grandfathered>(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?<language>([A-Za-z]{2,3}(-(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?))(-(?<script>[A-Za-z]{4}))?(-(?<region>[A-Za-z]{2}|[0-9]{3}))?(-(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*))$"
|
|
17
|
+
|
|
18
|
+
export const LanguageTag = Type.String({
|
|
19
|
+
pattern: pattern,
|
|
20
|
+
description: "The language tag must be a valid IETF BCP 47 language tag.",
|
|
21
|
+
examples: ["en", "de", "en-US", "zh-Hans", "es-419"],
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export type Literal = Static<typeof Literal>
|
|
25
|
+
export const Literal = Type.Object({
|
|
26
|
+
type: Type.Literal("literal"),
|
|
27
|
+
value: Type.String(),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A (text) element that is translatable and rendered to the UI.
|
|
32
|
+
*/
|
|
33
|
+
export type Text = Static<typeof Text>
|
|
34
|
+
export const Text = Type.Object({
|
|
35
|
+
type: Type.Literal("text"),
|
|
36
|
+
value: Type.String(),
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export type VariableReference = Static<typeof VariableReference>
|
|
40
|
+
export const VariableReference = Type.Object({
|
|
41
|
+
type: Type.Literal("variable"),
|
|
42
|
+
name: Type.String(),
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
export type Option = Static<typeof Option>
|
|
46
|
+
export const Option = Type.Object({
|
|
47
|
+
name: Type.String(),
|
|
48
|
+
value: Type.Union([Literal, VariableReference]),
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export type FunctionAnnotation = Static<typeof FunctionAnnotation>
|
|
52
|
+
export const FunctionAnnotation = Type.Object({
|
|
53
|
+
type: Type.Literal("function"),
|
|
54
|
+
name: Type.String(),
|
|
55
|
+
options: Type.Array(Option),
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* An expression is a reference to a variable or a function.
|
|
60
|
+
*
|
|
61
|
+
* Think of expressions as elements that are rendered to a
|
|
62
|
+
* text value during runtime.
|
|
63
|
+
*/
|
|
64
|
+
export type Expression = Static<typeof Expression>
|
|
65
|
+
export const Expression = Type.Object({
|
|
66
|
+
type: Type.Literal("expression"),
|
|
67
|
+
arg: Type.Union([Literal, VariableReference]),
|
|
68
|
+
annotation: Type.Optional(FunctionAnnotation),
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// export type FunctionReference = {
|
|
72
|
+
// type: "function"
|
|
73
|
+
// name: string
|
|
74
|
+
// operand?: Text | VariableReference
|
|
75
|
+
// options?: Option[]
|
|
76
|
+
// }
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* A pattern is a sequence of elements that comprise
|
|
80
|
+
* a message that is rendered to the UI.
|
|
81
|
+
*/
|
|
82
|
+
export type Pattern = Static<typeof Pattern>
|
|
83
|
+
export const Pattern = Type.Array(Type.Union([Text, Expression]))
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A variant contains a pattern that is rendered to the UI.
|
|
87
|
+
*/
|
|
88
|
+
export type Variant = Static<typeof Variant>
|
|
89
|
+
export const Variant = Type.Object({
|
|
90
|
+
/**
|
|
91
|
+
* The number of keys in each variant match MUST equal the number of expressions in the selectors.
|
|
92
|
+
*
|
|
93
|
+
* Inspired by: https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#pattern-selection
|
|
94
|
+
*/
|
|
95
|
+
// a match can always only be string-based because a string is what is rendered to the UI
|
|
96
|
+
match: Type.Array(Type.String()),
|
|
97
|
+
pattern: Pattern,
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
export type InputDeclaration = Static<typeof InputDeclaration>
|
|
101
|
+
export const InputDeclaration = Type.Object({
|
|
102
|
+
type: Type.Literal("input"),
|
|
103
|
+
name: Type.String(),
|
|
104
|
+
|
|
105
|
+
//TODO make this generic so that only Variable-Ref Expressions are allowed
|
|
106
|
+
value: Expression,
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// local declarations are not supported.
|
|
110
|
+
// Will only add when required. See discussion:
|
|
111
|
+
// https://github.com/opral/monorepo/pull/2700#discussion_r1591070701
|
|
112
|
+
//
|
|
113
|
+
// export type LocalDeclaration = Static<typeof InputDeclaration>
|
|
114
|
+
// export const LocalDeclaration = Type.Object({
|
|
115
|
+
// type: Type.Literal("local"),
|
|
116
|
+
// name: Type.String(),
|
|
117
|
+
// value: Expression,
|
|
118
|
+
// })
|
|
119
|
+
//
|
|
120
|
+
// export type Declaration = Static<typeof Declaration>
|
|
121
|
+
// export const Declaration = Type.Union([LocalDeclaration, InputDeclaration])
|
|
122
|
+
|
|
123
|
+
export type Declaration = Static<typeof Declaration>
|
|
124
|
+
export const Declaration = Type.Union([InputDeclaration])
|
|
125
|
+
|
|
126
|
+
export type Message = Static<typeof Message>
|
|
127
|
+
export const Message = Type.Object({
|
|
128
|
+
locale: LanguageTag,
|
|
129
|
+
declarations: Type.Array(Declaration),
|
|
130
|
+
/**
|
|
131
|
+
* The order in which the selectors are placed determines the precedence of patterns.
|
|
132
|
+
*/
|
|
133
|
+
selectors: Type.Array(Expression),
|
|
134
|
+
variants: Type.Array(Variant),
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
export type MessageBundle = Static<typeof MessageBundle>
|
|
138
|
+
export const MessageBundle = Type.Object({
|
|
139
|
+
id: Type.String(),
|
|
140
|
+
alias: Type.Record(Type.String(), Type.String()),
|
|
141
|
+
messages: Type.Array(Message),
|
|
142
|
+
})
|