@curly-message/parser 1.0.0-next.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 G.A.W.Group, s.r.o.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,417 @@
1
+ # @curly-message/parser
2
+
3
+ The JavaScript implementation of the
4
+ [Curly Message Format](https://github.com/curly-message/spec).
5
+
6
+ ```json
7
+ {
8
+ "greeting": "Hello, {{name; default:Guest;}}!",
9
+ "inbox": "You have {{count:number;}} {{count; 1:message; default:messages;}}."
10
+ }
11
+ ```
12
+
13
+ ```
14
+ greeting { name: 'Alice' } -> "Hello, Alice!"
15
+ greeting {} -> "Hello, Guest!"
16
+ inbox { count: 1 } -> "You have 1 message."
17
+ inbox { count: 1234 } -> "You have 1,234 messages."
18
+ ```
19
+
20
+ A placeholder is written on one line: a `{{` and a `}}` with a line terminator
21
+ anywhere between them are text rather than a placeholder, and escaping the
22
+ terminator does not make them one. A placeholder that names no key — `{{}}`,
23
+ `{{ }}` — is a placeholder still, and resolves through the fallback chain.
24
+
25
+ Placeholders may carry a modifier (`:number`, `:date`, `:ago`, `:currency`, or
26
+ one of the comparisons `eq`, `ne`, `lt`, `lte`, `gt`, `gte`), a set of options,
27
+ and a `default`. Locale-dependent formatting is delegated to `Intl`; the
28
+ package itself has no runtime dependencies. A modifier that cannot produce a
29
+ result — a locale the host rejects, a custom modifier that throws — resolves
30
+ the placeholder to its `default` rather than raising, and so does a value that
31
+ no conversion turns into text. Neither is silent: containment keeps the failure
32
+ out of the caller's render path, and a report is how the caller hears about it
33
+ anyway. A placeholder naming a modifier the parser does not know resolves to
34
+ its `default` and is reported as well; it is never run as a comparison instead.
35
+ A comparison that declares no options has been asked to select from nothing —
36
+ the inline `default` is the fallback itself rather than something to select, so
37
+ `{{v:eq; default:D}}` declares none either. It resolves to the fallback chain,
38
+ as it always did, and reports `missing-options`. That is a defect in how the
39
+ placeholder was written, so it reports whether or not the payload carries the
40
+ key. The six names are the format's comparisons while the format's own
41
+ modifier answers to them: a host that registers its own `eq` has replaced the
42
+ comparison, and whether its modifier needs options is that modifier's business,
43
+ so `{{v:eq}}` over that registration reports nothing. A placeholder naming no
44
+ key has nothing to compare, so `{{:eq}}` is no selection and reports nothing;
45
+ `{{:zz}}` still reports the modifier nobody registered.
46
+ Given no locale the formatting modifiers resolve to the empty string, not to
47
+ the fallback chain: a declared default does not stand in for a locale nobody
48
+ supplied. A caller that passes none and a caller that passes the empty string
49
+ resolve alike; one that passes a locale the host then rejects has supplied one,
50
+ and takes the fallback chain like any other formatting failure. The empty
51
+ string is reported as `missing-locale`, whose origin is the payload: a locale
52
+ nobody supplied is a defect in what the caller passed rather than in the
53
+ message that was written.
54
+
55
+ Given a locale, each formatting modifier reads its value as a particular kind
56
+ of number, and a value that is not one resolves the placeholder to its
57
+ `default` and is reported as `failed-modifier`, like any other result a
58
+ modifier could not produce. Its origin is the payload too: the value, the props
59
+ and the locale a modifier is handed are the caller's, and so is a
60
+ `customModifiers` entry that raised, so none of it is a defect in the message
61
+ that was written. The kind each reads:
62
+
63
+ | Modifier | Value |
64
+ | --- | --- |
65
+ | `number` | a number |
66
+ | `date` | milliseconds since the Unix epoch, or text the host can parse as a date |
67
+ | `ago` | a signed millisecond delta relative to now, negative for the past |
68
+ | `currency` | a number, multiplied by the `ratio` property below |
69
+
70
+ Empty text and text that is only whitespace are none of these, whatever the
71
+ host's own numeric conversion makes of them: `{{v:number}}` over `{ v: '' }`
72
+ takes the fallback chain rather than formatting a zero, and `{{v:date}}` over
73
+ the same value takes it rather than formatting the epoch.
74
+
75
+ ## Usage
76
+
77
+ ```js
78
+ import { createParser } from '@curly-message/parser';
79
+
80
+ const { resolve } = createParser();
81
+
82
+ resolve('Hello, {{name; default:Guest;}}!', { payload: { name: 'Alice' }, locale: 'en' });
83
+ // -> 'Hello, Alice!'
84
+ ```
85
+
86
+ `createParser(options?)` returns a parser whose `resolve(message, context?)`
87
+ takes the four inputs resolution is defined over, plus the message's own key:
88
+
89
+ | Context | Meaning |
90
+ | --- | --- |
91
+ | `payload` | The values the placeholders name, or the configuration of one — see [Payload](#payload). Its `default` key is the message-wide fallback. |
92
+ | `props` | Per-call formatting options handed to the modifiers, keyed by modifier name. A payload entry layers over them. |
93
+ | `locale` | The locale the locale-dependent modifiers format for. |
94
+ | `key` | The message's identifier. A missing message resolves to the payload's `default`, and to this where the payload carries none, echoed as the caller spelled it. |
95
+
96
+ A message that no conversion describes is a message that does not exist:
97
+ resolution steps past it to the payload's `default` and then to the key, and
98
+ reports nothing, because a message nobody wrote is not a defect. The link it
99
+ steps to is a payload value like any other, so one that is read and cannot be
100
+ described is reported.
101
+
102
+ The key is where that chain ends, and it is not text the format resolves over.
103
+ A key shaped like a placeholder is echoed rather than resolved, an escape
104
+ sequence inside one stays as it was spelled, and nothing behind the key is read
105
+ a second time. It still becomes text, because `resolve` answers with text.
106
+
107
+ `options` carries `customModifiers`, `modifierDefaults` and `onReport`. Nothing
108
+ else is read, and the package has no runtime dependencies — locale-dependent
109
+ formatting is delegated to `Intl`.
110
+
111
+ `customModifiers` registers modifiers by name, over the built-in ones, so a
112
+ name it carries is a name a message may write, and so is a name the parser
113
+ holds a modifier under already. What it registers has to be a modifier: an
114
+ entry that cannot be called registers none, so it takes no name of its own and
115
+ does not shadow the built-in it names. Where nothing else answers to the name,
116
+ a message writing it reads it as one nobody registered — `unknown-modifier`,
117
+ the fallback chain; where a built-in answers to it, that built-in answers as it
118
+ did, so `{ eq: 'text' }` costs a caller the name it wrote and nothing further.
119
+ The types say as much, but a JavaScript caller reaches the table regardless.
120
+
121
+ `onReport` is where diagnostics go. The parser writes to no channel of its own,
122
+ so unset it reports nowhere; resolution still fails soft, it just does so
123
+ silently. It is called with a `Report` describing what the parser could not do
124
+ — `code`, the `origin` that code declares, an English `message` carrying
125
+ nothing from the payload, the `limit` reached where the report is about one,
126
+ the message's `key` where one was passed, and `text`, the source of the
127
+ trouble: the placeholder that named it for `unknown-modifier`,
128
+ `failed-modifier`, `missing-options`, `missing-locale` and
129
+ `unserializable-value`, the output that would not settle for `pass-limit` and
130
+ `output-limit`, the message as it was passed where the read that refused is of
131
+ the call's own structure — a context entry, an entry of the option bag — and
132
+ nothing at all where what could not be described is the chain the message
133
+ itself resolves through. The last two name no placeholder, and a message that
134
+ is not text carries none of itself either. `origin` says who fixes what `code`
135
+ names — `'message'` for a defect in the message that was written, `'payload'`
136
+ for one in what the caller passed, and `'limit'` for a bound this parser set.
137
+ Every code declares one, and it ranks nothing: a report is no graver for coming
138
+ from one of the three than from another. Only `text` derives from the payload.
139
+ It is cut to 120 UTF-16 code units of what reached it — what a string's
140
+ `length` counts — or one fewer where the last of them is the high half of a
141
+ surrogate pair, so the cut never severs a character. A cut is marked with a
142
+ trailing `...` of the parser's own, and the excerpt is escaped after that —
143
+ quotes, backslashes, and every line terminator. So a cut excerpt arrives at
144
+ 122 code units at the shortest, one carrying something to escape arrives
145
+ longer still, and no payload can forge a line where a report is written.
146
+
147
+ Two guards bound resolution, and reaching either is what the two limit codes
148
+ report. A payload value may name another placeholder, so interpolation runs
149
+ again over what the last pass produced — at most **10 passes**, after which the
150
+ output is returned with its remaining placeholders unresolved. And the output
151
+ may not exceed **100000 UTF-16 code units** — what a string's `length` counts,
152
+ so a character outside the Basic Multilingual Plane counts twice; a pass that
153
+ would carry it past that stops, and the last output under the bound is what
154
+ resolves. Both are what make
155
+ a payload value that references or multiplies its own placeholder terminate
156
+ rather than hang the caller.
157
+
158
+ A third bound holds the conversion that feeds them. Turning a value into JSON
159
+ follows a shared reference again every time it meets one, so a value naming the
160
+ same child twice at each of twenty-four levels holds twenty-five objects and
161
+ describes sixteen million leaves — no cycle anywhere, and nothing an output
162
+ bound measured after the fact can prevent. So the walk stops after **100000
163
+ nodes**, and the value is read as one no conversion describes: it falls through
164
+ its fallback chain and reports as `unserializable-value`. That is what a single
165
+ conversion may spend, and a resolution converts one value once however many
166
+ placeholders name it, so what a resolution spends converting is bounded by the
167
+ values it reaches rather than by the reads it makes of them. That covers the
168
+ host's own conversion as well as the JSON walk, so a class instance whose
169
+ `toString` runs host code runs it once for the resolution rather than once for
170
+ each placeholder, and one that raises is not asked a second time. It covers a
171
+ bigint as well, which runs no host code at all but converts to digits the
172
+ engine works out afresh on every read. A value built afresh on each read — by
173
+ a payload getter, or by a custom modifier — is a new value every time and is
174
+ converted every time. Whether an entry is a wrapper is asked the same way:
175
+ recognizing one enumerates the entry's own names, work that grows with the
176
+ entry, so a resolution asks one entry once however many placeholders name it,
177
+ and an entry that refuses the question is not asked again — though it reports
178
+ at every placeholder that reads it.
179
+
180
+ All three bounds belong to the call rather than to the parser, and a call is
181
+ what a host begins by calling `resolve` again while one is running — from a
182
+ custom modifier, from an `onReport` handler writing its diagnostic into a
183
+ translated string, from a payload accessor, or from a value's own `toString`.
184
+ Such a resolution counts its own passes, spends its own output and keeps its
185
+ own record of what it has converted, so neither it nor the one around it can
186
+ reach a bound the other owns, each report names the message its own call was
187
+ resolving, and a value the two share is converted once for each of them.
188
+ Nothing bounds how deep that goes: a modifier resolving a message that names
189
+ it again recurses until the host's own stack runs out, which is contained like
190
+ any other failure — the placeholder takes its fallback chain and `resolve`
191
+ still answers with text.
192
+
193
+ ## Payload
194
+
195
+ Everything the format carries is text. A payload value reaches a modifier, and
196
+ the output, as text whatever type it was written at: a plain object and an
197
+ array become JSON, and every other value becomes what the host makes of it, so
198
+ a `Date`, a `RegExp` or a class instance reads as its own `toString` writes it.
199
+
200
+ ```
201
+ { v: 1234.5 } -> 1234.5
202
+ { v: [1, 2] } -> [1,2]
203
+ { v: { a: 1 } } -> {"a":1}
204
+ { v: /re/g } -> /re/g
205
+ ```
206
+
207
+ That conversion costs a `Date` its sub-second precision, because `String(date)`
208
+ writes seconds: `{{v:date}}` over `new Date('2024-03-05T10:00:00.123Z')`
209
+ renders the same instant with its milliseconds zeroed. The text is not numeric
210
+ either, so `{{v:number}}`, `{{v:currency}}`, `{{v:ago}}`, `{{v:lt}}` and
211
+ `{{v:gt}}` over a `Date` resolve to the fallback chain — the three formatting
212
+ ones given a locale, because with none they resolve to the empty string
213
+ whatever the value is. Pass a timestamp or an ISO string where a placeholder
214
+ needs either.
215
+
216
+ A payload entry may carry the value's own configuration — a wrapper — instead
217
+ of the value itself:
218
+
219
+ ```js
220
+ resolve('You have {{count:number}} points.', {
221
+ payload: {
222
+ count: { value: 1234.56, props: { number: { maximumFractionDigits: 1 } } },
223
+ },
224
+ locale: 'en',
225
+ });
226
+ // -> 'You have 1,234.6 points.'
227
+ ```
228
+
229
+ An entry is a wrapper when it is a plain object that owns at least one key and
230
+ every key it owns is `value`, `default` or `props`. An entry owning anything
231
+ else is a value, wrapper-shaped or not: `{ value: 1, unit: 'kg' }` and `{}` are
232
+ data and become JSON. Unwrapping happens once, so a wrapper's `value` is never
233
+ read as a wrapper of its own, and a wrapper carrying no `value` falls back like
234
+ a key the payload does not carry. The payload's own `default` is always a
235
+ value.
236
+
237
+ A placeholder resolves to its value wherever the payload carries one, and
238
+ otherwise to the first of these that yields text:
239
+
240
+ 1. the wrapper's `default`
241
+ 2. the payload's `default`
242
+ 3. the `default` the placeholder declares
243
+ 4. the empty string
244
+
245
+ A value no conversion describes — a structure that references itself, a getter
246
+ that raises — is read as missing and falls through this chain, and so does any
247
+ link in it. Anything the chain reads and could not convert is reported as
248
+ `unserializable-value`; a link nothing reaches is never read, so it is never
249
+ reported either.
250
+
251
+ Every entry the parser reads is read as an own **enumerable** property: a
252
+ payload key, a wrapper's, a `props` layer's, an entry of the modifier registry,
253
+ and the call's own `payload`, `props`, `locale` and `key`. That is the rule the
254
+ value conversion has always followed, so a property `Object.defineProperty`
255
+ left hidden is not one the format carries anywhere — after
256
+ `Object.defineProperty(payload, 'v', { value: 'V' })`, `{{v}}` resolves to its
257
+ fallback chain, and a hidden `onReport` reports nowhere. A prototype somebody
258
+ else wrote to supplies nothing at any of those reads either.
259
+
260
+ A read that raises is an absence and a report wherever it sits, not only at a
261
+ value. A `props` entry that refuses to be read leaves the layer beneath it
262
+ standing and reports `unserializable-value` at the placeholder that needed it;
263
+ `customModifiers`, `modifierDefaults` and the context's own entries are read
264
+ once for the call, so a report about one names the text that went looking
265
+ rather than a placeholder, and a `key` that is itself the entry that raised
266
+ leaves the report naming no key.
267
+
268
+ A modifier's answer becomes text by that same conversion, so an answer no
269
+ conversion describes is read as missing and reported the same way, and the
270
+ placeholder takes the chain. An answer that is nothing at all is an absent
271
+ answer rather than one that could not be described: it takes the chain too, and
272
+ says nothing.
273
+
274
+ A modifier reaches the chain by reading its own `defaultValue`, which resolves
275
+ at the moment of that read — running whatever host code the chain carries and
276
+ reporting a link it cannot describe. A generic copy of a modifier's config is
277
+ such a read, so a rest destructure, a spread or `JSON.stringify` walks a chain
278
+ that a modifier taking the keys it needs by name leaves alone.
279
+
280
+ Formatting options are keyed by modifier name, and their layers compose per
281
+ property: the parser's `modifierDefaults`, then the `props` the call passes,
282
+ then the wrapper's own `props`. Each layer overrides only the properties it
283
+ names, so a layer cannot reset an earlier one: a property set to `undefined`
284
+ names nothing, where one set to the host's null is named and null — a value,
285
+ like zero — is what the modifier is handed. Only what a layer owns composes,
286
+ and only under the name the placeholder wrote: what a layer holds under other
287
+ names is not read for it. The object a modifier is handed owns every entry it
288
+ is configured with and carries no prototype, so a prototype somebody else wrote
289
+ to configures nothing.
290
+
291
+ ```
292
+ modifierDefaults { number: { maximumFractionDigits: 4, useGrouping: false } }
293
+ call props { number: { useGrouping: true } }
294
+ wrapper props { number: { maximumFractionDigits: 1 } }
295
+ effective { maximumFractionDigits: 1, useGrouping: true } -> 1,234.6
296
+ ```
297
+
298
+ A modifier is handed that composition under its own name and nothing else — the
299
+ `effective` line is what `number` reads — so what one modifier is configured
300
+ with never reaches the next, and a modifier nobody configured is handed an empty
301
+ object rather than nothing. A modifier a host registers reads its properties the
302
+ same way, `modifierDefaults` included, and the object it holds is the parser's
303
+ own copy: writing into it reaches neither the next placeholder nor the caller.
304
+
305
+ `number` formats at most two fraction digits when no layer names a maximum.
306
+ That two is a default rather than a cap: a layer naming a
307
+ `minimumFractionDigits` above it widens the default to reach it, the way
308
+ `Intl.NumberFormat` widens its own.
309
+
310
+ `currency` formats in the currency style whatever a layer names as the style:
311
+ that style is the modifier rather than one of the options it layers. It
312
+ multiplies its value by a `ratio` property first, defaulting to 1, so a payload
313
+ carrying minor units renders as major ones.
314
+
315
+ `ago` takes the unit to count in from a `format` property holding a unit name,
316
+ in the singular or the plural: `second`, `minute`, `hour`, `day`, `week`,
317
+ `month` and `year` are the rungs of the ladder it climbs. Its `auto`, which is
318
+ what a layer naming none leaves in place, selects the unit from the magnitude
319
+ of the delta instead. A `format` naming anything else — a unit `Intl` knows and
320
+ this ladder does not climb, a rung spelled in another case — is a property the
321
+ modifier cannot process: the placeholder takes its fallback chain and reports
322
+ `failed-modifier`.
323
+
324
+ `ratio` and `format` are the format's own properties rather than `Intl`'s, and
325
+ both are read from the layers like every other property — a message cannot
326
+ write either as an option.
327
+
328
+ ```
329
+ { v: 2 } { currency: { currency: 'USD', ratio: 100 } } -> $200.00
330
+ { v: -172800000 } { ago: {} } -> 2 days ago
331
+ { v: -172800000 } { ago: { format: 'hour' } } -> 48 hours ago
332
+ ```
333
+
334
+ ## Escaping
335
+
336
+ The syntax reserves a colon, a semicolon, either brace, a backslash and
337
+ whitespace. A backslash takes the structural meaning away from the character
338
+ that follows it, and the rule is the same everywhere in a message — inside a
339
+ placeholder and in the text around it alike.
340
+
341
+ Whitespace means the twenty-five code points the specification enumerates, not
342
+ whatever the host calls whitespace: a host's own class is defined over a
343
+ Unicode category that has changed membership before.
344
+
345
+ ```
346
+ Braces are written \{\{ like this \}\} -> "Braces are written {{ like this }}"
347
+ Hello, {{first\ name; default:Guest}}! -> names the payload key "first name"
348
+ {{count; 1:one\ ; default:none}} -> keeps the trailing space
349
+ C:\\temp -> "C:\temp"
350
+ ```
351
+
352
+ The rule reaches the braces themselves: a brace a backslash consumed is text,
353
+ so it cannot be half of a delimiter. That is what lets a key end in a closing
354
+ brace; one that starts no pair needs no escape.
355
+
356
+ ```
357
+ \{{v}} -> "{{v}}" text, whatever the payload carries
358
+ \\{{v}} -> a backslash, then the placeholder {{v}}
359
+ {{v\}} -> "{{v}}" no closing pair, so the whole run is text
360
+ {{v\}}} -> names the payload key "v}"
361
+ {{v\\}} -> names the payload key "v\"
362
+ {{a}b}} -> names the payload key "a}b"
363
+ ```
364
+
365
+ Before anything the syntax does not reserve, a backslash is text itself, so a
366
+ regular expression or a Windows path survives as typed: `\d+` resolves to
367
+ `\d+`, and `C:\Users\name` to `C:\Users\name`.
368
+
369
+ A payload value is read the same way, because a value may carry a placeholder
370
+ of its own. A value that has to keep a backslash in front of a reserved
371
+ character doubles it — `\\server\share` resolves to `\server\share`.
372
+
373
+ Escape sequences are removed once, from the finished text, so the removal
374
+ reaches the text a conversion produced as readily as the text a message was
375
+ written in. The two characters JSON writes for a backslash are an escape
376
+ sequence, and the removal takes one of them, so the JSON a plain object
377
+ serializes to does not necessarily reach the output parsable as JSON.
378
+
379
+ ```
380
+ { v: { a: 'C:\U' } } serializes to {"a":"C:\\U"} and renders {"a":"C:\U"}
381
+ ```
382
+
383
+ A modifier is unaffected, because it reads its value before the removal runs:
384
+ one that parses a serialized object back reads the serialization the conversion
385
+ produced. A caller that needs the result itself to parse passes the text it
386
+ wants as an ordinary string value, with every backslash doubled.
387
+
388
+ ## Status
389
+
390
+ **Unreleased, and the public surface is unstable.**
391
+
392
+ Nothing here references a host framework: `resolve` takes the format's own
393
+ inputs, and an adapter that presents this parser to a host library belongs in
394
+ that host's own repository.
395
+
396
+ This implementation satisfies every conformance level the specification
397
+ defines: **Core**, **Intl** and **Extensions**. Section 2 asks an
398
+ implementation to say so, because a level it does not satisfy changes what a
399
+ message resolves to rather than merely what it can do — without Intl, `number`,
400
+ `date`, `ago` and `currency` are modifier names nobody registered.
401
+
402
+ The specification is normative — where this implementation and the
403
+ specification disagree, this implementation is wrong.
404
+
405
+ ## Development
406
+
407
+ ```bash
408
+ npm install
409
+ npm test # builds, typechecks, lints, then runs vitest
410
+ npm run lint:fix # applies what the lint step only reports
411
+ ```
412
+
413
+ Requires Node.js 22 or newer.
414
+
415
+ ## License
416
+
417
+ [MIT](./LICENSE)
@@ -0,0 +1,297 @@
1
+ /// <reference lib="es2020.intl" />
2
+ declare const eq: Modifier.T;
3
+ declare const ne: Modifier.T;
4
+ declare const lt: Modifier.T;
5
+ declare const gt: Modifier.T;
6
+ declare const lte: Modifier.T;
7
+ declare const gte: Modifier.T;
8
+ declare const number: Modifier.T<Modifier.NumberProperties>;
9
+ declare const date: Modifier.T<Modifier.DateProperties>;
10
+ declare const ago: Modifier.T<Modifier.AgoProperties>;
11
+ declare const currency: Modifier.T<Modifier.CurrencyProperties>;
12
+
13
+ declare const modifiers_ago: typeof ago;
14
+ declare const modifiers_currency: typeof currency;
15
+ declare const modifiers_date: typeof date;
16
+ declare const modifiers_eq: typeof eq;
17
+ declare const modifiers_gt: typeof gt;
18
+ declare const modifiers_gte: typeof gte;
19
+ declare const modifiers_lt: typeof lt;
20
+ declare const modifiers_lte: typeof lte;
21
+ declare const modifiers_ne: typeof ne;
22
+ declare const modifiers_number: typeof number;
23
+ declare namespace modifiers {
24
+ export { modifiers_ago as ago, modifiers_currency as currency, modifiers_date as date, modifiers_eq as eq, modifiers_gt as gt, modifiers_gte as gte, modifiers_lt as lt, modifiers_lte as lte, modifiers_ne as ne, modifiers_number as number };
25
+ }
26
+
27
+ declare const AGO_LADDER: readonly [{
28
+ readonly key: "second";
29
+ readonly multiplier: 1000;
30
+ }, {
31
+ readonly key: "minute";
32
+ readonly multiplier: 60;
33
+ }, {
34
+ readonly key: "hour";
35
+ readonly multiplier: 60;
36
+ }, {
37
+ readonly key: "day";
38
+ readonly multiplier: 24;
39
+ }, {
40
+ readonly key: "week";
41
+ readonly multiplier: 7;
42
+ }, {
43
+ readonly key: "month";
44
+ readonly multiplier: number;
45
+ }, {
46
+ readonly key: "year";
47
+ readonly multiplier: 12;
48
+ }];
49
+
50
+ /**
51
+ * A locale as it reaches resolution — an opaque identifier the modifiers hand
52
+ * to `Intl`. The format neither parses nor validates it.
53
+ */
54
+ type Locale = string;
55
+ /**
56
+ * Holds a position out of the inference the surrounding call does. A type
57
+ * parameter is decided where it is declared or where its own argument names it
58
+ * — never by a second position that merely has to agree with it.
59
+ */
60
+ type Given<T> = [T][T extends any ? 0 : never];
61
+ /**
62
+ * The branches of an all-optional bag that owns at least one of its keys: one
63
+ * per key, each requiring that key be present and leaving the rest optional.
64
+ * A key holding `undefined` is owned; the empty object owns none.
65
+ */
66
+ type AtLeastOne<T> = {
67
+ [Key in keyof T]-?: Record<Key, T[Key]> & Omit<T, Key>;
68
+ }[keyof T];
69
+ /**
70
+ * A diagnostic the parser hands to its caller. The format does not specify a
71
+ * channel to report through, so the parser writes nowhere itself and describes
72
+ * what happened instead.
73
+ */
74
+ type Report = {
75
+ /** What stopped resolution. */
76
+ code: 'unknown-modifier' | 'failed-modifier' | 'missing-options' | 'unserializable-value' | 'missing-locale' | 'pass-limit' | 'output-limit';
77
+ /**
78
+ * Which of the three the defect belongs to, and so who fixes it: the message
79
+ * that was written, what the caller passed, or a limit this parser set. Every
80
+ * code declares one. It ranks nothing — a report is no graver for coming from
81
+ * one of the three than from another.
82
+ */
83
+ origin: 'message' | 'payload' | 'limit';
84
+ /**
85
+ * The same in English. It is self-contained and carries nothing from the
86
+ * payload, so writing it anywhere is safe without further thought.
87
+ */
88
+ message: string;
89
+ /** The message's own key, where the caller passed one. */
90
+ key?: Parser.Key;
91
+ /** The limit that was reached, where the report is about one. */
92
+ limit?: number;
93
+ /**
94
+ * Where the trouble came from: the placeholder for a report about one, the
95
+ * output that would not settle for the two limits, the message as the caller
96
+ * wrote it where a read of the call's own structure refused — a context
97
+ * entry, an entry of the option bag — and nothing at all where what could
98
+ * not be described is the chain a message itself resolves through. The last
99
+ * two name no placeholder, and a message that is not text carries none of
100
+ * itself either. Truncated — a cut is marked with a trailing `...` of the
101
+ * parser's own — and with its line terminators escaped, so payload content
102
+ * cannot forge a line wherever this is written.
103
+ */
104
+ text: string;
105
+ };
106
+ declare namespace Modifier {
107
+ export type Key = string;
108
+ export type DefaultKeys = keyof typeof modifiers;
109
+ type AgoStep = (typeof AGO_LADDER)[number]['key'];
110
+ /**
111
+ * A unit `ago` can resolve to, read off the ladder it climbs rather than off
112
+ * `Intl`'s whole vocabulary: a `format` naming a unit the ladder does not
113
+ * climb is one the modifier cannot format with, so the placeholder takes the
114
+ * fallback chain and the failure is reported. Every step is accepted in the
115
+ * plural too, so a layer naming one may spell it either way.
116
+ */
117
+ export type AgoUnit = AgoStep | `${AgoStep}s`;
118
+ /**
119
+ * What a modifier of this name is handed: the properties composed under it,
120
+ * which is what the layers keyed by that name carry. The `*Props` type
121
+ * beside each is the layer itself — one name, holding those properties.
122
+ */
123
+ export type AgoProperties = Intl.RelativeTimeFormatOptions & {
124
+ format?: AgoUnit | 'auto';
125
+ };
126
+ export type AgoProps = {
127
+ ago?: AgoProperties;
128
+ };
129
+ export type DateProperties = Intl.DateTimeFormatOptions;
130
+ export type DateProps = {
131
+ date?: DateProperties;
132
+ };
133
+ export type NumberProperties = Intl.NumberFormatOptions;
134
+ export type NumberProps = {
135
+ number?: NumberProperties;
136
+ };
137
+ export type CurrencyProperties = Intl.NumberFormatOptions & {
138
+ ratio?: number;
139
+ };
140
+ export type CurrencyProps = {
141
+ currency?: CurrencyProperties;
142
+ };
143
+ export type DefaultProps = NumberProps & AgoProps & DateProps & CurrencyProps;
144
+ export type Props<T = DefaultProps> = T & DefaultProps;
145
+ export type ModifierOption = Record<'key' | 'value', string>;
146
+ /**
147
+ * A value's own configuration, standing in the payload where the value
148
+ * would. An entry is a wrapper only when it is a plain object that owns at
149
+ * least one key and every key it owns is one of these; an entry with a
150
+ * prototype of its own, one owning anything else, or one owning nothing at
151
+ * all, is a value, wrapper-shaped or not.
152
+ */
153
+ export type Wrapper<Value = any, CustomModifierProps = DefaultProps> = AtLeastOne<{
154
+ /** The value itself. A wrapper carrying none falls back like a missing key. */
155
+ value?: Value;
156
+ /** Tried before the payload's own `default` and before the inline one. */
157
+ default?: any;
158
+ /** Layered over the `props` the call passes, property by property. */
159
+ props?: Props<CustomModifierProps>;
160
+ }>;
161
+ /**
162
+ * A modifier is handed text and nothing else: a placeholder whose value is
163
+ * absent takes its fallback chain before any modifier is called, and that
164
+ * chain ends in the empty string.
165
+ *
166
+ * `OwnProps` is what this modifier's own name holds, because that
167
+ * composition is what it is handed; `CustomModifierProps` is the table that
168
+ * name sits in, which is what `parserOptions` is read under. A modifier
169
+ * registered through `customModifiers` is given both by the table it is
170
+ * registered in, so only one written down away from its table names them.
171
+ * The empty composition is the default, because that is what a name nobody
172
+ * configured holds.
173
+ */
174
+ export type T<OwnProps = {}, CustomModifierProps = DefaultProps> = (config: {
175
+ value: string;
176
+ /**
177
+ * The properties composed under this modifier's own name, layered from the
178
+ * implementation defaults up through the wrapper's own and copied, so a
179
+ * modifier that writes into what it was handed reaches neither the next
180
+ * placeholder nor the caller. A modifier nobody configured is handed an
181
+ * empty object.
182
+ */
183
+ props: OwnProps;
184
+ locale?: Locale;
185
+ parserOptions?: Parser.Options<Modifier.Key, CustomModifierProps>;
186
+ options: ModifierOption[];
187
+ /**
188
+ * The fallback chain, resolved by the read rather than before the modifier
189
+ * was called: reading it walks the wrapper's `default`, then the payload's,
190
+ * then the one the placeholder declared, runs whatever host code those
191
+ * links carry, and reports one no conversion describes. A generic copy of
192
+ * the config is such a read — a rest destructure, a spread,
193
+ * `JSON.stringify` — so a modifier with no use for the default takes the
194
+ * keys it needs by name.
195
+ */
196
+ defaultValue: string;
197
+ }) => any;
198
+ export type DefaultModifiers = typeof modifiers;
199
+ /**
200
+ * Modifiers by the name each answers to. A name the table declares
201
+ * properties for holds a modifier reading that slice; one it declares none
202
+ * for holds a modifier reading none by name. The slice reaches an entry
203
+ * through the names the table is typed with — the ones the factory infers
204
+ * from the table it is given, or the ones an annotation names — so a table
205
+ * or an option bag annotated without them types every entry, one under a
206
+ * built-in name included, as reading none.
207
+ */
208
+ export type CustomModifiers<K extends string = Key, ModifierProps = DefaultProps> = {
209
+ [Name in K]: Modifier.T<Name extends keyof Props<ModifierProps> ? NonNullable<Props<ModifierProps>[Name]> : {}, ModifierProps>;
210
+ };
211
+ export { };
212
+ }
213
+ declare namespace Parser {
214
+ type OnReport = (report: Report) => void;
215
+ type Options<Key extends string = Modifier.Key, Props = Modifier.DefaultProps> = {
216
+ /**
217
+ * Modifiers registered by name, over the built-in ones. A name a message
218
+ * may write is one the parser holds a modifier under or one a host
219
+ * registered one under, so an entry that is not a modifier registers none:
220
+ * it takes no name of its own and shadows no built-in. The name then reads
221
+ * as one nobody registered where nothing else answers to it, and answers
222
+ * as it did where a built-in does.
223
+ */
224
+ customModifiers?: Modifier.CustomModifiers<Key, Props>;
225
+ /**
226
+ * The bottom formatting layer, keyed by modifier name. It carries the same
227
+ * names the call's own `props` does, host-defined modifiers included — a
228
+ * modifier a host can configure per call it can also give defaults.
229
+ */
230
+ modifierDefaults?: Modifier.Props<Given<Props>>;
231
+ /**
232
+ * Where diagnostics go. Unset, the parser reports nowhere — resolution
233
+ * still fails soft, it just does so silently.
234
+ */
235
+ onReport?: OnReport;
236
+ };
237
+ type PayloadDefault = {
238
+ [key in 'default']?: any;
239
+ };
240
+ /** What a payload carries under a key: the value, or its configuration. */
241
+ type PayloadEntry<Value = any, Props = Modifier.DefaultProps> = Value | Modifier.Wrapper<Value, Props>;
242
+ /**
243
+ * The values a message's placeholders name, plus `default` — the fallback
244
+ * for every key the payload does not carry.
245
+ *
246
+ * A value reaches a modifier as text: a plain object and an array become
247
+ * JSON, and anything else becomes what the host makes of it. An entry may
248
+ * instead be a `Modifier.Wrapper`, which configures the value it carries.
249
+ *
250
+ * A `Date` loses its sub-second precision to that conversion, and the text
251
+ * `String` writes for one is not numeric, so `number`, `currency`, `ago`,
252
+ * `lt` and `gt` over a `Date` resolve to the fallback chain — the three
253
+ * formatting ones given a locale, because with none they resolve to the
254
+ * empty string whatever the value is. A timestamp or an ISO string keeps
255
+ * both.
256
+ *
257
+ * A value passes through the same unescaping as the message around it: a
258
+ * backslash before a character the syntax reserves — `:`, `;`, `{`, `}`, a
259
+ * backslash, or whitespace — writes that character as text and is dropped
260
+ * itself, while a backslash before anything else is left as it is. So a
261
+ * value holding `\d+` arrives as typed, and one holding `\\server\share`
262
+ * resolves to `\server\share` unless each consumed backslash is doubled.
263
+ */
264
+ type Payload<T = any, Props = Modifier.DefaultProps> = [Exclude<keyof T, keyof PayloadDefault>] extends [never] ? Record<string, PayloadEntry<any, Props>> & PayloadDefault : {
265
+ [Key in keyof T]: PayloadEntry<T[Key], Props>;
266
+ } & PayloadDefault;
267
+ type Key = string;
268
+ type Value = any;
269
+ /**
270
+ * Everything resolution reads besides the message itself. `key` is the
271
+ * message's own identifier where the caller has one. A missing message
272
+ * resolves to the payload's own `default`, and to `key` where the payload
273
+ * carries none — the same chain a placeholder falls through, one level up.
274
+ */
275
+ type Context<P = PayloadDefault, M = Modifier.DefaultProps> = {
276
+ payload?: Payload<P, M>;
277
+ props?: Modifier.Props<M>;
278
+ locale?: Locale;
279
+ key?: Key;
280
+ };
281
+ type Resolve<C extends Parser.Context = Parser.Context> = (message: Value, context?: C) => string;
282
+ type T<C extends Parser.Context = Parser.Context> = {
283
+ /**
284
+ * Interpolates the message against the given context and returns the result.
285
+ */
286
+ resolve: Resolve<C>;
287
+ };
288
+ /**
289
+ * The payload type comes first: with no host config to carry it, the factory
290
+ * is where a caller declares what its messages expect.
291
+ */
292
+ type Factory = <Payload = {}, Props = {}, Key extends string = Modifier.Key>(options?: Parser.Options<Key, Props>) => Parser.T<Parser.Context<Payload & PayloadDefault, Props & Modifier.DefaultProps>>;
293
+ }
294
+
295
+ declare const createParser: Parser.Factory;
296
+
297
+ export { type Locale, Modifier, Parser, type Report, createParser };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ var Ae=Object.defineProperty;var Oe=(e,n)=>{for(var t in n)Ae(e,t,{get:n[t],enumerable:!0})};var U={};Oe(U,{ago:()=>Ve,currency:()=>$e,date:()=>Se,eq:()=>te,gt:()=>he,gte:()=>De,lt:()=>ge,lte:()=>Ne,ne:()=>ke,number:()=>Le});var le=[`
2
+ `,"\r","\u2028","\u2029"],N=[{key:"second",multiplier:1e3},{key:"minute",multiplier:60},{key:"hour",multiplier:60},{key:"day",multiplier:24},{key:"week",multiplier:7},{key:"month",multiplier:4.333333333333333},{key:"year",multiplier:12}],Z=e=>`\\u${e.charCodeAt(0).toString(16).padStart(4,"0")}`,G=e=>!/[^\t\n\v\f\r\u0020\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/.test(e),d=(e,n,t)=>{try{return n!==void 0&&e&&Object.prototype.propertyIsEnumerable.call(e,n)?e[n]:void 0}catch{t?.();return}},k=(e,n)=>{if(!e)return[];try{return Object.keys(e)}catch{return n?.(),[]}},M=(e,n,t)=>{let o=Object.create(null);return k(e,t).forEach(r=>{o[r]=d(e,r,t)}),k(n,t).forEach(r=>{let i=d(n,r,t);i!==void 0&&(o[r]=i)}),o},ee=(e,n)=>{let t=Object.create(null);return k(e,n).forEach(o=>{let r=d(e,o,n);typeof r=="function"&&(t[o]=r)}),t},l=class extends Error{constructor(t){super(t);this.code=t}code},pe=e=>{try{return e instanceof l?e.code:void 0}catch{return}},P=e=>{if(typeof e=="string"&&G(e))return;let n=+e;return Number.isFinite(n)?n:void 0},me=e=>{let n=P(e);if(n!==void 0)return n;let t=Date.parse(e);return Number.isNaN(t)?void 0:t};var W=(e,n)=>e?e.value:n.defaultValue,te=e=>{let{value:n,options:t=[]}=e,o=`${n}`.toLowerCase();return W(t.find(({key:r})=>`${r}`.toLowerCase()===o),e)},ke=e=>{let{value:n,options:t=[]}=e,o=`${n}`.toLowerCase();return W(t.find(({key:r})=>`${r}`.toLowerCase()!==o),e)},ye=(e,n)=>e.filter(({key:t})=>!Number.isNaN(+t)).sort((t,o)=>n(+t.key,+o.key)),ge=e=>{let{value:n,options:t=[]}=e,o=+n;return W(ye(t,(r,i)=>r-i).find(({key:r})=>o<+r),e)},he=e=>{let{value:n,options:t=[]}=e,o=+n;return W(ye(t,(r,i)=>i-r).find(({key:r})=>o>+r),e)},Ne=e=>te({value:e.value,props:e.props,options:e.options,get defaultValue(){return ge(e)}}),De=e=>te({value:e.value,props:e.props,options:e.options,get defaultValue(){return he(e)}}),D=e=>{if(e===void 0)throw new l("failed-modifier");return e},Le=e=>{let{value:n,props:t,locale:o=""}=e;if(!o)throw new l("missing-locale");let r=D(P(n)),i=Number(d(t,"minimumFractionDigits"))||0,s=K(d(t,"maximumFractionDigits"),Math.max(i,2));return new Intl.NumberFormat(o,M(t,{maximumFractionDigits:s})).format(r)},Se=e=>{let{value:n,props:t,locale:o=""}=e;if(!o)throw new l("missing-locale");let r=D(me(n));return new Intl.DateTimeFormat(o,M(t,void 0)).format(r)},K=(e,n)=>e===void 0?n:e,ne=(e="",n="")=>new RegExp(`^${e}s?$`).test(n),je=e=>e==="auto"||N.some(({key:n})=>ne(n,e)),Ce=e=>N.indexOf(N.find(({key:n})=>ne(n,e))),_e=e=>Math.sign(e)*Math.round(Math.abs(e)),Fe=(e,n)=>N.reduce(([t,o],{key:r,multiplier:i},s)=>{if(ne(o,n))return[t,o];if(!o||s===Ce(o)+1){let a=_e(t/i);if(!o||Math.abs(a)>=1||n!=="auto")return[a,r]}return[t,o]},[e,""]),Ve=e=>{let{value:n,locale:t="",props:o}=e;if(!t)throw new l("missing-locale");let r=D(P(n)),i=K(d(o,"numeric"),"auto"),s=K(d(o,"format"),"auto");if(!je(s))throw new l("failed-modifier");let a=Fe(r,s);return new Intl.RelativeTimeFormat(t,M(o,{numeric:i})).format(...a)},$e=e=>{let{value:n,locale:t="",props:o}=e;if(!t)throw new l("missing-locale");let r=D(P(n)),i=D(P(r*K(d(o,"ratio"),1)));return new Intl.NumberFormat(t,M(o,{style:"currency"})).format(i)};var Re=`[${le.map(Z).join("")}]`,be=new RegExp(Re),ve=new RegExp(Re,"g"),ze=(e,n)=>{for(let t=n+2;t<e.length;t+=1){let o=e[t];if(o==="\\"){if(be.test(e.charAt(t+1)))return;t+=1;continue}if(be.test(o)||o==="{"&&e.charAt(t+1)==="{")return;if(o==="}"&&e.charAt(t+1)==="}")return t+2}},re=(e,n)=>{for(let t=n;t<e.length;t+=1){if(e[t]==="\\"){t+=1;continue}if(e[t]!=="{"||e.charAt(t+1)!=="{")continue;let o=ze(e,t);if(o!==void 0)return[t,o]}},Ge=e=>typeof e=="string"&&!!re(e,0),Ke=/[:;{}\\]/,q=e=>typeof e=="string"?e.replace(/\\([\s\S])/g,(n,t)=>Ke.test(t)||G(t)?t:n):e,L=e=>{let n=-1,t=0;for(let o=0;o<e.length;o+=1){let r=e[o]==="\\"&&o+1<e.length;!r&&G(e[o])||(n<0&&(n=o),o+=r?1:0,t=o+1)}return n<0?"":e.slice(n,t)},oe=(e,n)=>{let t=[],o=0;for(let r=0;r<e.length;r+=1)e[r]==="\\"?r+=1:e[r]===n&&(t.push(e.slice(o,r)),o=r+1);return[...t,e.slice(o)]},Ee=e=>{if(!e||typeof e!="object")return!1;try{let n=Object.getPrototypeOf(e);return n===Object.prototype||n===null}catch{return!1}},We=e=>{let n=I;try{let t=JSON.stringify(e,(o,r)=>{if(n-=1,n<0)throw new RangeError("The value visits more nodes than a resolvable output can hold.");return r});return typeof t=="string"?t:void 0}catch{return}},Me=e=>{try{if(!Ee(e)&&!Array.isArray(e))return String(e)}catch{return}return We(e)},B=(e,n)=>e===void 0?void 0:e!==null&&(typeof e=="object"||typeof e=="function"||typeof e=="bigint")?(n.has(e)||n.set(e,Me(e)),n.get(e)):Me(e),ie=(e,n,t)=>{let o=B(e,t);return e!==void 0&&o===void 0&&n(),o},Ue=["value","default","props"],qe=(e,n,t)=>{if(!Ee(e))return!1;if(!n.has(e)){let r=!1,i=k(e,()=>{r=!0});n.set(e,r?void 0:!!i.length&&i.every(s=>Ue.includes(s)))}let o=n.get(e);return o===void 0&&t?.(),!!o},we=e=>!!e&&(typeof e=="object"||typeof e=="function"),Xe=(e,n,t)=>e.reduce((o,r)=>{let i=we(r)?d(r,n,t):void 0;return we(i)?M(o,i,t):o},Object.create(null)),Be=["eq","ne","lt","gt","lte","gte"],He=(e,n)=>Be.includes(e)&&n[e]===U[e],Je=({value:e,props:n,payload:t,parserOptions:o,modifiers:r,modifierDefaults:i,onReport:s,locale:a,key:c,conversions:w,wrappers:R})=>{let f=Object.keys(r),E=m=>{let[A,...S]=oe(m.slice(2,-2),";"),[j,...C]=oe(A,":"),de=L(j),_=de?q(de):void 0,y=()=>h("unserializable-value",m,c,s),H=d(t,_,y),F=_!=="default"&&qe(H,R,y)?H:void 0,Te=F?d(F,"value",y):H,V=[],J;S.forEach(g=>{let[b,...ce]=oe(g,":"),z=q(L(b)),fe=ce.length?L(ce.join(":")):L(b);z&&(J===void 0&&z==="default"&&(J=fe),z!=="default"&&V.push({key:z,value:fe}))});let Y=g=>ie(g,y,w),$=Y(Te),ue,Pe=_==="default"?()=>$:()=>Y(d(t,"default",y)),O=()=>(ue??=[()=>Y(d(F,"default",y)),Pe].reduce((g,b)=>g??b(),void 0)??J??"",ue),v=q(L(C.join(":"))),Q=!!v;if(Q&&!f.includes(v))return h("unknown-modifier",m,c,s),O();if(_!==void 0&&!V.length&&He(v,r)&&h("missing-options",m,c,s),$===void 0)return O();if(!Q&&!V.length)return $;let ae=Q?v:"eq",Ie=r[ae];try{let g=Xe([i,n,d(F,"props",y)],ae,y);return ie(Ie({value:$,options:V,props:g,get defaultValue(){return O()},locale:a,parserOptions:o}),y,w)??O()}catch(g){let b=pe(g)??"failed-modifier";return h(b,m,c,s),b==="missing-locale"?"":O()}},u=`${e}`,x=[],T=0,p=0;for(let m=re(u,p);m;m=re(u,p)){let[A,S]=m;if(A+T>I)return;let j=u.slice(A,S),C=E(j);if(x.push(u.slice(p,A),C),T+=C.length-j.length,p=S,p+T>I)return}return u.length+T>I?void 0:[...x,u.slice(p)].join("")},se=10,xe=ee(U),I=1e5,X=120,Ye=e=>e.slice(0,(e.codePointAt(X-1)??0)>65535?X-1:X),Qe=e=>JSON.stringify(e.length>X?`${Ye(e)}...`:e).slice(1,-1).replace(ve,Z),Ze={"unknown-modifier":"A placeholder named a modifier this parser does not know.","failed-modifier":"A modifier could not produce a result, so the placeholder took its fallback chain.","missing-options":"A comparison was given no options to select from, so the placeholder took its fallback chain.","unserializable-value":"A value could not become text, so resolution read it as missing.","missing-locale":"A formatting modifier was given no locale, so the placeholder resolved to the empty string.","pass-limit":`Interpolation stopped after ${se} passes. A payload value probably references its own placeholder.`,"output-limit":`Interpolation stopped before exceeding ${I} characters. A payload value probably multiplies its own placeholder.`},et={"unknown-modifier":void 0,"failed-modifier":void 0,"missing-options":void 0,"unserializable-value":void 0,"missing-locale":void 0,"pass-limit":se,"output-limit":I},tt={"unknown-modifier":"message","failed-modifier":"payload","missing-options":"message","unserializable-value":"payload","missing-locale":"payload","pass-limit":"limit","output-limit":"limit"},h=(e,n,t,o)=>{if(o)try{o({code:e,origin:tt[e],message:Ze[e],key:t,limit:et[e],text:Qe(n)})}catch{}},nt=({value:e,props:n,payload:t,parserOptions:o,modifiers:r,modifierDefaults:i,onReport:s,locale:a,key:c,conversions:w,wrappers:R})=>{let f=e;for(let E=0;Ge(f);E+=1){if(E===se){h("pass-limit",f,c,s);break}let u=Je({value:f,payload:t,props:n,parserOptions:o,modifiers:r,modifierDefaults:i,onReport:s,locale:a,key:c,conversions:w,wrappers:R});if(u===void 0){h("output-limit",f,c,s);break}f=u}return B(q(f),w)??""},dt=e=>({resolve:(n,t)=>{let o=d(e,"onReport"),r=typeof n=="string"?n:"",i=d(t,"key",()=>h("unserializable-value",r,void 0,o)),s=()=>h("unserializable-value",r,i,o),a=d(t,"payload",s),c=d(t,"props",s),w=d(t,"locale",s),R=d(e,"customModifiers",s),f=d(e,"modifierDefaults",s),E=R===void 0?xe:M(xe,ee(R,s)),u=()=>h("unserializable-value","",i,o),x=new Map,T=new Map,p=B(n,x)??ie(d(a,"default",u),u,x);return p===void 0?B(i,x)??"":nt({value:p,payload:a,props:c,parserOptions:e,modifiers:E,modifierDefaults:f,onReport:o,locale:w,key:i,conversions:x,wrappers:T})}});export{dt as createParser};
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@curly-message/parser",
3
+ "version": "1.0.0-next.0",
4
+ "description": "JavaScript implementation of the Curly Message Format.",
5
+ "type": "module",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./package.json": "./package.json"
13
+ },
14
+ "engines": {
15
+ "node": ">=22"
16
+ },
17
+ "scripts": {
18
+ "dev": "tsup --watch",
19
+ "typecheck": "tsc --noEmit -p tsconfig.json",
20
+ "pretest": "npm run build && npm run typecheck && npm run lint",
21
+ "test": "vitest run",
22
+ "build": "tsup",
23
+ "prepack": "npm run build",
24
+ "lint": "eslint . --max-warnings=0",
25
+ "lint:fix": "eslint --fix .",
26
+ "prepare": "cd .. && simple-git-hooks js/.simple-git-hooks.json"
27
+ },
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+ssh://git@github.com/curly-message/parsers.git",
34
+ "directory": "js"
35
+ },
36
+ "keywords": [
37
+ "curly-message",
38
+ "parser",
39
+ "i18n",
40
+ "internationalization",
41
+ "translations",
42
+ "interpolation"
43
+ ],
44
+ "author": "Jarda Svoboda",
45
+ "license": "MIT",
46
+ "bugs": {
47
+ "url": "https://github.com/curly-message/parsers/issues"
48
+ },
49
+ "homepage": "https://github.com/curly-message/parsers/tree/main/js#readme",
50
+ "devDependencies": {
51
+ "@eslint/js": "^10.0.1",
52
+ "@stylistic/eslint-plugin": "^5.10.0",
53
+ "eslint": "^10.8.1",
54
+ "eslint-plugin-import-x": "^4.17.1",
55
+ "globals": "^17.11.0",
56
+ "simple-git-hooks": "^2.13.1",
57
+ "tsup": "^8.0.1",
58
+ "typescript": "^5.1.6",
59
+ "typescript-eslint": "^8.67.0",
60
+ "vitest": "^4.1.10"
61
+ }
62
+ }