polytypo 1.12.0 → 1.13.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -0
  3. data/lib/polytypo/data/VERSION +1 -1
  4. data/lib/polytypo/data/fixtures/bg.json +1 -1
  5. data/lib/polytypo/data/fixtures/cs.json +1 -1
  6. data/lib/polytypo/data/fixtures/de-CH.json +1 -1
  7. data/lib/polytypo/data/fixtures/de-DE.json +77 -1
  8. data/lib/polytypo/data/fixtures/el.json +1 -1
  9. data/lib/polytypo/data/fixtures/en-GB.json +89 -1
  10. data/lib/polytypo/data/fixtures/en-US.json +450 -1
  11. data/lib/polytypo/data/fixtures/es.json +1 -1
  12. data/lib/polytypo/data/fixtures/fi.json +1 -1
  13. data/lib/polytypo/data/fixtures/fr-CA.json +1 -1
  14. data/lib/polytypo/data/fixtures/fr.json +41 -1
  15. data/lib/polytypo/data/fixtures/hr.json +1 -1
  16. data/lib/polytypo/data/fixtures/hu.json +1 -1
  17. data/lib/polytypo/data/fixtures/it.json +1 -1
  18. data/lib/polytypo/data/fixtures/locale-resolution.json +1 -1
  19. data/lib/polytypo/data/fixtures/nl.json +1 -1
  20. data/lib/polytypo/data/fixtures/pl.json +1 -1
  21. data/lib/polytypo/data/fixtures/pt-BR.json +1 -1
  22. data/lib/polytypo/data/fixtures/pt-PT.json +1 -1
  23. data/lib/polytypo/data/fixtures/ro.json +1 -1
  24. data/lib/polytypo/data/fixtures/ru.json +1 -1
  25. data/lib/polytypo/data/fixtures/sk.json +1 -1
  26. data/lib/polytypo/data/fixtures/sv.json +1 -1
  27. data/lib/polytypo/data/fixtures/tr.json +1 -1
  28. data/lib/polytypo/data/fixtures/uk.json +1 -1
  29. data/lib/polytypo/data/locales/registry.json +1 -1
  30. data/lib/polytypo/data/rules/analyze.md +16 -4
  31. data/lib/polytypo/data/rules/modes.md +262 -9
  32. data/lib/polytypo/data/rules/order.json +1 -1
  33. data/lib/polytypo/data/schema/fixtures.schema.json +160 -18
  34. data/lib/polytypo/engine/origin.rb +13 -1
  35. data/lib/polytypo/engine/protect.rb +122 -0
  36. data/lib/polytypo/modes/runner.rb +0 -1
  37. data/lib/polytypo/version.rb +1 -1
  38. data/lib/polytypo.rb +34 -19
  39. metadata +2 -1
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "en-US",
4
4
  "cases": [
5
5
  {
@@ -4152,6 +4152,455 @@
4152
4152
  "in": "--- !t\ntitle: c -- d\n",
4153
4153
  "out": "--- !t\ntitle: c -- d\n",
4154
4154
  "note": "modes.md §3.8.4 (spec 1.11.0, polytypo#64): accepted miss (§7.11): a document marker in column 0 followed by a tag absorbs the document."
4155
+ },
4156
+ {
4157
+ "id": "en-us-protect-leading-hole-dash",
4158
+ "rule": "dashes",
4159
+ "mode": "text",
4160
+ "in": "${x} - Rules",
4161
+ "protect": [
4162
+ {
4163
+ "start": 0,
4164
+ "end": 4
4165
+ }
4166
+ ],
4167
+ "out": "${x}—Rules",
4168
+ "protectOut": [
4169
+ {
4170
+ "start": 0,
4171
+ "end": 4
4172
+ }
4173
+ ],
4174
+ "note": "modes.md §3.9.3: a protected run at the very start of the input is still a marker, so the dash has a word on its left and converts exactly as it does in the whole string."
4175
+ },
4176
+ {
4177
+ "id": "en-us-protect-dash-adjacent-contraction",
4178
+ "rule": "dashes",
4179
+ "mode": "text",
4180
+ "in": "${a}--${b}",
4181
+ "protect": [
4182
+ {
4183
+ "start": 0,
4184
+ "end": 4
4185
+ },
4186
+ {
4187
+ "start": 6,
4188
+ "end": 10
4189
+ }
4190
+ ],
4191
+ "out": "${a}—${b}",
4192
+ "protectOut": [
4193
+ {
4194
+ "start": 0,
4195
+ "end": 4
4196
+ },
4197
+ {
4198
+ "start": 5,
4199
+ "end": 9
4200
+ }
4201
+ ],
4202
+ "note": "modes.md §3.9.4: '--' → U+2014 is a contraction, which the edge-growth rule admits at a span edge (§7.9)."
4203
+ },
4204
+ {
4205
+ "id": "en-us-protect-quotes-around-hole",
4206
+ "rule": "quotes",
4207
+ "mode": "text",
4208
+ "in": "He said \"${name}\" twice.",
4209
+ "protect": [
4210
+ {
4211
+ "start": 9,
4212
+ "end": 16
4213
+ }
4214
+ ],
4215
+ "out": "He said “${name}” twice.",
4216
+ "protectOut": [
4217
+ {
4218
+ "start": 9,
4219
+ "end": 16
4220
+ }
4221
+ ],
4222
+ "note": "modes.md §3.9.3: the marker is OPENISH and CLOSEISH, so a quotation whose content is only a hole still pairs (§3.3)."
4223
+ },
4224
+ {
4225
+ "id": "en-us-protect-quotes-nested-hole",
4226
+ "rule": "quotes",
4227
+ "mode": "text",
4228
+ "in": "\"Say 'hi' to ${name}\" he said.",
4229
+ "protect": [
4230
+ {
4231
+ "start": 13,
4232
+ "end": 20
4233
+ }
4234
+ ],
4235
+ "out": "“Say ‘hi’ to ${name}” he said.",
4236
+ "protectOut": [
4237
+ {
4238
+ "start": 13,
4239
+ "end": 20
4240
+ }
4241
+ ],
4242
+ "note": "modes.md §3.9.3: a hole inside a quotation does not stop the inner pair from taking the secondary glyphs."
4243
+ },
4244
+ {
4245
+ "id": "en-us-protect-apostrophe-after-hole",
4246
+ "rule": "apostrophe",
4247
+ "mode": "text",
4248
+ "in": "${name}'s book",
4249
+ "protect": [
4250
+ {
4251
+ "start": 0,
4252
+ "end": 7
4253
+ }
4254
+ ],
4255
+ "out": "${name}’s book",
4256
+ "protectOut": [
4257
+ {
4258
+ "start": 0,
4259
+ "end": 7
4260
+ }
4261
+ ],
4262
+ "note": "modes.md §3.9.3: a possessive apostrophe flush against a hole is read by the span-boundary veto of quotes.md §3.2 and is converted, as it is after an inline element."
4263
+ },
4264
+ {
4265
+ "id": "en-us-protect-ellipsis-around-hole",
4266
+ "rule": "ellipsis",
4267
+ "mode": "text",
4268
+ "in": "wait...${x}...done",
4269
+ "protect": [
4270
+ {
4271
+ "start": 7,
4272
+ "end": 11
4273
+ }
4274
+ ],
4275
+ "out": "wait…${x}…done",
4276
+ "protectOut": [
4277
+ {
4278
+ "start": 5,
4279
+ "end": 9
4280
+ }
4281
+ ],
4282
+ "note": "modes.md §3.9.4: replacements that shrink are applied right up to the hole."
4283
+ },
4284
+ {
4285
+ "id": "en-us-protect-all-input",
4286
+ "rule": "spaces",
4287
+ "mode": "text",
4288
+ "in": "${x} -- ${y}",
4289
+ "protect": [
4290
+ {
4291
+ "start": 0,
4292
+ "end": 14
4293
+ }
4294
+ ],
4295
+ "out": "${x} -- ${y}",
4296
+ "protectOut": [
4297
+ {
4298
+ "start": 0,
4299
+ "end": 14
4300
+ }
4301
+ ],
4302
+ "note": "modes.md §3.9.3: when the whole input is protected the array is a single marker and nothing changes."
4303
+ },
4304
+ {
4305
+ "id": "en-us-protect-astral-offsets",
4306
+ "rule": "dashes",
4307
+ "mode": "text",
4308
+ "in": "😀 -- 😀${x} -- 😀",
4309
+ "protect": [
4310
+ {
4311
+ "start": 5,
4312
+ "end": 10
4313
+ }
4314
+ ],
4315
+ "out": "😀—😀${x}—😀",
4316
+ "protectOut": [
4317
+ {
4318
+ "start": 2,
4319
+ "end": 7
4320
+ }
4321
+ ],
4322
+ "note": "modes.md §3.9.2: offsets are Unicode code points. An astral character before and inside the hole shifts every UTF-16 index by one but no code-point offset."
4323
+ },
4324
+ {
4325
+ "id": "en-us-protect-hole-with-line-terminator",
4326
+ "rule": "dashes",
4327
+ "mode": "text",
4328
+ "in": "x ${\n y\n} -- z",
4329
+ "protect": [
4330
+ {
4331
+ "start": 2,
4332
+ "end": 9
4333
+ }
4334
+ ],
4335
+ "out": "x ${\n y\n} -- z",
4336
+ "protectOut": [
4337
+ {
4338
+ "start": 2,
4339
+ "end": 9
4340
+ }
4341
+ ],
4342
+ "note": "modes.md §3.9.1, accepted cost: a protected run containing a line terminator is a line marker (BREAK), so the dash to its right is declined. The caller sees one line of rendered text; the specification looks at the source of the hole."
4343
+ },
4344
+ {
4345
+ "id": "en-us-protect-spaces-bordering-hole",
4346
+ "rule": "spaces",
4347
+ "mode": "text",
4348
+ "in": "Hello ${n} and you",
4349
+ "protect": [
4350
+ {
4351
+ "start": 7,
4352
+ "end": 11
4353
+ }
4354
+ ],
4355
+ "out": "Hello ${n} and you",
4356
+ "protectOut": [
4357
+ {
4358
+ "start": 7,
4359
+ "end": 11
4360
+ }
4361
+ ],
4362
+ "note": "modes.md §3.3 edge test: a run of spaces bordering the marker is left alone; the run in the interior collapses."
4363
+ },
4364
+ {
4365
+ "id": "en-us-protect-empty-list",
4366
+ "rule": "dashes",
4367
+ "mode": "text",
4368
+ "in": "a -- b",
4369
+ "protect": [],
4370
+ "out": "a—b",
4371
+ "protectOut": [],
4372
+ "note": "modes.md §3.9.2: an empty list is the same as no option."
4373
+ },
4374
+ {
4375
+ "id": "en-us-protect-throws-overlap",
4376
+ "rule": "dashes",
4377
+ "mode": "text",
4378
+ "in": "a -- b -- c",
4379
+ "protect": [
4380
+ {
4381
+ "start": 0,
4382
+ "end": 4
4383
+ },
4384
+ {
4385
+ "start": 3,
4386
+ "end": 6
4387
+ }
4388
+ ],
4389
+ "throws": "POLYTYPO_INVALID_OPTION",
4390
+ "note": "modes.md §3.9.2: ranges overlap."
4391
+ },
4392
+ {
4393
+ "id": "en-us-protect-throws-descending",
4394
+ "rule": "dashes",
4395
+ "mode": "text",
4396
+ "in": "a -- b -- c",
4397
+ "protect": [
4398
+ {
4399
+ "start": 5,
4400
+ "end": 7
4401
+ },
4402
+ {
4403
+ "start": 0,
4404
+ "end": 2
4405
+ }
4406
+ ],
4407
+ "throws": "POLYTYPO_INVALID_OPTION",
4408
+ "note": "modes.md §3.9.2: ranges are not in ascending order."
4409
+ },
4410
+ {
4411
+ "id": "en-us-protect-throws-empty-range",
4412
+ "rule": "dashes",
4413
+ "mode": "text",
4414
+ "in": "a -- b",
4415
+ "protect": [
4416
+ {
4417
+ "start": 2,
4418
+ "end": 2
4419
+ }
4420
+ ],
4421
+ "throws": "POLYTYPO_INVALID_OPTION",
4422
+ "note": "modes.md §3.9.2: start must be less than end."
4423
+ },
4424
+ {
4425
+ "id": "en-us-protect-throws-past-end",
4426
+ "rule": "dashes",
4427
+ "mode": "text",
4428
+ "in": "a -- b",
4429
+ "protect": [
4430
+ {
4431
+ "start": 4,
4432
+ "end": 7
4433
+ }
4434
+ ],
4435
+ "throws": "POLYTYPO_INVALID_OPTION",
4436
+ "note": "modes.md §3.9.2: the input has six code points."
4437
+ },
4438
+ {
4439
+ "id": "en-us-protect-throws-code-point-bound",
4440
+ "rule": "dashes",
4441
+ "mode": "text",
4442
+ "in": "😀",
4443
+ "protect": [
4444
+ {
4445
+ "start": 0,
4446
+ "end": 2
4447
+ }
4448
+ ],
4449
+ "throws": "POLYTYPO_INVALID_OPTION",
4450
+ "note": "modes.md §3.9.2: the bound is in code points. This input has one code point and two UTF-16 units; end = 2 is past the end."
4451
+ },
4452
+ {
4453
+ "id": "en-us-protect-throws-negative-start",
4454
+ "rule": "dashes",
4455
+ "mode": "text",
4456
+ "in": "a -- b",
4457
+ "protect": [
4458
+ {
4459
+ "start": -1,
4460
+ "end": 2
4461
+ }
4462
+ ],
4463
+ "throws": "POLYTYPO_INVALID_OPTION",
4464
+ "note": "modes.md §3.9.2: start is not negative."
4465
+ },
4466
+ {
4467
+ "id": "en-us-protect-throws-non-integer",
4468
+ "rule": "dashes",
4469
+ "mode": "text",
4470
+ "in": "a -- b",
4471
+ "protect": [
4472
+ {
4473
+ "start": 0,
4474
+ "end": 1.5
4475
+ }
4476
+ ],
4477
+ "throws": "POLYTYPO_INVALID_OPTION",
4478
+ "note": "modes.md §3.9.2: offsets are integers."
4479
+ },
4480
+ {
4481
+ "id": "en-us-protect-throws-not-a-list",
4482
+ "rule": "dashes",
4483
+ "mode": "text",
4484
+ "in": "a -- b",
4485
+ "protect": {
4486
+ "start": 0,
4487
+ "end": 1
4488
+ },
4489
+ "throws": "POLYTYPO_INVALID_OPTION",
4490
+ "note": "modes.md §3.9.2: the option is a list."
4491
+ },
4492
+ {
4493
+ "id": "en-us-protect-throws-html",
4494
+ "rule": "dashes",
4495
+ "mode": "html",
4496
+ "in": "<p>a -- b</p>",
4497
+ "protect": [
4498
+ {
4499
+ "start": 3,
4500
+ "end": 4
4501
+ }
4502
+ ],
4503
+ "throws": "POLYTYPO_INVALID_OPTION",
4504
+ "note": "modes.md §3.9.2: a non-empty protect is refused outside text mode instead of being ignored, because ignoring it would typeset text the caller marked."
4505
+ },
4506
+ {
4507
+ "id": "en-us-protect-throws-yaml",
4508
+ "rule": "dashes",
4509
+ "mode": "yaml",
4510
+ "keys": [
4511
+ "title"
4512
+ ],
4513
+ "in": "title: a -- b\n",
4514
+ "protect": [
4515
+ {
4516
+ "start": 7,
4517
+ "end": 8
4518
+ }
4519
+ ],
4520
+ "throws": "POLYTYPO_INVALID_OPTION",
4521
+ "note": "modes.md §3.9.2: the same refusal in yaml mode."
4522
+ },
4523
+ {
4524
+ "id": "en-us-protect-hole-with-line-separator",
4525
+ "rule": "dashes",
4526
+ "mode": "text",
4527
+ "in": "x ${
 y} -- z",
4528
+ "protect": [
4529
+ {
4530
+ "start": 2,
4531
+ "end": 8
4532
+ }
4533
+ ],
4534
+ "out": "x ${
 y} -- z",
