@sveltekit-i18n/parser-curly 3.1.2 → 3.2.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 +35 -11
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @sveltekit-i18n/parser-curly
|
|
4
4
|
|
|
5
|
-
The [Curly Message Format](https://curlymessage.dev) for [@sveltekit-i18n/base](https://github.com/sveltekit-i18n/base): placeholders, defaults, modifiers and
|
|
5
|
+
The [Curly Message Format](https://curlymessage.dev) for [@sveltekit-i18n/base](https://github.com/sveltekit-i18n/base): placeholders, defaults, modifiers, comparisons and plural selection 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.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -163,7 +163,7 @@ i18n.t('cost', { amount: 1999 }, { currency: { currency: 'USD', ratio: 0.01 } })
|
|
|
163
163
|
// → "Cost: $19.99"
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
-
A value the modifier cannot read — text that is not a number, an empty string, a `Date` object under `number` — takes the fallback and is reported as `failed-modifier`; so does a `currency` placeholder with no currency code or an `ago` whose `format` names no unit. Nothing raises. A `Date` object does work under `date`, to the second, because it reaches the modifier as its `toString` text; pass a timestamp or an ISO string where a placeholder wants one. With no locale — none passed, or the empty string — a formatting modifier resolves to the empty string rather than to the fallback and reports `missing-locale`.
|
|
166
|
+
A value the modifier cannot read — text that is not a number, an empty string, a `Date` object under `number` — takes the fallback and is reported as `failed-modifier`; so does a `currency` placeholder with no currency code or an `ago` whose `format` names no unit. Nothing raises. A `Date` object does work under `date`, to the second, because it reaches the modifier as its `toString` text; pass a timestamp or an ISO string where a placeholder wants one. With no locale — none passed, or the empty string — a formatting modifier or a [plural selection](#plural-selection) resolves to the empty string rather than to the fallback and reports `missing-locale`.
|
|
167
167
|
|
|
168
168
|
### Comparisons
|
|
169
169
|
|
|
@@ -172,7 +172,6 @@ A value the modifier cannot read — text that is not a number, an empty string,
|
|
|
172
172
|
```json
|
|
173
173
|
{
|
|
174
174
|
"status": "{{state; active:Online; inactive:Offline; default:Unknown;}}",
|
|
175
|
-
"items": "You have {{count}} {{count; 1:item; default:items;}}.",
|
|
176
175
|
"stock": "{{count:gt; 0:In stock ({{count}}); default:Out of stock;}}",
|
|
177
176
|
"age": "{{age:gte; 18:Adult; default:Minor;}}",
|
|
178
177
|
"temp": "{{degrees:lt; 0:Freezing; default:Above freezing;}}",
|
|
@@ -183,8 +182,6 @@ A value the modifier cannot read — text that is not a number, an empty string,
|
|
|
183
182
|
```javascript
|
|
184
183
|
i18n.t('status', { state: 'active' }) // → "Online"
|
|
185
184
|
i18n.t('status', { state: 'pending' }) // → "Unknown"
|
|
186
|
-
i18n.t('items', { count: 1 }) // → "You have 1 item."
|
|
187
|
-
i18n.t('items', { count: 5 }) // → "You have 5 items."
|
|
188
185
|
i18n.t('stock', { count: 5 }) // → "In stock (5)"
|
|
189
186
|
i18n.t('stock', { count: 0 }) // → "Out of stock"
|
|
190
187
|
i18n.t('age', { age: 25 }) // → "Adult"
|
|
@@ -193,7 +190,32 @@ i18n.t('health', { state: 'error' }) // → "Problem"
|
|
|
193
190
|
i18n.t('health', {}) // → "Fine"
|
|
194
191
|
```
|
|
195
192
|
|
|
196
|
-
An absent value never reaches a comparison — it takes the fallback under every modifier, `ne` included. An option written as `key` alone stands for its own key; `key:` declares the empty string. An option value runs to the next unescaped semicolon, so `link:http://example.com` keeps its colons. A comparison with no options (`{{v:eq; default:D}}`) takes the fallback and reports `missing-options`; a placeholder naming a modifier nobody registered takes the fallback and reports `unknown-modifier` — it is never run as `eq`.
|
|
193
|
+
An absent value never reaches a comparison — it takes the fallback under every modifier, `ne` included. An option written as `key` alone stands for its own key; `key:` declares the empty string. An option value runs to the next unescaped semicolon, so `link:http://example.com` keeps its colons. A comparison or a plural selection with no options (`{{v:eq; default:D}}`) takes the fallback and reports `missing-options`; a placeholder naming a modifier nobody registered takes the fallback and reports `unknown-modifier` — it is never run as `eq`.
|
|
194
|
+
|
|
195
|
+
### Plural Selection
|
|
196
|
+
|
|
197
|
+
`plural` and `ordinal` select an option by the category the locale's plural rules put a number in — `Intl.PluralRules`, cardinal and ordinal. The categories are CLDR's `zero`, `one`, `two`, `few`, `many` and `other`, and each locale uses its own subset: in Russian 1, 21 and 101 are `one`, which a comparison cannot express.
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"items": "You have {{count}} {{count:plural; one:item; other:items;}}.",
|
|
202
|
+
"inbox": "{{count:plural; 0:No messages; one:{{count}} message; other:{{count}} messages;}}",
|
|
203
|
+
"files": "{{count}} {{count:plural; one:файл; few:файла; many:файлов; other:файла;}}",
|
|
204
|
+
"place": "{{n}}{{n:ordinal; one:st; two:nd; few:rd; other:th;}}"
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
i18n.t('items', { count: 1 }) // → "You have 1 item."
|
|
210
|
+
i18n.t('items', { count: 5 }) // → "You have 5 items."
|
|
211
|
+
i18n.t('inbox', { count: 0 }) // → "No messages"
|
|
212
|
+
i18n.t('inbox', { count: 21 }) // → "21 messages"
|
|
213
|
+
i18n.t('files', { count: 21 }) // → "21 файл" (in ru)
|
|
214
|
+
i18n.t('files', { count: 5 }) // → "5 файлов" (in ru)
|
|
215
|
+
i18n.t('place', { n: 22 }) // → "22nd"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
A numeric key matches the value exactly and wins over a category, as `0:` does above. A category the placeholder writes no option for takes the fallback — no option catches it as ICU's `other` does — so a message writes every category its locale uses. How `default` and the rest of the fallback chain answer, which props each modifier reads, and why `plural` follows the digits `number` shows are set out in [the reference implementation's README](https://github.com/curly-message/parsers/tree/js-v3.1.0/js#plural-selection).
|
|
197
219
|
|
|
198
220
|
### Nested Placeholders
|
|
199
221
|
|
|
@@ -416,10 +438,11 @@ Each parameter is reported once, in the order the message first names it, and sa
|
|
|
416
438
|
| `number`, `currency` | `'number'` |
|
|
417
439
|
| `ago` | `'number'` — a signed millisecond delta relative to now, not a point in time |
|
|
418
440
|
| `date` | `['date', 'string']` — milliseconds since the epoch, and failing that text the host reads as a date |
|
|
441
|
+
| `plural`, `ordinal` | `'number'` — `ordinal` takes an integer, which the kinds have no word for |
|
|
419
442
|
|
|
420
443
|
A parameter several placeholders name accepts what all of them say together, and `unknown` is the top of that lattice rather than a member of it: it is what a parameter accepts while nothing has narrowed it, and it drops out the moment something does. So `{{count}}` alone reports `unknown`, and the example above — where a second placeholder formats the same key with `number` — reports `'number'` rather than `['unknown', 'number']`.
|
|
421
444
|
|
|
422
|
-
`values` lists the option keys of an `eq` selection, which is the one comparison whose keys are values of the parameter: `ne`'s are what the value must differ from, and an inequality's are thresholds it is ordered against. It is a hint and never a closed set — a value none of them matches takes the fallback chain rather than failing.
|
|
445
|
+
`values` lists the option keys of an `eq` selection, which is the one comparison whose keys are values of the parameter: `ne`'s are what the value must differ from, and an inequality's are thresholds it is ordered against. A plural selection lists its keys that are numbers — of `ordinal`'s, the integers — which are values the parameter takes; its categories (`one`, `few`, …) are not. It is a hint and never a closed set — a value none of them matches takes the fallback chain rather than failing.
|
|
423
446
|
|
|
424
447
|
`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.
|
|
425
448
|
|
|
@@ -427,7 +450,7 @@ Build the extractor from the same options `parser()` is built from: a custom mod
|
|
|
427
450
|
|
|
428
451
|
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.
|
|
429
452
|
|
|
430
|
-
This is what types `t` and `l` by key and payload: [`@sveltekit-i18n/typegen`](https://github.com/sveltekit-i18n/typegen), a Vite plugin, runs the config the module it is pointed at exports as `config`, reads every message through the extractor and writes the schema, which it registers for every config that states no [`schema`](https://github.com/sveltekit-i18n/base/blob/master/docs/README.md#schema) of its own, so on base 3.1 there is nothing to wire ([an older core](https://github.com/sveltekit-i18n/typegen#3-nothing-to-wire-
|
|
453
|
+
This is what types `t` and `l` by key and payload: [`@sveltekit-i18n/typegen`](https://github.com/sveltekit-i18n/typegen), a Vite plugin, runs the config the module it is pointed at exports as `config`, reads every message through the extractor and writes the schema, which it registers for every config that states no [`schema`](https://github.com/sveltekit-i18n/base/blob/master/docs/README.md#schema) of its own, so on base 3.1 there is nothing to wire ([an older core](https://github.com/sveltekit-i18n/typegen#3-nothing-to-wire-on-a-31-core) takes a cast). `sveltekit-i18n` re-exports this extractor, so its users name that package; a base app names this one:
|
|
431
454
|
|
|
432
455
|
```javascript
|
|
433
456
|
// vite.config.js, with sveltekit-i18n
|
|
@@ -465,7 +488,7 @@ It reads no options: a name is a name whether or not a modifier answers to it, s
|
|
|
465
488
|
**parser-curly:**
|
|
466
489
|
```json
|
|
467
490
|
{
|
|
468
|
-
"items": "You have {{count}} {{count;
|
|
491
|
+
"items": "You have {{count}} {{count:plural; one:item; other:items;}}."
|
|
469
492
|
}
|
|
470
493
|
```
|
|
471
494
|
|
|
@@ -477,7 +500,8 @@ It reads no options: a name is a name whether or not a modifier answers to it, s
|
|
|
477
500
|
```
|
|
478
501
|
|
|
479
502
|
- `parser-curly` has simpler syntax
|
|
480
|
-
- ICU
|
|
503
|
+
- both select plural forms by the locale's CLDR categories; ICU's `other` catches every category a message does not write, while in `parser-curly` such a category takes the fallback, so a message for a locale writes the categories it uses
|
|
504
|
+
- ICU also has `offset` and the `#` shorthand
|
|
481
505
|
- `parser-curly` has one dependency, the format's reference implementation, and no others
|
|
482
506
|
- ICU is an industry standard
|
|
483
507
|
|
|
@@ -489,7 +513,7 @@ Choose `parser-curly` for simplicity, ICU for standards compliance.
|
|
|
489
513
|
- [All Parsers](https://github.com/sveltekit-i18n/parsers) – Parser overview
|
|
490
514
|
- [Curly Message Format](https://curlymessage.dev) – The specification
|
|
491
515
|
- [Examples](https://github.com/sveltekit-i18n/lib/tree/master/examples) – Working examples
|
|
492
|
-
- [Changelog](
|
|
516
|
+
- [Changelog](https://github.com/sveltekit-i18n/parsers/blob/parser-curly@3.2.0/parser-curly/CHANGELOG.md) – Version history
|
|
493
517
|
|
|
494
518
|
## Issues
|
|
495
519
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sveltekit-i18n/parser-curly",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Curly Message Format parser for the sveltekit-i18n library.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"@sveltekit-i18n/base": "^3.0.0 || ^3.1.0-next.0"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@curly-message/parser": "^3.
|
|
56
|
+
"@curly-message/parser": "^3.1.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@curly-message/conformance": "^4.
|
|
59
|
+
"@curly-message/conformance": "^4.1.0",
|
|
60
60
|
"@eslint/js": "^10.0.1",
|
|
61
61
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
62
|
-
"@sveltekit-i18n/base": "^3.
|
|
62
|
+
"@sveltekit-i18n/base": "^3.1.2",
|
|
63
63
|
"eslint": "^10.8.1",
|
|
64
64
|
"eslint-plugin-import-x": "^4.17.1",
|
|
65
65
|
"globals": "^17.11.0",
|