polytypo 1.11.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 (47) 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 +253 -0
  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 +253 -0
  16. data/lib/polytypo/data/fixtures/hu.json +264 -0
  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 +234 -0
  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/bg.json +82 -0
  30. data/lib/polytypo/data/locales/hr.json +82 -0
  31. data/lib/polytypo/data/locales/hu.json +88 -0
  32. data/lib/polytypo/data/locales/registry.json +6 -2
  33. data/lib/polytypo/data/locales/ro.json +82 -0
  34. data/lib/polytypo/data/rules/analyze.md +16 -4
  35. data/lib/polytypo/data/rules/dashes.md +2 -1
  36. data/lib/polytypo/data/rules/modes.md +262 -9
  37. data/lib/polytypo/data/rules/order.json +1 -1
  38. data/lib/polytypo/data/rules/quotes.md +9 -0
  39. data/lib/polytypo/data/rules/ranges.md +6 -5
  40. data/lib/polytypo/data/schema/fixtures.schema.json +160 -18
  41. data/lib/polytypo/data/schema/locale.schema.json +2 -2
  42. data/lib/polytypo/engine/origin.rb +13 -1
  43. data/lib/polytypo/engine/protect.rb +122 -0
  44. data/lib/polytypo/modes/runner.rb +0 -1
  45. data/lib/polytypo/version.rb +1 -1
  46. data/lib/polytypo.rb +34 -19
  47. metadata +10 -1
