@sveltekit-i18n/parser-curly 3.0.0-next.3 → 3.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 +46 -18
- package/dist/index.d.ts +16 -6
- package/dist/index.js +1 -1
- package/package.json +13 -7
package/README.md
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
[](https://badge.fury.io/js/@sveltekit-i18n%2Fparser-curly) [](https://github.com/sveltekit-i18n/parsers/actions/workflows/tests-parser-curly.yml)
|
|
2
|
-
[](https://app.netlify.com/sites/parser-default/deploys)
|
|
3
2
|
|
|
4
3
|
# @sveltekit-i18n/parser-curly
|
|
5
4
|
|
|
6
|
-
The [Curly Message Format](https://
|
|
7
|
-
|
|
8
|
-
**[Live Demo](https://parser-default.netlify.app)** – See it in action
|
|
5
|
+
The [Curly Message Format](https://curlymessage.dev) for [@sveltekit-i18n/base](https://github.com/sveltekit-i18n/base): placeholders, defaults, modifiers and comparisons written in double curly braces. Every message is resolved by [`@curly-message/parser`](https://github.com/curly-message/parsers), the format's reference implementation and this package's only dependency; the package itself unpacks the base library's calling convention and requires the diagnostics channel to be stated. This README is a practical guide to the syntax — the full grammar and the resolution rules are in the specification.
|
|
9
6
|
|
|
10
7
|
## Installation
|
|
11
8
|
|
|
12
9
|
```bash
|
|
13
10
|
npm install @sveltekit-i18n/parser-curly
|
|
11
|
+
# bun add @sveltekit-i18n/parser-curly
|
|
12
|
+
# deno add npm:@sveltekit-i18n/parser-curly
|
|
14
13
|
```
|
|
15
14
|
|
|
16
15
|
This parser is included by default in [sveltekit-i18n](https://github.com/sveltekit-i18n/lib).
|
|
17
16
|
|
|
18
|
-
**Requirements:** Node.js 22 or newer. Version 3 is ESM-only and expects [`@sveltekit-i18n/base`](https://github.com/sveltekit-i18n/base) v3 as a peer dependency.
|
|
17
|
+
**Requirements:** Node.js 22, Bun 1.2 or Deno 2, or newer. Version 3 is ESM-only and expects [`@sveltekit-i18n/base`](https://github.com/sveltekit-i18n/base) v3 as a peer dependency.
|
|
19
18
|
|
|
20
19
|
## Usage
|
|
21
20
|
|
|
@@ -211,7 +210,7 @@ i18n.t('notification', { count: 0 })
|
|
|
211
210
|
// → "You have no messages."
|
|
212
211
|
```
|
|
213
212
|
|
|
214
|
-
|
|
213
|
+
An option value is the one position where a placeholder holds another, and only the selected option's is resolved: over `{ count: 0 }` the comparison selects nothing, so no payload entry is read for the inner placeholders and no report they would have made is made. What a placeholder resolves to is nested in nothing — a value is data, so no payload reaches a branch the message did not select for it. How deep a message writes placeholders inside one another is what the [limits](#limits) bound.
|
|
215
214
|
|
|
216
215
|
### Escaping
|
|
217
216
|
|
|
@@ -227,7 +226,7 @@ A backslash cancels the structural meaning of the character after it: `:`, `;`,
|
|
|
227
226
|
| `\d+` | `\d+` |
|
|
228
227
|
| `\{{v}}` | `{{v}}`, text whatever the payload carries |
|
|
229
228
|
|
|
230
|
-
Escape sequences are removed
|
|
229
|
+
Escape sequences are removed from the message and from nothing else: a payload value is data, so its backslashes and its braces reach the output as they stand and a value holding `\d+` renders `\d+`. A placeholder is written on one line — `{{` and `}}` with a line terminator between them are text — and `{{}}` is a placeholder naming no key, which resolves to the fallback.
|
|
231
230
|
|
|
232
231
|
## Payload
|
|
233
232
|
|
|
@@ -243,6 +242,8 @@ i18n.t('greeting', { name: { default: 'stranger' } })
|
|
|
243
242
|
|
|
244
243
|
An entry owning any other key is data, wrapper-shaped or not: `{ value: 1, unit: 'kg' }` becomes JSON. Every entry is read as an own enumerable property — nothing on a prototype resolves.
|
|
245
244
|
|
|
245
|
+
A value is data throughout: no escape sequence is removed from it and no placeholder is found in it, so it reaches the output as it was passed. Where the payload carries data the caller did not write, pass `recognizeWrappers: false` — an entry of that shape would otherwise reconfigure every modifier its placeholder reaches without spelling any syntax at all.
|
|
246
|
+
|
|
246
247
|
## Options
|
|
247
248
|
|
|
248
249
|
```javascript
|
|
@@ -263,6 +264,12 @@ const config = {
|
|
|
263
264
|
// Where diagnostics go. Required: a function, or `null` to state that
|
|
264
265
|
// reports go nowhere.
|
|
265
266
|
onReport: (report) => { /* ... */ },
|
|
267
|
+
// Whether a wrapper-shaped payload entry configures its value. On unless
|
|
268
|
+
// stated; `false` where the payload carries data the caller did not write.
|
|
269
|
+
recognizeWrappers: true,
|
|
270
|
+
// A migration aid: where a value holding what version 1 of the format read
|
|
271
|
+
// as syntax is announced. Nothing is looked for while this is unset.
|
|
272
|
+
onSuspectValue: null,
|
|
266
273
|
}),
|
|
267
274
|
};
|
|
268
275
|
```
|
|
@@ -333,18 +340,20 @@ A `Report` carries:
|
|
|
333
340
|
|
|
334
341
|
| Field | Meaning |
|
|
335
342
|
| --- | --- |
|
|
336
|
-
| `code` | `unknown-modifier`, `failed-modifier`, `missing-options`, `unserializable-value`, `missing-locale`, `
|
|
343
|
+
| `code` | `unknown-modifier`, `failed-modifier`, `missing-options`, `unserializable-value`, `missing-locale`, `output-limit`, `read-limit` or `nesting-limit` |
|
|
337
344
|
| `origin` | who fixes it: `message` (the message as written), `payload` (what the call passed) or `limit` (a bound this parser set) |
|
|
338
345
|
| `message` | a self-contained English sentence carrying nothing from the payload |
|
|
339
346
|
| `id` | the message's id, where the call passed one; base passes the translation key |
|
|
340
|
-
| `limit` | the limit reached, for the
|
|
341
|
-
| `text` | the excerpt: the placeholder, or the
|
|
347
|
+
| `limit` | the limit reached, for the three limit reports |
|
|
348
|
+
| `text` | the excerpt: the placeholder that named the trouble, or the message as it was passed where the read that refused is of the call's own structure — message text throughout and never a payload value, cut to 120 code units, with quotes, backslashes and line terminators escaped, so it can be written anywhere |
|
|
342
349
|
|
|
343
350
|
A report never raises: the placeholder takes its fallback (the empty string for `missing-locale`) and the rest of the message resolves.
|
|
344
351
|
|
|
345
352
|
## Limits
|
|
346
353
|
|
|
347
|
-
|
|
354
|
+
Three budgets bound a resolution. **Output** is what the output carries: 100 000 UTF-16 code units, and a placeholder whose result would carry it past that resolves to the empty string and reports `output-limit`, leaving the next placeholder to resolve. **Read** is what the payload is read for: 100 000 code units as well, spent by every character a placeholder takes from the payload whether or not any of it reaches the output, reported as `read-limit`. **Nesting** is how deep a message writes placeholders inside one another: the outermost is level 1, and one deeper than 8 levels is not resolved at all, taking its fallback chain and reporting `nesting-limit`. A fourth bound holds the conversion that feeds them — 100 000 nodes per value, past which the value is read as missing and reported as `unserializable-value`. A message's own text is the caller's and always reaches the output; what these bound is what the payload and the nesting add to it.
|
|
355
|
+
|
|
356
|
+
The specification's conformance set, `@curly-message/conformance`, runs against this package's public API in its tests, at every level the format defines (Core, Intl, Extensions), and the tree cases against `cst`.
|
|
348
357
|
|
|
349
358
|
## TypeScript
|
|
350
359
|
|
|
@@ -376,7 +385,7 @@ i18n.t('common.welcome', { aplicationName: 'My app' })
|
|
|
376
385
|
// → type error: typo caught
|
|
377
386
|
```
|
|
378
387
|
|
|
379
|
-
`Config<Payload, Props>` types the payload and the props `i18n.t` accepts; left bare, `Config` accepts any payload key and the built-in modifiers' props. A custom modifier types its own props through `Modifier.T<OwnProps>`. The factory's type arguments check the parser options the same way: with `Props` spelled as the second, `parser<Payload, Props>({ ... })`, a `modifierDefaults` entry for a custom modifier is checked; a modifier written inline reads typed `props` once the modifier names are spelled as the third argument too, `parser<Payload, Props, 'truncate'>({ ... })`. `Parser` holds the option and parameter types (`Parser.Options`, `Parser.OnReport`, `Parser.Params`, `Parser.Payload`), `Modifier` the modifier and wrapper types (`Modifier.T`, `Modifier.Wrapper`, `Modifier.Props`), and `Report` is the report.
|
|
388
|
+
`Config<Payload, Props>` types the payload and the props `i18n.t` accepts; left bare, `Config` accepts any payload key and the built-in modifiers' props. A custom modifier types its own props through `Modifier.T<OwnProps>`. The factory's type arguments check the parser options the same way: with `Props` spelled as the second, `parser<Payload, Props>({ ... })`, a `modifierDefaults` entry for a custom modifier is checked; a modifier written inline reads typed `props` once the modifier names are spelled as the third argument too, `parser<Payload, Props, 'truncate'>({ ... })`. `Parser` holds the option and parameter types (`Parser.Options`, `Parser.OnReport`, `Parser.Params`, `Parser.Payload`), `Modifier` the modifier and wrapper types (`Modifier.T`, `Modifier.Wrapper`, `Modifier.Props`), `Cst` the [tree](#describing-a-message) node types, and `Report` is the report.
|
|
380
389
|
|
|
381
390
|
## Extracting Parameters
|
|
382
391
|
|
|
@@ -408,15 +417,28 @@ A parameter several placeholders name accepts what all of them say together, and
|
|
|
408
417
|
|
|
409
418
|
`optional` reports what the message says rather than what resolution tolerates. Every placeholder renders without its value, an absent one taking the fallback chain, so a placeholder declaring an inline `default` is the message saying the value may be missing, and one declaring none is the message saying it is expected.
|
|
410
419
|
|
|
411
|
-
Build the extractor from the same options `parser()` is built from: a custom modifier registered under a name the format defines changes what a message naming it says about its value. `onReport` is not required here, and neither it nor `modifierDefaults` reaches anything — extraction formats nothing and
|
|
420
|
+
Build the extractor from the same options `parser()` is built from: a custom modifier registered under a name the format defines changes what a message naming it says about its value. `onReport` is not required here, and neither it nor `modifierDefaults`, `recognizeWrappers` or `onSuspectValue` reaches anything — extraction formats nothing, reports nothing and reads no payload.
|
|
412
421
|
|
|
413
|
-
Only the text of a message is scanned. A translation leaf that is not text names no parameters rather than throwing, and a placeholder a payload value carries
|
|
422
|
+
Only the text of a message is scanned. A translation leaf that is not text names no parameters rather than throwing, and a placeholder a payload value carries is not one the message itself names — a value is data, so nothing reads it as source. A placeholder the message writes inside another is named beside the one holding it, in the order the message writes them.
|
|
414
423
|
|
|
415
|
-
##
|
|
424
|
+
## Describing a Message
|
|
416
425
|
|
|
417
|
-
|
|
426
|
+
`cst` describes a message as the parts it is written from: where its placeholders are, what each is made of, and where every escape sequence falls. It is the format's own describer, a named export for the same reason `extractParamsFactory` is one — resolution never calls it, so a bundle that never reaches it drops it.
|
|
418
427
|
|
|
419
|
-
|
|
428
|
+
```typescript
|
|
429
|
+
import { cst } from '@sveltekit-i18n/parser-curly';
|
|
430
|
+
|
|
431
|
+
cst('Hello, {{name; default:Guest;}}!');
|
|
432
|
+
// → { type: 'message', start: 0, end: 32, nodes: [
|
|
433
|
+
// { type: 'text', start: 0, end: 7 },
|
|
434
|
+
// { type: 'placeholder', start: 7, end: 31, nodes: [/* ... */] },
|
|
435
|
+
// { type: 'text', start: 31, end: 32 },
|
|
436
|
+
// ] }
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
The tree is concrete: every node carries its `[start, end)` span in UTF-16 code units, the leaves come in the order the message writes them, and concatenating them spells the message back — which is what an editor, a linter or a syntax highlighter needs. The placeholders are the ones resolution finds, because the same scan finds them: one written in an option value is described there, and a construct the grammar derives nothing from is text. A name — a key, a modifier name, an option key — carries `name`, the span unescaped, beside `nodes`, how the message spells it; an escape carries `cancels`, which of its two readings the backslash takes.
|
|
440
|
+
|
|
441
|
+
It reads no options: a name is a name whether or not a modifier answers to it, so nothing a host registers changes the text. The node types are the `Cst` namespace (`Cst.Message`, `Cst.Placeholder`, `Cst.Node`, ...).
|
|
420
442
|
|
|
421
443
|
## Comparison with Other Parsers
|
|
422
444
|
|
|
@@ -445,8 +467,9 @@ Choose `parser-curly` for simplicity, ICU for standards compliance.
|
|
|
445
467
|
|
|
446
468
|
## More Resources
|
|
447
469
|
|
|
470
|
+
- [sveltekit-i18n.github.io](https://sveltekit-i18n.github.io) – The documentation site, with a live playground
|
|
448
471
|
- [All Parsers](https://github.com/sveltekit-i18n/parsers) – Parser overview
|
|
449
|
-
- [Curly Message Format](https://
|
|
472
|
+
- [Curly Message Format](https://curlymessage.dev) – The specification
|
|
450
473
|
- [Examples](https://github.com/sveltekit-i18n/lib/tree/master/examples) – Working examples
|
|
451
474
|
- [Changelog](./CHANGELOG.md) – Version history
|
|
452
475
|
|
|
@@ -454,6 +477,11 @@ Choose `parser-curly` for simplicity, ICU for standards compliance.
|
|
|
454
477
|
|
|
455
478
|
If you're facing issues with this parser, create a ticket [here](https://github.com/sveltekit-i18n/lib/issues).
|
|
456
479
|
|
|
480
|
+
## Sponsor
|
|
481
|
+
|
|
482
|
+
You can support the maintenance of this package through
|
|
483
|
+
[GitHub Sponsors](https://github.com/sponsors/sveltekit-i18n).
|
|
484
|
+
|
|
457
485
|
## License
|
|
458
486
|
|
|
459
487
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import { Parser as Parser$2, Config as Config$1 } from '@sveltekit-i18n/base';
|
|
2
2
|
import { Modifier, Parser as Parser$1 } from '@curly-message/parser';
|
|
3
|
-
export { Modifier, Report } from '@curly-message/parser';
|
|
3
|
+
export { Cst, Modifier, Report, cst } from '@curly-message/parser';
|
|
4
4
|
|
|
5
5
|
declare namespace Parser {
|
|
6
6
|
/**
|
|
7
7
|
* The options `parser()` takes, handed on to `@curly-message/parser`:
|
|
8
|
-
* `customModifiers`, `modifierDefaults
|
|
9
|
-
* required, `null` included: this package
|
|
10
|
-
* so where a report goes is stated by
|
|
8
|
+
* `customModifiers`, `modifierDefaults`, `onReport`, `recognizeWrappers` and
|
|
9
|
+
* `onSuspectValue`. `onReport` is required, `null` included: this package
|
|
10
|
+
* writes to no channel of its own, so where a report goes is stated by
|
|
11
|
+
* whoever builds the parser.
|
|
11
12
|
*/
|
|
12
13
|
type Options<Key extends string = Modifier.Key, Props = Modifier.DefaultProps> = Omit<Parser$1.Options<Key, Props>, 'onReport'> & {
|
|
13
14
|
onReport: OnReport | null | undefined;
|
|
14
15
|
};
|
|
15
16
|
type OnReport = Parser$1.OnReport;
|
|
17
|
+
/**
|
|
18
|
+
* A value a placeholder read that version 1 of the format would have read as
|
|
19
|
+
* syntax. It is not a report — the placeholder resolved to exactly the text
|
|
20
|
+
* the payload holds — but a migration aid for a catalogue that composed
|
|
21
|
+
* messages through its payload.
|
|
22
|
+
*/
|
|
23
|
+
type Suspect = Parser$1.Suspect;
|
|
24
|
+
type OnSuspectValue = Parser$1.OnSuspectValue;
|
|
16
25
|
type PayloadDefault = Parser$1.PayloadDefault;
|
|
17
26
|
type Payload<T = any, Props = Modifier.DefaultProps> = Parser$1.Payload<T, Props>;
|
|
18
27
|
/**
|
|
@@ -28,8 +37,9 @@ declare namespace Parser {
|
|
|
28
37
|
* so an extractor is built the way the app builds its parser — a custom
|
|
29
38
|
* modifier registered under a name the format defines changes what a message
|
|
30
39
|
* naming it says about its value. `onReport` is not required here, and
|
|
31
|
-
* neither it nor `modifierDefaults`
|
|
32
|
-
* nothing and
|
|
40
|
+
* neither it nor `modifierDefaults`, `recognizeWrappers` or `onSuspectValue`
|
|
41
|
+
* reaches anything: extraction formats nothing, reports nothing and reads no
|
|
42
|
+
* payload.
|
|
33
43
|
*/
|
|
34
44
|
type ExtractOptions<Key extends string = Modifier.Key, Props = Modifier.DefaultProps> = Parser$1.Options<Key, Props>;
|
|
35
45
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createParser as f}from"@curly-message/parser";import{createExtractor as
|
|
1
|
+
import{createParser as f}from"@curly-message/parser";import{createExtractor as c}from"@curly-message/parser";var i=c;import{cst as M}from"@curly-message/parser";var m=r=>{let{resolve:t}=f(r);return{parse:(e,[o,a],p,s)=>t(e,{payload:o,props:a,locale:p,id:s})}},n=m;export{M as cst,n as default,i as extractParamsFactory};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sveltekit-i18n/parser-curly",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Curly Message Format parser for the sveltekit-i18n library.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
"./package.json": "./package.json"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
16
|
-
"node": ">=22"
|
|
16
|
+
"node": ">=22",
|
|
17
|
+
"bun": ">=1.2",
|
|
18
|
+
"deno": ">=2"
|
|
17
19
|
},
|
|
18
20
|
"scripts": {
|
|
19
21
|
"dev": "tsup --watch",
|
|
@@ -45,18 +47,19 @@
|
|
|
45
47
|
"bugs": {
|
|
46
48
|
"url": "https://github.com/sveltekit-i18n/lib/issues"
|
|
47
49
|
},
|
|
48
|
-
"homepage": "https://
|
|
50
|
+
"homepage": "https://sveltekit-i18n.github.io",
|
|
51
|
+
"funding": "https://github.com/sponsors/sveltekit-i18n",
|
|
49
52
|
"peerDependencies": {
|
|
50
|
-
"@sveltekit-i18n/base": "^3.0.0
|
|
53
|
+
"@sveltekit-i18n/base": "^3.0.0"
|
|
51
54
|
},
|
|
52
55
|
"dependencies": {
|
|
53
|
-
"@curly-message/parser": "^
|
|
56
|
+
"@curly-message/parser": "^3.0.0"
|
|
54
57
|
},
|
|
55
58
|
"devDependencies": {
|
|
56
|
-
"@curly-message/conformance": "^
|
|
59
|
+
"@curly-message/conformance": "^4.0.0",
|
|
57
60
|
"@eslint/js": "^10.0.1",
|
|
58
61
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
59
|
-
"@sveltekit-i18n/base": "^3.0.0
|
|
62
|
+
"@sveltekit-i18n/base": "^3.0.0",
|
|
60
63
|
"eslint": "^10.8.1",
|
|
61
64
|
"eslint-plugin-import-x": "^4.17.1",
|
|
62
65
|
"globals": "^17.11.0",
|
|
@@ -65,5 +68,8 @@
|
|
|
65
68
|
"typescript": "^5.1.6",
|
|
66
69
|
"typescript-eslint": "^8.67.0",
|
|
67
70
|
"vitest": "^4.1.10"
|
|
71
|
+
},
|
|
72
|
+
"overrides": {
|
|
73
|
+
"esbuild": "^0.28.1"
|
|
68
74
|
}
|
|
69
75
|
}
|