4535
+ "protectOut": [
4536
+ {
4537
+ "start": 2,
4538
+ "end": 8
4539
+ }
4540
+ ],
4541
+ "note": "modes.md §3.9.1: U+2028 is a line terminator, so a hole containing it is a line marker and the dash to its right is declined, exactly as for U+000A."
4542
+ },
4543
+ {
4544
+ "id": "en-us-protect-hole-with-next-line",
4545
+ "rule": "dashes",
4546
+ "mode": "text",
4547
+ "in": "x ${… y} -- z",
4548
+ "protect": [
4549
+ {
4550
+ "start": 2,
4551
+ "end": 8
4552
+ }
4553
+ ],
4554
+ "out": "x ${… y} -- z",
4555
+ "protectOut": [
4556
+ {
4557
+ "start": 2,
4558
+ "end": 8
4559
+ }
4560
+ ],
4561
+ "note": "modes.md §3.9.1: U+0085 is one of the seven code points that make a protected run a line marker; a port that tests only U+000A and U+000D fails here."
4562
+ },
4563
+ {
4564
+ "id": "en-us-protect-empty-list-html",
4565
+ "rule": "dashes",
4566
+ "mode": "html",
4567
+ "in": "<p>a -- b</p>",
4568
+ "protect": [],
4569
+ "out": "<p>a—b</p>",
4570
+ "protectOut": [],
4571
+ "note": "modes.md §3.9.2: an empty list is accepted in every mode, being the same call as no option."
4572
+ },
4573
+ {
4574
+ "id": "en-us-protect-throws-markdown",
4575
+ "rule": "dashes",
4576
+ "mode": "markdown",
4577
+ "dialect": "commonmark",
4578
+ "in": "a -- b\n",
4579
+ "protect": [
4580
+ {
4581
+ "start": 0,
4582
+ "end": 1
4583
+ }
4584
+ ],
4585
+ "throws": "POLYTYPO_INVALID_OPTION",
4586
+ "note": "modes.md §3.9.2: the same refusal in markdown mode."
4587
+ },
4588
+ {
4589
+ "id": "en-us-protect-throws-after-rules",
4590
+ "rule": "dashes",
4591
+ "mode": "text",
4592
+ "rules": {
4593
+ "nosuchrule": true
4594
+ },
4595
+ "in": "a -- b",
4596
+ "protect": [
4597
+ {
4598
+ "start": 3,
4599
+ "end": 2
4600
+ }
4601
+ ],
4602
+ "throws": "POLYTYPO_UNKNOWN_RULE",
4603
+ "note": "modes.md §3.9.2, validation order: an unknown rule id is reported before a malformed protect."
4155
4604
  }