@@ -0,0 +1,234 @@
1
+ {
2
+ "spec": "1.13.0",
3
+ "locale": "ro",
4
+ "cases": [
5
+ {
6
+ "id": "ro-quotes-primary",
7
+ "rule": "quotes",
8
+ "mode": "text",
9
+ "in": "A spus \"salut\".",
10
+ "out": "A spus „salut”.",
11
+ "note": "quotes.primary = U+201E/U+201D (forma «99…99»): ÎOOP6 §III.1.2, «ghilimele românești „ ”»."
12
+ },
13
+ {
14
+ "id": "ro-quotes-not-german",
15
+ "rule": "quotes",
16
+ "mode": "text",
17
+ "in": "„salut“",
18
+ "out": "„salut”",
19
+ "note": "DISTINCTIVE CASE: input is the German/Czech pair with closing U+201C; output is the Romanian closing U+201D. The two differ by one glyph in print, hence a fixture of their own."
20
+ },
21
+ {
22
+ "id": "ro-quotes-nested",
23
+ "rule": "quotes",
24
+ "mode": "text",
25
+ "in": "\"Ea a zis 'bine' acolo\"",
26
+ "out": "„Ea a zis «bine» acolo”",
27
+ "note": "quotes.secondary = U+00AB/U+00BB, pointing inwards: ÎOOP6 gives guillemets the role of a quote inside a quote."
28
+ },
29
+ {
30
+ "id": "ro-quotes-nested-guillemets-kept",
31
+ "rule": "quotes",
32
+ "mode": "text",
33
+ "in": "„Ea a zis «bine» acolo”",
34
+ "out": "„Ea a zis «bine» acolo”",
35
+ "note": "An already typeset nested pair is a fixed point."
36
+ },
37
+ {
38
+ "id": "ro-quotes-no-inner-space",
39
+ "rule": "quotes",
40
+ "mode": "text",
41
+ "in": "„ salut ”",
42
+ "out": "„salut”",
43
+ "note": "innerSpace = none: ÎOOP6 «Între ghilimele și elementul pe care îl încadrează nu se lasă blanc.»"
44
+ },
45
+ {
46
+ "id": "ro-ellipsis-three-dots",
47
+ "rule": "ellipsis",
48
+ "mode": "text",
49
+ "in": "Stai... ce?",
50
+ "out": "Stai… ce?",
51
+ "note": "Three dots become the single character U+2026."
52
+ },
53
+ {
54
+ "id": "ro-ellipsis-not-abbreviated",
55
+ "rule": "ellipsis",
56
+ "mode": "text",
57
+ "in": "Serios?.. da",
58
+ "out": "Serios?… da",
59
+ "note": "abbreviatedAfterTerminal = false: no source attests the two-dot form after a terminal mark; medium confidence, see sources."
60
+ },
61
+ {
62
+ "id": "ro-dashes-parenthetical",
63
+ "rule": "dashes",
64
+ "mode": "text",
65
+ "in": "Nu avem încă - firește - rezultate.",
66
+ "out": "Nu avem încă – firește – rezultate.",
67
+ "note": "dash.parenthetical = en-spaced: ÎOOP6 «Înainte și după linia de pauză, blancul este obligatoriu»."
68
+ },
69
+ {
70
+ "id": "ro-dashes-existing-em-dash",
71
+ "rule": "dashes",
72
+ "mode": "text",
73
+ "in": "Nu avem încă — firește — rezultate.",
74
+ "out": "Nu avem încă – firește – rezultate.",
75
+ "note": "The sources name U+2013 (the Order's annex, En-Dash) and never U+2014, so an em dash is rewritten."
76
+ },
77
+ {
78
+ "id": "ro-ranges-hyphen-kept",
79
+ "rule": "ranges",
80
+ "mode": "text",
81
+ "in": "anii 2020-2021, perioada 1-13 mai",
82
+ "out": "anii 2020-2021, perioada 1-13 mai",
83
+ "note": "ranges.md §3: dash.range = none. ÎOOP6 and DOOM3 prefer the unspaced hyphen for ranges, which is the input form, so nothing is substituted.",
84
+ "rules": {
85
+ "ranges": true
86
+ }
87
+ },
88
+ {
89
+ "id": "ro-ranges-spaced-kept",
90
+ "rule": "ranges",
91
+ "mode": "text",
92
+ "in": "noaptea de 30 – 31 decembrie",
93
+ "out": "noaptea de 30 – 31 decembrie",
94
+ "note": "ranges.md §3: dash.range = none. The spaced en dash is the second form ÎOOP6 attests for dates (30 – 31 decembrie); none preserves it.",
95
+ "rules": {
96
+ "ranges": true
97
+ }
98
+ },
99
+ {
100
+ "id": "ro-ranges-en-dash-tight-kept",
101
+ "rule": "ranges",
102
+ "mode": "text",
103
+ "in": "perioada 1–31 ianuarie",
104
+ "out": "perioada 1–31 ianuarie",
105
+ "note": "ranges.md §3: dash.range = none. DOOM3 «Bara oblică» II.3 b attests the unspaced en dash as a third range form; none preserves it.",
106
+ "rules": {
107
+ "ranges": true
108
+ }
109
+ },
110
+ {
111
+ "id": "ro-ranges-spaced-hyphen-untouched",
112
+ "rule": "ranges",
113
+ "mode": "text",
114
+ "in": "paginile 1 - 22",
115
+ "out": "paginile 1 - 22",
116
+ "note": "ranges.md §3: dash.range = none. With range = none a spaced hyphen is left as authored.",
117
+ "rules": {
118
+ "ranges": true
119
+ }
120
+ },
121
+ {
122
+ "id": "ro-hyphen-noop",
123
+ "rule": "hyphen",
124
+ "mode": "text",
125
+ "in": "s-a dus și l-am văzut",
126
+ "out": "s-a dus și l-am văzut",
127
+ "note": "The hyphen lists are empty: DOOM3 5.1.5 gives a general recommendation, not a closed list of forms."
128
+ },
129
+ {
130
+ "id": "ro-nbsp-no-short-words",
131
+ "rule": "nbsp",
132
+ "mode": "text",
133
+ "in": "și la ea, o dată",
134
+ "out": "și la ea, o dată",
135
+ "note": "afterShortWords is empty: no source states a short-word rule."
136
+ },
137
+ {
138
+ "id": "ro-nbsp-before-number",
139
+ "rule": "nbsp",
140
+ "mode": "text",
141
+ "in": "conform art. 3 și nr. 5",
142
+ "out": "conform art. 3 și nr. 5",
143
+ "note": "beforeNumber: DOOM3 5.2 «nu art. // 3»; nr. is the same class."
144
+ },
145
+ {
146
+ "id": "ro-nbsp-units",
147
+ "rule": "nbsp",
148
+ "mode": "text",
149
+ "in": "10 km, 5 kg, 20 cm, 3 mm și 90 km/h",
150
+ "out": "10 km, 5 kg, 20 cm, 3 mm și 90 km/h",
151
+ "note": "beforeUnits: DOOM3 5.2 «nu 10 // km»; the other symbols follow BIPM."
152
+ },
153
+ {
154
+ "id": "ro-nbsp-celsius",
155
+ "rule": "nbsp",
156
+ "mode": "text",
157
+ "in": "19 °C",
158
+ "out": "19 °C",
159
+ "note": "°C: same class as the other unit symbols; BIPM leaves a single space before the unit."
160
+ },
161
+ {
162
+ "id": "ro-nbsp-percent-inert",
163
+ "rule": "nbsp",
164
+ "mode": "text",
165
+ "in": "50 % din preț",
166
+ "out": "50 % din preț",
167
+ "note": "% is not listed: it rests only on a non-normative guide, so it is not bound."
168
+ },
169
+ {
170
+ "id": "ro-nbsp-initial-single",
171
+ "rule": "nbsp",
172
+ "mode": "text",
173
+ "in": "I. Popescu",
174
+ "out": "I. Popescu",
175
+ "note": "initialBinding = single: DOOM3 5.2 «nu Ion // Popescu, I. // Popescu»."
176
+ },
177
+ {
178
+ "id": "ro-nbsp-initials-compact-unbound",
179
+ "rule": "nbsp",
180
+ "mode": "text",
181
+ "in": "I.L. Caragiale",
182
+ "out": "I.L. Caragiale",
183
+ "note": "ACCEPTED MISS: DOOM3 1.2.6 writes compound initials closed up (I.L.), and N7 (nbsp.md §3.9) does not treat the L. after a full stop as an initial, so the space before the surname stays breakable. A false negative, never a damaged text."
184
+ },
185
+ {
186
+ "id": "ro-nbsp-initial-sentence-boundary",
187
+ "rule": "nbsp",
188
+ "mode": "text",
189
+ "in": "în anul A. Apoi",
190
+ "out": "în anul A. Apoi",
191
+ "note": "ACCEPTED COST of initialBinding = single: a lone capital before a full stop is read as an initial even at a sentence boundary (nbsp.md §3.9), the price of binding «I. // Popescu»."
192
+ },
193
+ {
194
+ "id": "ro-nbsp-no-space-before-question",
195
+ "rule": "nbsp",
196
+ "mode": "text",
197
+ "in": "Serios?",
198
+ "out": "Serios?",
199
+ "note": "No no-break space before ? ! : ;."
200
+ },
201
+ {
202
+ "id": "ro-nbsp-abbreviation-inert",
203
+ "rule": "nbsp",
204
+ "mode": "text",
205
+ "in": "nr. crt. 4",
206
+ "out": "nr. crt. 4",
207
+ "note": "abbreviations is empty: no source forbids splitting a spaced abbreviation such as nr. crt."
208
+ },
209
+ {
210
+ "id": "ro-spaces-brackets-and-final-stop",
211
+ "rule": "spaces",
212
+ "mode": "text",
213
+ "in": "Text ( obișnuit ) rămâne .",
214
+ "out": "Text (obișnuit) rămâne.",
215
+ "note": "The spaces rule is language-independent."
216
+ },
217
+ {
218
+ "id": "ro-symbols-copyright",
219
+ "rule": "symbols",
220
+ "mode": "text",
221
+ "in": "Copyright (c) 2026",
222
+ "out": "Copyright © 2026",
223
+ "note": "The symbols rule is language-independent."
224
+ },
225
+ {
226
+ "id": "ro-apostrophe-foreign-name",
227
+ "rule": "apostrophe",
228
+ "mode": "text",
229
+ "in": "A mers la McDonald's și înapoi",
230
+ "out": "A mers la McDonald’s și înapoi",
231
+ "note": "An apostrophe in a foreign name is written U+2019."
232
+ }
233
+ ]
234
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.11.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "ru",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.11.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "sk",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.11.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "sv",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.11.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "tr",
4
4
  "cases": [
5
5
  {
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.11.0",
2
+ "spec": "1.13.0",
3
3
  "locale": "uk",
4
4
  "cases": [
5
5
  {
@@ -0,0 +1,82 @@
1
+ {
2
+ "locale": "bg",
3
+ "name": "Bulgarian",
4
+ "quotes": {
5
+ "primary": {
6
+ "open": "„",
7
+ "close": "“",
8
+ "innerSpace": "none"
9
+ },
10
+ "secondary": {
11
+ "open": "„",
12
+ "close": "“",
13
+ "innerSpace": "none"
14
+ },
15
+ "elisionIdioms": [],
16
+ "elisionClitics": {
17
+ "before": [],
18
+ "after": []
19
+ }
20
+ },
21
+ "dash": {
22
+ "parenthetical": "en-spaced",
23
+ "range": "en-spaced"
24
+ },
25
+ "ellipsis": {
26
+ "abbreviatedAfterTerminal": false
27
+ },
28
+ "hyphen": {
29
+ "prefixes": [],
30
+ "suffixes": [],
31
+ "compounds": []
32
+ },
33
+ "nbsp": {
34
+ "beforePunctuation": [],
35
+ "narrowBeforePunctuation": [],
36
+ "afterShortWords": [],
37
+ "abbreviations": [],
38
+ "beforeUnits": ["км", "кг", "лв.", "г."],
39
+ "beforeNumber": [],
40
+ "beforeWord": [],
41
+ "afterSymbols": [],
42
+ "initialBinding": "none"
43
+ },
44
+ "sources": [
45
+ {
46
+ "rule": "quotes",
47
+ "cite": "Институт за български език (БАН), Езикови справки, «Други случаи», quoting Официален правописен речник на българския език (2012), т. 135.1.5: «Не се удвояват кавичките, когато ограденият в кавички текст завършва с друг текст, ограден в кавички: стихотворението „Кафене „Славия“.»; ИБЕ, Речник на българския език (онлайн), s.v. «кавички»: «Препинателен знак от две двойки запетайки („ .. “)»; ИБЕ, «За интервалите и препинателните знаци» (Н. Костова, Аз Буки бр. 3/2018): a space before the opening and after the closing quotation mark, none inside",
48
+ "url": "https://ibl.bas.bg/ezikovispravki/vaprosi/drugi-sluchai-2",
49
+ "note": "Primary: U+201E open, U+201C close (NOT U+201D as in ro/hu). The code points were read from the rendered IBL text, not from a code-point table, so MEDIUM-HIGH. Secondary equals primary: the т. 135.1.5 example nests with the same „ “ pair, and no ‚ ‘ or « » level is attested for nesting (single quotes appear only for glosses in scientific text, not cited). The ОПР itself (beron.mon.bg) answered HTTP 403 and is cited through the Institute that compiled it; the paragraph numbers come from the IBL answers, the paragraphs were not read. innerSpace none per the IBL spacing article. The ОПР rule against doubling the closing marks is an algorithm and is not expressed in data. ACCEPTED COST of the identical pair: a nest whose inner mark touches the outer one is declined by the quotes rule (the fixtures bg-quotes-nest-* and bg-quotes-decline-spreads pin it, quotes.md §6). Q-W: both pairs are WIDE. elisionIdioms and elisionClitics empty: no apostrophe elision in the standard. Verified 2026-10-21."
50
+ },
51
+ {
52
+ "rule": "dashes",
53
+ "cite": "Институт за български език (БАН), Езикови справки №572 («При препинателни знаци»): «Пред и след голямо тире има шпация.»; ИБЕ, «За интервалите и препинателните знаци» (2018): spaced тире in «Технически университет – София»",
54
+ "url": "https://ibl.bas.bg/ezikovispravki/vaprosi/pri-prepinatelni-znatsi-9/572",
55
+ "note": "Supports dash.parenthetical = en-spaced: a space on both sides is stated. The width U+2013 is read from IBL's own rendered text, so MEDIUM. The European Commission DGT Bulgarian «Езиков наръчник» (2010, rev. 2011; house style, ranks below IBL) sets U+2014 in its own typesetting. DECISION recorded: U+2013, following the Institute. Verified 2026-10-21."
56
+ },
57
+ {
58
+ "rule": "ranges",
59
+ "cite": "Институт за български език (БАН), Езикови справки, «За означаване на интервал», quoting ОПР (2012) т. 103.4, 117.1, 117.2: «Тирето може се използва за означаване на период от време, когато е налице пропускане на предлог: 2000 – 2010 г., I – II в.»; Езикови справки №1017, ОПР т. 109.4: «13.10. – 20.11.2018 г.»; ИБЕ, «Отново за тиретата» (Ц. Георгиева, Аз Буки бр. 11/2016): ОПР 2012 codifies the long dash with spaces between numbers, replacing the earlier unspaced norm",
60
+ "url": "https://ibl.bas.bg/ezikovispravki/vaprosi/za-oznachavane-na-interval-15-20-yanuari-8/1605",
61
+ "note": "Supports dash.range = en-spaced (U+0020 U+2013 U+0020), as in sk. The tight form is the pre-2012 norm. Width read from rendered text, so MEDIUM. The ranges rule is off by default. Verified 2026-10-21."
62
+ },
63
+ {
64
+ "rule": "ellipsis",
65
+ "cite": "Институт за български език (БАН), Езикови справки №2158: after «2011 г.» «пишат се три, а не четири точки»; №2132: «Между многоточие и следваща го дума се оставя шпация.»",
66
+ "url": "https://ibl.bas.bg/ezikovispravki/vaprosi/schetavane-na-punktuatsionni-i-nepunktuatsionni-znatsi-v-teksta-3/2158",
67
+ "note": "abbreviatedAfterTerminal = false is a DECISION, not a citation: no source read states a two-dot form after '?' or '!'. ОПР т. 136.2 («Многоточие с други знаци») is the relevant paragraph but beron.mon.bg answered 403 and it was not read. Verified 2026-10-21."
68
+ },
69
+ {
70
+ "rule": "hyphen",
71
+ "cite": "Институт за български език (БАН), Езикови справки, «Пренасяне на думи на нов ред», citing ОПР (2012) т. 65.1–65.4, 66.1 (syllabic line-break rules)",
72
+ "url": "https://ibl.bas.bg/ezikovispravki/vaprosi/prenasyane-na-dumi-na-nov-red-7",
73
+ "note": "All three lists are empty: the cited rules govern syllable division and give no closed list of unbreakable hyphenated forms. Verified 2026-10-21."
74
+ },
75
+ {
76
+ "rule": "nbsp",
77
+ "cite": "Институт за български език (БАН), «За интервалите и препинателните знаци» (2018): «Пред точка, запетая, въпросителен знак, удивителен знак, точка и запетая, двоеточие не се оставя интервал.»; Езикови справки №1601: «Между последната цифра и съкращението се поставя шпация (интервал): 20 лв., също и 5 кг, 10 м, 100 км, 2020 г. и под.»; №626: «В записа 5% няма интервал (шпация) между цифрата и знака за процент.»",
78
+ "url": "https://ibl.bas.bg/ezikovispravki/vaprosi/pri-skrashcheniya-vkl-mezhdu-tsifra-i-skrashchenie-5-kg-2014-g-4/1601",
79
+ "note": "(1) beforePunctuation and narrowBeforePunctuation empty: no space before . , ? ! ; :. (2) beforeUnits = км, кг, лв., г.: each attested with a space after a number. This is form evidence only (IBL does not say non-breaking), the same footing as hu and sk, MEDIUM. The single-letter м is excluded by decision. (3) % is excluded: IBL writes 5% unspaced; the DGT house handbook's «4 %» with a non-breaking space ranks below it. (4) abbreviations, beforeNumber, beforeWord, afterSymbols and afterShortWords are empty and initialBinding = none: no source states a rule (IBL says proper names follow ordinary line-break rules). Decision, not citation. Verified 2026-10-21."
80
+ }
81
+ ]
82
+ }
@@ -0,0 +1,82 @@
1
+ {
2
+ "locale": "hr",
3
+ "name": "Croatian",
4
+ "quotes": {
5
+ "primary": {
6
+ "open": "„",
7
+ "close": "”",
8
+ "innerSpace": "none"
9
+ },
10
+ "secondary": {
11
+ "open": "‘",
12
+ "close": "’",
13
+ "innerSpace": "none"
14
+ },
15
+ "elisionIdioms": [],
16
+ "elisionClitics": {
17
+ "before": [],
18
+ "after": []
19
+ }
20
+ },
21
+ "dash": {
22
+ "parenthetical": "en-spaced",
23
+ "range": "en-spaced"
24
+ },
25
+ "ellipsis": {
26
+ "abbreviatedAfterTerminal": false
27
+ },
28
+ "hyphen": {
29
+ "prefixes": [],
30
+ "suffixes": [],
31
+ "compounds": []
32
+ },
33
+ "nbsp": {
34
+ "beforePunctuation": [],
35
+ "narrowBeforePunctuation": [],
36
+ "afterShortWords": [],
37
+ "abbreviations": ["dipl. ing.", "d. o. o."],
38
+ "beforeUnits": ["%", "‰", "€", "°C", "kg"],
39
+ "beforeNumber": ["str."],
40
+ "beforeWord": [],
41
+ "afterSymbols": ["§"],
42
+ "initialBinding": "none"
43
+ },
44
+ "sources": [
45
+ {
46
+ "rule": "quotes",
47
+ "cite": "Institut za hrvatski jezik i jezikoslovlje, Hrvatski pravopis (online edition), 8.16 Navodnici: «Navodnici su pravopisni znakovi koji se pojavljuju u paru.» «Imaju dva oblika: „...” i »...«.» «Iza prvoga i ispred drugoga navodnika ne piše se bjelina.»; 8.17 Polunavodnici: «Polunavodnici su pravopisni znakovi koji se pojavljuju u paru (‘...’).» — „Himna je Republike Hrvatske ‘Lijepa naša domovino’.”; 8.18 Izostavnik: «Drž’te ga!», «Je l’ došao?»",
48
+ "url": "https://pravopis.hr/pravilo/navodnici/71/",
49
+ "note": "Primary: U+201E open, U+201D close (as in ro/hu, NOT U+201C as in cs/sk/de/bg). 8.16 gives two forms unranked: „…” and »…« (U+00BB open, U+00AB close). U+201E/U+201D is a DECISION, supported by every example on the site (8.16, 8.16.1, 8.17, 8.1). Secondary: U+2018 open, U+2019 close, stated in 8.17 for a quotation inside a quotation; it differs from the primary and is not the low U+201A. innerSpace none for both, per 8.16 and 8.17. Q-W: primary WIDE, secondary NARROW. Code points were read from rendered pravopis.hr text, consistently across four pages, so MEDIUM-HIGH. elisionIdioms and elisionClitics are empty: 8.18 lists both U+2019 and U+0027 and gives no closed set of forward-attaching fragments (l’, al’ stand before a space). Verified 2026-10-21. Not cross-checked against Matica hrvatska (Babić–Moguš) or the EU DGT Croatian guide."
50
+ },
51
+ {
52
+ "rule": "dashes",
53
+ "cite": "Institut za hrvatski jezik i jezikoslovlje, Hrvatski pravopis (online edition), 8.14 Crtica: «Crtica ( – ) pravopisni je znak koji se piše s bjelinama slijeva i zdesna.»",
54
+ "url": "https://pravopis.hr/pravilo/crtica/69/",
55
+ "note": "Supports dash.parenthetical = en-spaced (U+0020 U+2013 U+0020). The width U+2013 was read from rendered text; 8.14 does not mention U+2014. MEDIUM-HIGH. Verified 2026-10-21."
56
+ },
57
+ {
58
+ "rule": "ranges",
59
+ "cite": "Institut za hrvatski jezik i jezikoslovlje, Hrvatski pravopis (online edition), 8.14 Crtica: «pri izražavanju raspona ili u značenju ‘od – do’: 2010. – 2012., autocesta Split – Zagreb»; «Pri bilježenju stranica u bibliografskim podatcima crtica se piše bez bjelina: str. 145–162.»; 8.1 Bjelina: «ispred i iza crtice: Posjeti: 15 – 17»",
60
+ "url": "https://pravopis.hr/pravilo/crtica/69/",
61
+ "note": "Supports dash.range = en-spaced. ACCEPTED COST: bibliographic page ranges are tight per the same paragraph, and locale data cannot select by context, so the rule will space them. The ranges rule is off by default. Year ranges carry an ordinal period (2010.), so the flank is U+002E rather than a digit; see the fixtures hr-ranges-* for the measured behaviour. Verified 2026-10-21."
62
+ },
63
+ {
64
+ "rule": "ellipsis",
65
+ "cite": "Institut za hrvatski jezik i jezikoslovlje, Hrvatski pravopis (online edition), 8.10 Trotočka (uses a–d, e.g. «Popio bih… kavu.»); 8.3 Upitnik",
66
+ "url": "https://pravopis.hr/pravilo/trotocka/64/",
67
+ "note": "abbreviatedAfterTerminal = false is a DECISION, not a citation: neither 8.10 nor 8.3 states a two-dot form after '?' or '!'. Verified 2026-10-21."
68
+ },
69
+ {
70
+ "rule": "hyphen",
71
+ "cite": "Institut za hrvatski jezik i jezikoslovlje, Hrvatski pravopis (online edition), 8.13 Spojnica, rule 6: «Ako se riječi koje se pišu sa spojnicom ipak prenose u novi redak, spojnica se zapisuje i na početku novoga retka: hrvatsko- / -engleski rječnik.»",
72
+ "url": "https://pravopis.hr/pravilo/spojnica/68/",
73
+ "note": "All three lists are empty. Breaking at a hyphen is explicitly provided for, and no closed list of unbreakable hyphenated forms exists. Verified 2026-10-21."
74
+ },
75
+ {
76
+ "rule": "nbsp",
77
+ "cite": "Institut za hrvatski jezik i jezikoslovlje, Hrvatski pravopis (online edition), 8.1 Bjelina: no space before . ? ! , … ; : ?! !? («U koliko je sati ručak?»); «između broja i znaka za mjernu jedinicu: 3 kg, 2 m, 220 V, 60 W, 5 °C»; «između broja i znaka za postotak i promil: 50 %, 62,5 %, 0,5 ‰»; «između broja i oznake novčane jedinice: 99,90 €, 34 kn»; «između oznake za članak i broja: § 25.»; no space «između broja i oznake za kutni stupanj, minutu i sekundu»; 8.19.11: «Znak za postotak bjelinom se odvaja od broja: 63 %.»; 8.19.8: «Znak za članak bjelinom se odvaja od broja.»; 8.13 Spojnica, rule 6: «Treba izbjegavati rastavljanje stranih riječi i imena, složenih kratica (dipl. ing., d. o. o.), složenih pokrata (CD-RW), mrežnih adresa i riječi koje se pišu sa spojnicom.»",
78
+ "url": "https://pravopis.hr/pravilo/bjelina/54/",
79
+ "note": "(1) beforePunctuation and narrowBeforePunctuation are empty: a stated negative, not silence. (2) beforeUnits = %, ‰, €, °C, kg: each is attested with a space after the number; form evidence, sufficient per nbsp.md §2. DECISIONS: single-letter m, V and W are excluded (as in sk and bg); kn is excluded as defunct; bare ° is never listed, since angular degrees are closed up; km, cm and mm are not attested on pravopis.hr and are not added. (3) afterSymbols = §. (4) abbreviations = dipl. ing., d. o. o.: 8.13 states a line-break intent for exactly these two complex abbreviations (MEDIUM-HIGH). The other spaced forms of 7.1 (d. d., dr. sc., i dr. and so on) are not listed: extending the intent to them would be a derivation, not a citation. (5) beforeNumber = str., form only, from «str. 145–162» in 8.14; a decision, MEDIUM. (6) afterShortWords is empty: no online section states a rule for one-letter prepositions or conjunctions at line end (8.13 rule 2 concerns syllable division only); the print chapter on end-of-line division was not read. (7) beforeWord is empty and initialBinding = none: 7.3 and 8.1 give only the spaced form of initials and say nothing about line breaks. Decisions, not citations. Verified 2026-10-21."
80
+ }
81
+ ]
82
+ }
@@ -0,0 +1,88 @@
1
+ {
2
+ "locale": "hu",
3
+ "name": "Hungarian",
4
+ "quotes": {
5
+ "primary": {
6
+ "open": "„",
7
+ "close": "”",
8
+ "innerSpace": "none"
9
+ },
10
+ "secondary": {
11
+ "open": "»",
12
+ "close": "«",
13
+ "innerSpace": "none"
14
+ },
15
+ "elisionIdioms": [],
16
+ "elisionClitics": {
17
+ "before": [],
18
+ "after": []
19
+ }
20
+ },
21
+ "dash": {
22
+ "parenthetical": "en-spaced",
23
+ "range": "en-tight"
24
+ },
25
+ "ellipsis": {
26
+ "abbreviatedAfterTerminal": false
27
+ },
28
+ "hyphen": {
29
+ "prefixes": [],
30
+ "suffixes": [],
31
+ "compounds": []
32
+ },
33
+ "nbsp": {
34
+ "beforePunctuation": [],
35
+ "narrowBeforePunctuation": [],
36
+ "afterShortWords": [],
37
+ "abbreviations": ["a. m.", "i. sz.", "Kr. e.", "Kr. u.", "s. k.", "i. e."],
38
+ "beforeUnits": ["km", "kg", "kWh", "km/h", "°C"],
39
+ "beforeNumber": [],
40
+ "beforeWord": [],
41
+ "afterSymbols": [],
42
+ "initialBinding": "none"
43
+ },
44
+ "sources": [
45
+ {
46
+ "rule": "quotes",
47
+ "cite": "Magyar Tudományos Akadémia, A magyar helyesírás szabályai, 12. kiadás (AkH12), §240 j: «Az idézőjel lehet: „ ” (macskaköröm) vagy » « (lúdlábidézőjel).»; «Az első idézőjel az alapforma, a második általában idézeten belüli idézőjel»; §257: «Ha az idézőjelbe tett mondaton belül még egy idézőjelre van szükség, lúdlábidézőjelet használunk:» («Szemében »mesterségem« / Most is nagy szálka még.»); §275: «Hiányjel vagy aposztróf. Például: hál’ istennek!; ’95 (= 1995)»",
48
+ "url": "https://helyesiras.mta.hu/helyesiras/default/akh12",
49
+ "note": "Primary: U+201E open, U+201D close. AkH describes the marks by name and position only; U+201D (not U+201C) is read from the character in the online text, so the closing code point is MEDIUM-HIGH confidence. Secondary: U+00BB open, U+00AB close (»…«, the reverse of ro's «…»), for a quote inside a quote. innerSpace none is read from the printed examples (§257 «Szemében »mesterségem«», the marks attached to the enclosed words), not from a stated rule, so MEDIUM. AkH12 has no third level. Q-W: both pairs WIDE. elisionIdioms and elisionClitics are empty: the attested apostrophe uses (hál’, ’95) are neither a closed set nor the shape of elisionClitics. Verified 2026-10-21."
50
+ },
51
+ {
52
+ "rule": "dashes",
53
+ "cite": "Magyar Tudományos Akadémia, A magyar helyesírás szabályai, 12. kiadás (AkH12), §240 g: «A gondolatjel mindig szóközzel kapcsolódik az előtte álló szó utolsó betűjéhez (vagy az előtte álló számjegyhez)», «a gondolatjel után is szóközt kell tenni»; §240 l: «A nagykötőjel középső helyzetű, és hosszúsága megegyezik a gondolatjel hosszúságával.»; §252: «Holnap – ha esik, ha fúj – elmegyünk kirándulni.»",
54
+ "url": "https://helyesiras.mta.hu/helyesiras/default/akh12",
55
+ "note": "Supports dash.parenthetical = en-spaced: a space on both sides is mandatory. The length (U+2013) is read from the printed glyph and from §240 l, which gives the gondolatjel the same length as the nagykötőjel; AkH never names the code point, so MEDIUM. Verified 2026-10-21."
56
+ },
57
+ {
58
+ "rule": "ranges",
59
+ "cite": "Magyar Tudományos Akadémia, A magyar helyesírás szabályai, 12. kiadás (AkH12), §240 l: «A nagykötőjel tapad az előtte és az utána következő szóhoz vagy számjegyhez:»; §264 c: «Nagykötőjellel fűzzük egymáshoz az olyan szavakat, amelyek valamitől valameddig viszonyt érzékeltetnek, például: Budapest–Bécs, Rajna–Majna–Duna-csatorna; 1983–1984. évi, a 128–9. lapon, az 1–4. osztályban»; «Bonyolultabb esetekben a nagykötőjel nem tapadva is illeszkedhet a tagok közé, például: 2011. április 25. – 2012. május 13.»",
60
+ "url": "https://helyesiras.mta.hu/helyesiras/default/akh12",
61
+ "note": "Supports dash.range = en-tight: the tight en dash is the default and AkH ranks it; the spaced form is limited to complex members. Every spaced example has '.' or a letter on one side, so under ranges.md §3.2/§3.2a none is a range candidate and en-tight never respaces it. §240 l adds «Nem tapad a nagykötőjel, ha több szóból álló, bonyolultabb írásmódú szerkezeteket kapcsol össze» (mandatory spacing) where §264 c says the spaced form is optional; neither affects digit-flanked ranges. «a 128–9. lapon» (§264 c) has the shape (3,1) and is outside the candidates, so a typed `128-9.` keeps its hyphen. The ranges rule is off by default. Verified 2026-10-21."
62
+ },
63
+ {
64
+ "rule": "ellipsis",
65
+ "cite": "Magyar Tudományos Akadémia, A magyar helyesírás szabályai, 12. kiadás (AkH12), §240 b: «A kihagyást, a gondolat félbeszakítását jelölő három pont szóköz nélkül kapcsolódik az előtte álló szóhoz.»; §251 example: «…imádott / Jankóm…?»",
66
+ "url": "https://helyesiras.mta.hu/helyesiras/default/akh12",
67
+ "note": "abbreviatedAfterTerminal = false is the safe value, not a citation: AkH never prints a two-dot form after '?' or '!', and the only attested combination is an ellipsis followed by '?'. AkH has no explicit sentence on this, so MEDIUM. Verified 2026-10-21."
68
+ },
69
+ {
70
+ "rule": "hyphen",
71
+ "cite": "Magyar Tudományos Akadémia, A magyar helyesírás szabályai, 12. kiadás (AkH12), §270: «Ha a szó már tartalmaz kötőjelet vagy nagykötőjelet, ezeknél az írásjeleknél is elválaszthatjuk a szót» («tűzzel- / vassal, Dumas- / nak, a MÁV- / ot.»)",
72
+ "url": "https://helyesiras.mta.hu/helyesiras/default/akh12",
73
+ "note": "All three lists are empty. Breaking at a hyphen or nagykötőjel is explicitly permitted, and AkH gives no closed list of unbreakable forms. Verified 2026-10-21."
74
+ },
75
+ {
76
+ "rule": "nbsp",
77
+ "cite": "Magyar Tudományos Akadémia, A magyar helyesírás szabályai, 12. kiadás (AkH12), §240 c: «A kérdőjel előtt nincs szóköz, tehát tapad az őt megelőző szó utolsó betűjéhez vagy más írásjelhez»; §277 a: «A különírt szavakból álló szókapcsolatok rövidítése általában annyi (ponttal elkülönített) tagot tartalmaz» — «a. m. (= annyi mint), i. sz. (= időszámításunk szerint), Kr. e. (= Krisztus előtt), s. k. (= saját kezűleg)»; §284 table: «Kr. u.», «i. e.»; §275: «A paragrafus jele. Például: 4. §»",
78
+ "url": "https://helyesiras.mta.hu/helyesiras/default/akh12",
79
+ "note": "(1) beforePunctuation and narrowBeforePunctuation are empty: §240 c states that no space precedes «?», and the same is stated for the period and the comma; «!», «:» and «;» are not separately verified, and the empty list changes nothing. (2) abbreviations: the six forms are attested with an internal space; AkH says nothing about line breaks, so this rests on form evidence alone, the same footing as sk's «t. j.». (3) afterSymbols is empty: AkH writes '4. §' with the number first. (4) afterShortWords, beforeNumber and beforeWord are empty and initialBinding = none: no source; AkH12 has no rule on personal-name initials (Hungarian puts them after the surname). Decision, not citation. Verified 2026-10-21."
80
+ },
81
+ {
82
+ "rule": "nbsp",
83
+ "cite": "Magyar Tudományos Akadémia, A magyar helyesírás szabályai, 12. kiadás (AkH12), §279 c: «A mértékegységnek a jelét szóköz választja el az előtte levő számtól, például: 100 km, 1 kg, 10 kWh, 120 km/h, 220 W.»; §275: «Fokjel. Például: 45°; 23 °C», «A százalék jele. Például: 48%»",
84
+ "url": "https://helyesiras.mta.hu/helyesiras/default/akh12",
85
+ "note": "beforeUnits = km, kg, kWh, km/h, °C, each attested with a space in §279 c or §275. '%' is deliberately absent: AkH prints 48% without a space; geometric 45° is likewise unspaced. Single-letter symbols are excluded by decision, not by citation: §279 c itself writes «220 W» with a space, but `s` is a Hungarian conjunction and a list of single letters would be a derivation (as in sk and ro). 'Ft' is omitted: it is a currency abbreviation attested only by example (§290), so it ships unverified-empty. Verified 2026-10-21."
86
+ }
87
+ ]
88
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "spec": "1.11.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",
@@ -21,7 +21,11 @@
21
21
  "uk",
22
22
  "cs",
23
23
  "tr",
24
- "sk"
24
+ "sk",
25
+ "ro",
26
+ "hu",
27
+ "bg",
28
+ "hr"
25
29
  ],
26
30
  "aliases": {
27
31
  "en": "en-US",