4156
4605
  ]
4157
4606
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "es",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "fi",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "fr-CA",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "fr",
4
4
  "cases": [
5
5
  {
@@ -1038,6 +1038,46 @@
1038
1038
  "in": "title: a \"b\"\nslug: c\rd \"e\"\n",
1039
1039
  "out": "title: a « b »\nslug: c\rd \"e\"\n",
1040
1040
  "note": "modes.md §3.8.4 step 9 in a locale whose quotation marks carry their own spacing: the line with the lone U+000D is declined and the line before it converts, so the cost is one line and not the document."
1041
+ },
1042
+ {
1043
+ "id": "fr-protect-nbsp-before-hole",
1044
+ "rule": "nbsp",
1045
+ "mode": "text",
1046
+ "in": "Bonjour ${name} !",
1047
+ "protect": [
1048
+ {
1049
+ "start": 8,
1050
+ "end": 15
1051
+ }
1052
+ ],
1053
+ "out": "Bonjour ${name} !",
1054
+ "protectOut": [
1055
+ {
1056
+ "start": 8,
1057
+ "end": 15
1058
+ }
1059
+ ],
1060
+ "note": "modes.md §3.9.3: the space before '!' is already in the text, so nbsp converts it in place (1 → 1)."
1061
+ },
1062
+ {
1063
+ "id": "fr-protect-nbsp-insertion-at-hole-declined",
1064
+ "rule": "nbsp",
1065
+ "mode": "text",
1066
+ "in": "Bonjour ${name}!",
1067
+ "protect": [
1068
+ {
1069
+ "start": 8,
1070
+ "end": 15
1071
+ }
1072
+ ],
1073
+ "out": "Bonjour ${name}!",
1074
+ "protectOut": [
1075
+ {
1076
+ "start": 8,
1077
+ "end": 15
1078
+ }
1079
+ ],
1080
+ "note": "modes.md §3.9.4, accepted cost: the narrow space would be inserted at a span edge next to the hole, which the edge-growth rule (§3.4) discards. Without protect it is inserted."
1041
1081
  }
1042
1082
  ]
1043
1083
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "hr",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "hu",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "it",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "cases": [
4
4
  {
5
5
  "id": "exact-en-us",
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "nl",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "pl",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "pt-BR",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "pt-PT",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "ro",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "ru",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "sk",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "sv",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "tr",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "uk",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.12.0",
2
+ "spec": "1.13.0",
3
3
  "$comment": "Locale resolution input. Algorithm is specified in spec/rules/locale-resolution.md and is identical in every runtime — never delegate it to a platform locale-negotiation library. \"spec\" here must track spec/VERSION exactly — it is not itself the global version source; scripts/validate-spec.mjs enforces the match.",
4
4
  "locales": [
5
5
  "en-US",