@lokascript/i18n 2.7.2 → 2.8.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 (60) hide show
  1. package/README.md +1 -1
  2. package/dist/browser.cjs +649 -180
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.cts +2 -2
  5. package/dist/browser.d.ts +2 -2
  6. package/dist/browser.js +649 -180
  7. package/dist/browser.js.map +1 -1
  8. package/dist/dictionaries/index.cjs +370 -174
  9. package/dist/dictionaries/index.cjs.map +1 -1
  10. package/dist/dictionaries/index.js +370 -174
  11. package/dist/dictionaries/index.js.map +1 -1
  12. package/dist/index.cjs +649 -180
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +35 -35
  15. package/dist/index.d.ts +35 -35
  16. package/dist/index.js +649 -180
  17. package/dist/index.js.map +1 -1
  18. package/dist/lokascript-i18n.min.js +1 -1
  19. package/dist/lokascript-i18n.min.js.map +1 -1
  20. package/dist/lokascript-i18n.mjs +678 -175
  21. package/dist/lokascript-i18n.mjs.map +1 -1
  22. package/dist/plugins/vite.cjs +370 -174
  23. package/dist/plugins/vite.cjs.map +1 -1
  24. package/dist/plugins/vite.js +370 -174
  25. package/dist/plugins/vite.js.map +1 -1
  26. package/dist/plugins/webpack.cjs +370 -174
  27. package/dist/plugins/webpack.cjs.map +1 -1
  28. package/dist/plugins/webpack.js +370 -174
  29. package/dist/plugins/webpack.js.map +1 -1
  30. package/dist/{transformer-DM7iplCU.d.cts → transformer--W46PGaW.d.cts} +8 -0
  31. package/dist/{transformer-BA8YX9u1.d.ts → transformer-FU06J6kG.d.ts} +8 -0
  32. package/package.json +7 -9
  33. package/src/dictionaries/ar.ts +20 -15
  34. package/src/dictionaries/bn.ts +10 -5
  35. package/src/dictionaries/de.ts +20 -8
  36. package/src/dictionaries/es.ts +10 -5
  37. package/src/dictionaries/fr.ts +15 -6
  38. package/src/dictionaries/he.ts +4 -0
  39. package/src/dictionaries/hi.ts +3 -2
  40. package/src/dictionaries/id.ts +31 -16
  41. package/src/dictionaries/it.ts +11 -4
  42. package/src/dictionaries/ja.ts +23 -15
  43. package/src/dictionaries/ko.ts +10 -5
  44. package/src/dictionaries/ms.ts +4 -1
  45. package/src/dictionaries/pl.ts +12 -5
  46. package/src/dictionaries/pt.ts +10 -5
  47. package/src/dictionaries/qu.ts +30 -19
  48. package/src/dictionaries/ru.ts +5 -2
  49. package/src/dictionaries/sw.ts +29 -14
  50. package/src/dictionaries/th.ts +5 -2
  51. package/src/dictionaries/tl.ts +4 -1
  52. package/src/dictionaries/tr.ts +24 -16
  53. package/src/dictionaries/uk.ts +5 -2
  54. package/src/dictionaries/vi.ts +23 -17
  55. package/src/dictionaries/zh.ts +24 -9
  56. package/src/grammar/grammar.test.ts +152 -0
  57. package/src/grammar/profiles/index.ts +316 -1
  58. package/src/grammar/transformer.ts +64 -1
  59. package/src/new-languages.test.ts +9 -3
  60. package/src/positional-keyword-drift.test.ts +6 -19
@@ -708,17 +708,19 @@ const es$1 = {
708
708
  mouseup: 'ratónarriba',
709
709
  mouseenter: 'ratónentrar',
710
710
  mouseleave: 'ratónsalir',
711
- mouseover: 'ratónencima',
712
- mouseout: 'ratónfuera',
711
+ // V3 Batch 2: fused forms split to the S5b/tokenizer-known forms (the fused
712
+ // spellings captured event=expression:undefined — broken listeners)
713
+ mouseover: 'ratón encima',
714
+ mouseout: 'ratón fuera',
713
715
  mousemove: 'ratónmover',
714
- keydown: 'teclaabajo',
715
- keyup: 'teclaarriba',
716
+ keydown: 'tecla abajo',
717
+ keyup: 'tecla arriba',
716
718
  keypress: 'teclapresar',
717
719
  focus: 'enfocar',
718
720
  blur: 'desenfocar',
719
721
  change: 'cambiar',
720
722
  input: 'entrada',
721
- submit: 'enviar',
723
+ submit: 'envío', // Batch 3: enviar is the send verb — on-submit listener captured event "send"
722
724
  reset: 'reiniciar',
723
725
  load: 'cargar',
724
726
  unload: 'descargar',
@@ -798,6 +800,9 @@ const es$1 = {
798
800
  properties: 'propiedades',
799
801
  },
800
802
  expressions: {
803
+ characters: 'caracteres',
804
+ inclusive: 'inclusivo',
805
+ exclusive: 'exclusivo',
801
806
  first: 'primero',
802
807
  last: 'último',
803
808
  next: 'siguiente',
@@ -908,7 +913,7 @@ const ja$1 = {
908
913
  clear: 'クリア',
909
914
  close: '閉じる',
910
915
  open: '開く',
911
- select: '選択',
916
+ select: '選ぶ', // Batch 3: 選択 is the pick keyword — bare select rendered with it parses null
912
917
  clone: '複製',
913
918
  prepend: '先頭追加',
914
919
  socket: 'ソケット',
@@ -933,17 +938,22 @@ const ja$1 = {
933
938
  },
934
939
  events: {
935
940
  click: 'クリック',
936
- dblclick: 'ダブルクリック',
941
+ dblclick: 'dblclick',
937
942
  mousedown: 'マウス押下',
938
943
  mouseup: 'マウス解放',
939
- mouseenter: 'マウス入る',
940
- mouseleave: 'マウス離れる',
941
- mouseover: 'マウス上',
942
- mouseout: 'マウス外',
943
- mousemove: 'マウス移動',
944
- keydown: 'キー押下',
945
- keyup: 'キー解放',
946
- keypress: 'キー押す',
944
+ // V3c burn-down (2026-07-14): English passthrough — no native form round-trips
945
+ // on the parse side (probe: split forms shatter, fused forms capture verbatim).
946
+ mouseenter: 'mouseenter',
947
+ mouseleave: 'mouseleave',
948
+ // V3 Batch 2: kanji compounds realigned to the S5b/tokenizer katakana
949
+ // loanwords (the standard JS event terms; the compounds captured
950
+ // event=expression:undefined — broken listeners)
951
+ mouseover: 'マウスオーバー',
952
+ mouseout: 'マウスアウト',
953
+ mousemove: 'mousemove',
954
+ keydown: 'キーダウン',
955
+ keyup: 'キーアップ',
956
+ keypress: 'キープレス',
947
957
  focus: 'フォーカス',
948
958
  blur: 'ぼかし',
949
959
  change: '変更',
@@ -951,13 +961,13 @@ const ja$1 = {
951
961
  submit: '送信',
952
962
  reset: 'リセット',
953
963
  load: '読み込み',
954
- unload: '読み込み解除',
964
+ unload: 'unload',
955
965
  resize: 'サイズ変更',
956
966
  scroll: 'スクロール',
957
- touchstart: 'タッチ開始',
958
- touchend: 'タッチ終了',
959
- touchmove: 'タッチ移動',
960
- touchcancel: 'タッチキャンセル',
967
+ touchstart: 'touchstart',
968
+ touchend: 'touchend',
969
+ touchmove: 'touchmove',
970
+ touchcancel: 'touchcancel',
961
971
  },
962
972
  logical: {
963
973
  when: '時',
@@ -1029,6 +1039,9 @@ const ja$1 = {
1029
1039
  properties: 'プロパティ群',
1030
1040
  },
1031
1041
  expressions: {
1042
+ characters: '文字',
1043
+ inclusive: '含む',
1044
+ exclusive: '除く',
1032
1045
  first: '最初',
1033
1046
  last: '最後',
1034
1047
  next: '次',
@@ -1169,15 +1182,21 @@ const fr$1 = {
1169
1182
  events: {
1170
1183
  click: 'clic',
1171
1184
  dblclick: 'doubleclic',
1172
- mousedown: 'sourisappuyée',
1173
- mouseup: 'sourisrelâchée',
1185
+ // mousedown/mouseup: English passthrough — no native form round-trips on the
1186
+ // parse side (V3c probe 2026-07-14: fused forms captured verbatim, split forms
1187
+ // shattered to their first word — live broken listeners in repeat-until-event).
1188
+ // The Batch-2 id-keyup precedent; revisit if S5b + tokenizer gain native entries.
1189
+ mousedown: 'mousedown',
1190
+ mouseup: 'mouseup',
1174
1191
  mouseenter: 'sourisentrer',
1175
1192
  mouseleave: 'sourissortir',
1176
- mouseover: 'sourissur',
1177
- mouseout: 'sourisdehors',
1193
+ // V3 Batch 2: fused forms split to the S5b/tokenizer-known forms (the fused
1194
+ // spellings captured event=expression:undefined — broken listeners)
1195
+ mouseover: 'souris dessus',
1196
+ mouseout: 'souris dehors',
1178
1197
  mousemove: 'sourisbouger',
1179
- keydown: 'toucheappuyée',
1180
- keyup: 'toucherelâchée',
1198
+ keydown: 'touche bas',
1199
+ keyup: 'touche haut',
1181
1200
  keypress: 'touchepressée',
1182
1201
  focus: 'focus',
1183
1202
  blur: 'flou',
@@ -1263,6 +1282,9 @@ const fr$1 = {
1263
1282
  properties: 'propriétés',
1264
1283
  },
1265
1284
  expressions: {
1285
+ characters: 'caractères',
1286
+ inclusive: 'inclusif',
1287
+ exclusive: 'exclusif',
1266
1288
  first: 'premier',
1267
1289
  last: 'dernier',
1268
1290
  next: 'suivant',
@@ -1377,7 +1399,7 @@ const de$1 = {
1377
1399
  clear: 'bereinigen', // löschen is a remove alternative in the profile
1378
1400
  close: 'schließen',
1379
1401
  open: 'öffnen',
1380
- select: 'auswählen',
1402
+ select: 'markieren', // Batch 3: auswählen is the pick keyword — bare select rendered with it parses null
1381
1403
  clone: 'klonen',
1382
1404
  prepend: 'voranstellen',
1383
1405
  focus: 'fokussieren',
@@ -1403,18 +1425,27 @@ const de$1 = {
1403
1425
  events: {
1404
1426
  click: 'klick',
1405
1427
  dblclick: 'doppelklick',
1406
- mousedown: 'mausunten',
1407
- mouseup: 'mausoben',
1428
+ // mousedown/mouseup: English passthrough — no native form round-trips on the
1429
+ // parse side (V3c probe 2026-07-14: fused forms captured verbatim, split forms
1430
+ // shattered to their first word — live broken listeners in repeat-until-event).
1431
+ // The Batch-2 id-keyup precedent; revisit if S5b + tokenizer gain native entries.
1432
+ mousedown: 'mousedown',
1433
+ mouseup: 'mouseup',
1408
1434
  mouseenter: 'mauseintreten',
1409
1435
  mouseleave: 'mausverlassen',
1410
- mouseover: 'mausüber',
1411
- mouseout: 'maushinaus',
1436
+ // V3 Batch 2: fused/dead event words realigned to the de tokenizer's
1437
+ // registered forms (S5b's `maus über`/`taste runter` are aspirational — they
1438
+ // do not tokenize; `mausüber` etc. captured event=expression:undefined).
1439
+ mouseover: 'maus drüber',
1440
+ mouseout: 'maus weg',
1412
1441
  mousemove: 'mausbewegen',
1413
- keydown: 'tasteunten',
1414
- keyup: 'tasteoben',
1442
+ keydown: 'taste unten',
1443
+ keyup: 'taste oben',
1415
1444
  keypress: 'tastedrücken',
1416
1445
  focus: 'fokus',
1417
- blur: 'unscharf',
1446
+ // matches commands.blur (which shadows this at render time) and the live
1447
+ // corpus render; `unscharf` was dead vocab that captured no event
1448
+ blur: 'defokussieren',
1418
1449
  change: 'ändern',
1419
1450
  input: 'eingabe',
1420
1451
  submit: 'absenden',
@@ -1497,6 +1528,9 @@ const de$1 = {
1497
1528
  properties: 'eigenschaften',
1498
1529
  },
1499
1530
  expressions: {
1531
+ characters: 'Zeichen',
1532
+ inclusive: 'inklusiv',
1533
+ exclusive: 'exklusiv',
1500
1534
  first: 'erste',
1501
1535
  last: 'letzte',
1502
1536
  next: 'nächste',
@@ -1617,7 +1651,7 @@ const ar$1 = {
1617
1651
  clear: 'نظّف', // امسح is a remove alternative in the profile
1618
1652
  close: 'أغلق',
1619
1653
  open: 'افتح',
1620
- select: 'اختر',
1654
+ select: 'ظلل', // Batch 3: اختر is the pick keyword — bare select rendered with it parses null
1621
1655
  clone: 'استنسخ',
1622
1656
  prepend: 'سبق',
1623
1657
  socket: 'مقبس',
@@ -1642,31 +1676,33 @@ const ar$1 = {
1642
1676
  },
1643
1677
  events: {
1644
1678
  click: 'نقر',
1645
- dblclick: 'نقر مزدوج',
1646
- mousedown: 'فأرة أسفل',
1647
- mouseup: 'فأرة أعلى',
1648
- mouseenter: 'فأرة دخول',
1649
- mouseleave: 'فأرة خروج',
1679
+ // V3c burn-down (2026-07-14): English passthrough — no native form round-trips
1680
+ // on the parse side (probe: split forms shatter, fused forms capture verbatim).
1681
+ dblclick: 'dblclick',
1682
+ mousedown: 'mousedown',
1683
+ mouseup: 'mouseup',
1684
+ mouseenter: 'mouseenter',
1685
+ mouseleave: 'mouseleave',
1650
1686
  mouseover: 'فأرة فوق',
1651
- mouseout: 'فأرة خارج',
1652
- mousemove: 'فأرة تحرك',
1687
+ mouseout: 'mouseout',
1688
+ mousemove: 'mousemove',
1653
1689
  keydown: 'مفتاح أسفل',
1654
1690
  keyup: 'مفتاح أعلى',
1655
- keypress: 'مفتاح ضغط',
1691
+ keypress: 'keypress',
1656
1692
  focus: 'تركيز',
1657
1693
  blur: 'ضبابية',
1658
1694
  change: 'تغيير',
1659
1695
  input: 'إدخال',
1660
1696
  submit: 'إرسال',
1661
- reset: 'إعادة تعيين',
1697
+ reset: 'reset',
1662
1698
  load: 'تحميل',
1663
- unload: 'إلغاء تحميل',
1699
+ unload: 'unload',
1664
1700
  resize: 'تغيير حجم',
1665
1701
  scroll: 'تمرير',
1666
- touchstart: 'بداية لمس',
1667
- touchend: 'نهاية لمس',
1668
- touchmove: 'تحرك لمس',
1669
- touchcancel: 'إلغاء لمس',
1702
+ touchstart: 'touchstart',
1703
+ touchend: 'touchend',
1704
+ touchmove: 'touchmove',
1705
+ touchcancel: 'touchcancel',
1670
1706
  },
1671
1707
  logical: {
1672
1708
  when: 'عندما',
@@ -1738,6 +1774,9 @@ const ar$1 = {
1738
1774
  properties: 'خصائص',
1739
1775
  },
1740
1776
  expressions: {
1777
+ characters: 'حروف',
1778
+ inclusive: 'شامل',
1779
+ exclusive: 'حصري',
1741
1780
  first: 'أول',
1742
1781
  last: 'آخر',
1743
1782
  next: 'التالي',
@@ -1856,7 +1895,7 @@ const ko$1 = {
1856
1895
  clear: '지우기',
1857
1896
  close: '닫기',
1858
1897
  open: '열기',
1859
- select: '선택',
1898
+ select: '고르기', // Batch 3: 선택 is the pick keyword — bare select rendered with it parses null
1860
1899
  clone: '복제',
1861
1900
  prepend: '앞에추가',
1862
1901
  socket: '소켓',
@@ -1897,14 +1936,16 @@ const ko$1 = {
1897
1936
  change: '변경',
1898
1937
  input: '입력',
1899
1938
  submit: '제출',
1900
- reset: '리셋',
1939
+ reset: '재설정', // Batch 3: 리셋 captured on.event as expression (broken listener) — profile word round-trips
1901
1940
  load: '로드',
1902
- unload: '언로드',
1941
+ unload: 'unload',
1903
1942
  resize: '리사이즈',
1904
1943
  scroll: '스크롤',
1905
- touchstart: '터치시작',
1944
+ // V3c burn-down (2026-07-14): English passthrough — no native form round-trips
1945
+ // on the parse side (probe: split forms shatter, fused forms capture verbatim).
1946
+ touchstart: 'touchstart',
1906
1947
  touchend: '터치종료',
1907
- touchmove: '터치이동',
1948
+ touchmove: 'touchmove',
1908
1949
  touchcancel: '터치취소',
1909
1950
  },
1910
1951
  logical: {
@@ -1977,6 +2018,9 @@ const ko$1 = {
1977
2018
  properties: '프로퍼티들',
1978
2019
  },
1979
2020
  expressions: {
2021
+ characters: '문자',
2022
+ inclusive: '포함',
2023
+ exclusive: '제외',
1980
2024
  first: '첫번째',
1981
2025
  last: '마지막',
1982
2026
  next: '다음',
@@ -2121,16 +2165,26 @@ const zh$1 = {
2121
2165
  events: {
2122
2166
  click: '点击',
2123
2167
  dblclick: '双击',
2124
- mousedown: '鼠标按下',
2125
- mouseup: '鼠标抬起',
2126
- mouseenter: '鼠标进入',
2127
- mouseleave: '鼠标离开',
2128
- mouseover: '鼠标悬停',
2168
+ // mousedown/mouseup: English passthrough — no native form round-trips on the
2169
+ // parse side (V3c probe 2026-07-14: fused forms captured verbatim, split forms
2170
+ // shattered to their first word — live broken listeners in repeat-until-event).
2171
+ // The Batch-2 id-keyup precedent; revisit if S5b + tokenizer gain native entries.
2172
+ mousedown: 'mousedown',
2173
+ mouseup: 'mouseup',
2174
+ mouseenter: 'mouseenter',
2175
+ mouseleave: 'mouseleave',
2176
+ // V3 Batch 2: 鼠标悬停 captured no event (expression:undefined) — realigned
2177
+ // to the tokenizer-registered 鼠标移入 (also S5b-listed). 按键抬起
2178
+ // prefix-shattered to 按键 and captured `keydown` (a keyup handler firing
2179
+ // on keydown) → 松键, the S5b/tokenizer form. keydown keeps 按键按下
2180
+ // (captures keydown via the registered 按键 prefix; S5b-aliased) — fixing
2181
+ // it to bare 按键 would collide with keypress below.
2182
+ mouseover: '鼠标移入',
2129
2183
  mouseout: '鼠标移出',
2130
2184
  mousemove: '鼠标移动',
2131
2185
  keydown: '按键按下',
2132
- keyup: '按键抬起',
2133
- keypress: '按键',
2186
+ keyup: '松键',
2187
+ keypress: 'keypress',
2134
2188
  focus: '聚焦',
2135
2189
  blur: '失焦',
2136
2190
  change: '改变',
@@ -2142,9 +2196,11 @@ const zh$1 = {
2142
2196
  resize: '调整大小',
2143
2197
  scroll: '滚动',
2144
2198
  touchstart: '触摸开始',
2145
- touchend: '触摸结束',
2199
+ touchend: 'touchend',
2146
2200
  touchmove: '触摸移动',
2147
- touchcancel: '触摸取消',
2201
+ // V3c burn-down (2026-07-14): English passthrough — no native form round-trips
2202
+ // on the parse side (probe: split forms shatter, fused forms capture verbatim).
2203
+ touchcancel: 'touchcancel',
2148
2204
  },
2149
2205
  logical: {
2150
2206
  when: '当',
@@ -2216,6 +2272,9 @@ const zh$1 = {
2216
2272
  properties: '特性集',
2217
2273
  },
2218
2274
  expressions: {
2275
+ characters: '字符',
2276
+ inclusive: '包含',
2277
+ exclusive: '排除',
2219
2278
  first: '第一个',
2220
2279
  last: '最后一个',
2221
2280
  next: '下一个',
@@ -2333,7 +2392,7 @@ const tr$1 = {
2333
2392
  clear: 'temizle',
2334
2393
  close: 'kapat',
2335
2394
  open: 'aç',
2336
- select: 'seç',
2395
+ select: 'vurgula', // Batch 3: seç is the pick keyword — bare select rendered with it parses null
2337
2396
  clone: 'klonla',
2338
2397
  prepend: 'başınaekle',
2339
2398
  socket: 'soket',
@@ -2358,34 +2417,39 @@ const tr$1 = {
2358
2417
  },
2359
2418
  events: {
2360
2419
  click: 'tıklama',
2361
- dblclick: 'çift_tıklama',
2420
+ dblclick: 'dblclick',
2362
2421
  // Fused (no `_`): the semantic tokenizer splits on `_`, breaking event
2363
2422
  // recognition (mousedown → "bas" / mouseup mis-captured). Mirrors the #535
2364
2423
  // ru/uk fuse + #510 resize fix.
2365
2424
  mousedown: 'farebas',
2366
2425
  mouseup: 'farebırak',
2367
- mouseenter: 'fare_gir',
2368
- mouseleave: 'fare_çık',
2369
- mouseover: 'fare_üstü',
2370
- mouseout: 'fare_dışı',
2371
- mousemove: 'fare_hareket',
2372
- keydown: 'tuş_bas',
2373
- keyup: 'tuş_bırak',
2374
- keypress: 'tuş_basım',
2426
+ mouseenter: 'mouseenter',
2427
+ mouseleave: 'mouseleave',
2428
+ // V3 Batch 2: same `_`-split class (tuş_bas captured verbatim `bas` — a
2429
+ // listener for a DOM event named "bas") — realigned to the S5b/tokenizer
2430
+ // fused forms.
2431
+ mouseover: 'fareiçinde',
2432
+ mouseout: 'faredışında',
2433
+ mousemove: 'mousemove',
2434
+ keydown: 'tuşbasma',
2435
+ keyup: 'tuşbırakma',
2436
+ // V3c burn-down (2026-07-14): English passthrough — no native form round-trips
2437
+ // on the parse side (probe: split forms shatter, fused forms capture verbatim).
2438
+ keypress: 'keypress',
2375
2439
  focus: 'odak',
2376
2440
  blur: 'bulanık',
2377
2441
  change: 'değişim',
2378
2442
  input: 'giriş',
2379
- submit: 'gönder',
2443
+ submit: 'gönderme', // Batch 3: gönder is the send verb — on-submit listener captured event "send"
2380
2444
  reset: 'sıfırla',
2381
2445
  load: 'yükle',
2382
- unload: 'yükle_kaldır',
2446
+ unload: 'unload',
2383
2447
  resize: 'boyutlandırma',
2384
2448
  scroll: 'kaydır',
2385
- touchstart: 'dokunma_başla',
2386
- touchend: 'dokunma_bitir',
2387
- touchmove: 'dokunma_hareket',
2388
- touchcancel: 'dokunma_iptal',
2449
+ touchstart: 'touchstart',
2450
+ touchend: 'touchend',
2451
+ touchmove: 'touchmove',
2452
+ touchcancel: 'touchcancel',
2389
2453
  },
2390
2454
  logical: {
2391
2455
  when: 'iken',
@@ -2456,6 +2520,9 @@ const tr$1 = {
2456
2520
  properties: 'özellikler',
2457
2521
  },
2458
2522
  expressions: {
2523
+ characters: 'karakterler',
2524
+ inclusive: 'dahil',
2525
+ exclusive: 'hariç',
2459
2526
  first: 'ilk',
2460
2527
  // 'son' is also the END keyword emission (block terminator), and the
2461
2528
  // parser's end-recognizers match it by value — so a positional `last`
@@ -2579,9 +2646,9 @@ const id$1 = {
2579
2646
  install: 'pasang',
2580
2647
  breakpoint: 'titik-henti',
2581
2648
  clear: 'bersihkan', // hapus is the remove PRIMARY in the profile
2582
- close: 'tutup',
2649
+ close: 'tutupkan', // Batch 3: tutup is the hide keyword — rendered close parsed as action=hide
2583
2650
  open: 'buka',
2584
- select: 'pilih',
2651
+ select: 'tandai', // Batch 3: pilih is the pick keyword — bare select rendered with it parses null
2585
2652
  clone: 'klon',
2586
2653
  prepend: 'awali',
2587
2654
  },
@@ -2605,17 +2672,27 @@ const id$1 = {
2605
2672
  },
2606
2673
  events: {
2607
2674
  click: 'klik',
2608
- dblclick: 'klik_ganda',
2609
- mousedown: 'tekan_mouse',
2610
- mouseup: 'lepas_mouse',
2611
- mouseenter: 'mouse_masuk',
2612
- mouseleave: 'mouse_keluar',
2613
- mouseover: 'mouse_atas',
2614
- mouseout: 'mouse_luar',
2615
- mousemove: 'gerak_mouse',
2675
+ dblclick: 'dblclick',
2676
+ // mousedown/mouseup: English passthrough — no native form round-trips on the
2677
+ // parse side (V3c probe 2026-07-14: fused forms captured verbatim, split forms
2678
+ // shattered to their first word — live broken listeners in repeat-until-event).
2679
+ // The Batch-2 id-keyup precedent; revisit if S5b + tokenizer gain native entries.
2680
+ mousedown: 'mousedown',
2681
+ mouseup: 'mouseup',
2682
+ mouseenter: 'mouseenter',
2683
+ mouseleave: 'mouseleave',
2684
+ // V3 Batch 2: mouse_atas/mouse_luar captured no event (the S5b forms
2685
+ // `mouse masuk`/`mouse keluar` are aspirational — they don't tokenize
2686
+ // either) — realigned to the tokenizer-registered natives. keydown keeps
2687
+ // tekan_tombol (captures keydown via the registered `tekan`; S5b-aliased).
2688
+ // keyup has NO parseable native (lepas is unregistered) → English
2689
+ // passthrough, matching the blur/reset precedent below.
2690
+ mouseover: 'arahkan',
2691
+ mouseout: 'tinggalkan',
2692
+ mousemove: 'mousemove',
2616
2693
  keydown: 'tekan_tombol',
2617
- keyup: 'lepas_tombol',
2618
- keypress: 'pencet_tombol',
2694
+ keyup: 'keyup',
2695
+ keypress: 'keypress',
2619
2696
  focus: 'fokus',
2620
2697
  blur: 'blur',
2621
2698
  change: 'ubah',
@@ -2626,10 +2703,12 @@ const id$1 = {
2626
2703
  unload: 'bongkar',
2627
2704
  resize: 'ubah_ukuran',
2628
2705
  scroll: 'gulir',
2629
- touchstart: 'mulai_sentuh',
2630
- touchend: 'akhir_sentuh',
2631
- touchmove: 'gerak_sentuh',
2632
- touchcancel: 'batal_sentuh',
2706
+ touchstart: 'touchstart',
2707
+ touchend: 'touchend',
2708
+ touchmove: 'touchmove',
2709
+ // V3c burn-down (2026-07-14): English passthrough — no native form round-trips
2710
+ // on the parse side (probe: split forms shatter, fused forms capture verbatim).
2711
+ touchcancel: 'touchcancel',
2633
2712
  },
2634
2713
  logical: {
2635
2714
  when: 'ketika',
@@ -2700,6 +2779,9 @@ const id$1 = {
2700
2779
  properties: 'properti_properti',
2701
2780
  },
2702
2781
  expressions: {
2782
+ characters: 'karakter',
2783
+ inclusive: 'inklusif',
2784
+ exclusive: 'eksklusif',
2703
2785
  first: 'pertama',
2704
2786
  last: 'terakhir',
2705
2787
  next: 'berikutnya',
@@ -2825,8 +2907,8 @@ const qu$1 = {
2825
2907
  breakpoint: 'sayachinay',
2826
2908
  clear: 'pichay',
2827
2909
  close: 'wichqay',
2828
- open: 'kichay',
2829
- select: 'akllay',
2910
+ open: 'paskay', // Batch 3: kichay is the trigger keyword — rendered open parsed null
2911
+ select: 'marcay', // Batch 3: akllay is the pick keyword — bare select rendered with it parses null
2830
2912
  clone: 'kikinchay',
2831
2913
  prepend: 'ñawpachiy',
2832
2914
  socket: 'tinkina',
@@ -2851,33 +2933,39 @@ const qu$1 = {
2851
2933
  },
2852
2934
  events: {
2853
2935
  click: 'ñitiy',
2854
- dblclick: 'iskay_ñitiy',
2936
+ dblclick: 'dblclick',
2855
2937
  // Fused (no `_`): the semantic tokenizer splits on `_` — and `rat_ñitiy` then
2856
2938
  // mis-captured as the `click` event (ñitiy→click). Mirrors the #535 ru/uk fuse.
2857
2939
  mousedown: 'ratñitiy',
2858
2940
  mouseup: 'rathuqariy',
2859
- mouseenter: 'rat_yaykuy',
2860
- mouseleave: 'rat_lluqsiy',
2861
- mouseover: 'rat_hawapi',
2862
- mouseout: 'rat_hawamanta',
2863
- mousemove: 'rat_kuyuy',
2864
- keydown: 'yupana_ñitiy',
2865
- keyup: 'yupana_huqariy',
2866
- keypress: 'yupana_ñitana',
2941
+ mouseenter: 'mouseenter',
2942
+ mouseleave: 'mouseleave',
2943
+ mouseover: 'mouseover',
2944
+ mouseout: 'mouseout',
2945
+ mousemove: 'mousemove',
2946
+ // V3 Batch 2: `yupana_ñitiy` split at `_` and mis-captured as the `click`
2947
+ // event (ñitiy→click, the same mechanism as the mousedown fuse above);
2948
+ // `yupana_huqariy` captured verbatim `huqariy`. Realigned to the
2949
+ // S5b/tokenizer forms.
2950
+ keydown: 'llave uray',
2951
+ keyup: 'llave hawa',
2952
+ keypress: 'keypress',
2867
2953
  focus: 'qhaway',
2868
2954
  blur: 'paqariy',
2869
- change: 'tikray',
2955
+ change: 'kambiay', // Batch 3: tikray is the toggle verb — on-change listener captured event "toggle"
2870
2956
  input: 'yaykuchiy',
2871
- submit: 'kachay',
2872
- reset: 'qallariy',
2957
+ submit: 'apaykachay', // Batch 3: kachay is the send verb — on-submit captured "send" in one corpus slot
2958
+ reset: 'musuqchay', // Batch 3: qallariy is the init/start word — on-reset listener captured event "default"
2873
2959
  load: 'apakuy',
2874
- unload: 'urmay',
2960
+ unload: 'unload',
2875
2961
  resize: 'hatun_kay',
2876
2962
  scroll: 'kunray',
2877
- touchstart: 'llamiy_qallay',
2878
- touchend: 'llamiy_tukuy',
2879
- touchmove: 'llamiy_kuyuy',
2880
- touchcancel: 'llamiy_hark_ay',
2963
+ // V3c burn-down (2026-07-14): English passthrough — no native form round-trips
2964
+ // on the parse side (probe: split forms shatter, fused forms capture verbatim).
2965
+ touchstart: 'touchstart',
2966
+ touchend: 'touchend',
2967
+ touchmove: 'touchmove',
2968
+ touchcancel: 'touchcancel',
2881
2969
  },
2882
2970
  logical: {
2883
2971
  when: 'maykama',
@@ -2952,6 +3040,11 @@ const qu$1 = {
2952
3040
  properties: 'kanaykuna',
2953
3041
  },
2954
3042
  expressions: {
3043
+ // Pick unit word (arc 3): native-first per qu convention — `sanampa`
3044
+ // ("sign/letter", the standard term for alphabet characters) over a
3045
+ // loanword. Paired with the QUECHUA_EXTRAS entry in the semantic
3046
+ // tokenizer; both surfaces must match verbatim.
3047
+ characters: 'sanampa',
2955
3048
  first: 'ñawpaq',
2956
3049
  last: 'qhipa',
2957
3050
  next: 'qhipantin',
@@ -3041,7 +3134,10 @@ const sw$1 = {
3041
3134
  wait: 'ngoja',
3042
3135
  fetch: 'leta',
3043
3136
  call: 'ita',
3044
- return: 'rudi',
3137
+ // `rudisha` (transitive, "give back a value"), not `rudi` ("go back"): the
3138
+ // semantic profile reads `rudisha`/`rejea`, so emitting `rudi` produced sw code
3139
+ // the parser could not read back. Kept clear of `rudia` (= `repeat`).
3140
+ return: 'rudisha',
3045
3141
  make: 'tengeneza', // auditor: dict emitted a word the profile reads differently
3046
3142
  log: 'andika',
3047
3143
  throw: 'tupa',
@@ -3054,7 +3150,7 @@ const sw$1 = {
3054
3150
  go: 'nenda',
3055
3151
  pushUrl: 'sukumaUrl',
3056
3152
  replaceUrl: 'badilishaUrl',
3057
- copy: 'nakili',
3153
+ copy: 'nakala', // Batch 3: nakili is the clone keyword — rendered copy parsed as action=clone
3058
3154
  pick: 'chagua',
3059
3155
  beep: 'lia',
3060
3156
  js: 'js',
@@ -3070,7 +3166,7 @@ const sw$1 = {
3070
3166
  clear: 'safisha', // futa is a remove alternative in the profile
3071
3167
  close: 'funga',
3072
3168
  open: 'fungua',
3073
- select: 'chagua',
3169
+ select: 'alama', // Batch 3: chagua is the pick keyword — bare select rendered with it parses null
3074
3170
  clone: 'nakili',
3075
3171
  prepend: 'tanguliza',
3076
3172
  focus: 'lenga',
@@ -3103,31 +3199,42 @@ const sw$1 = {
3103
3199
  },
3104
3200
  events: {
3105
3201
  click: 'bonyeza',
3106
- dblclick: 'bonyeza_mara_mbili',
3202
+ // V3c burn-down (2026-07-14): English passthrough — no native form round-trips
3203
+ // on the parse side (probe: split forms shatter, fused forms capture verbatim).
3204
+ dblclick: 'dblclick',
3107
3205
  mousedown: 'panya_shuka',
3108
3206
  mouseup: 'panya_juu',
3109
3207
  mouseenter: 'panya_ingia',
3110
3208
  mouseleave: 'panya_toka',
3111
- mouseover: 'panya_juu',
3209
+ // V3 Batch 2: panya_juu is mouseup's form (shared here by mistake — the
3210
+ // tokenizer deliberately maps it to mouseup for the corpus row, so a
3211
+ // mouseover handler captured `mouseup`) — realigned to the
3212
+ // tokenizer-registered `sogeza juu`.
3213
+ mouseover: 'sogeza juu',
3112
3214
  mouseout: 'panya_nje',
3113
3215
  mousemove: 'panya_sogea',
3114
3216
  keydown: 'kitufe_shuka',
3115
3217
  keyup: 'kitufe_juu',
3116
- keypress: 'kitufe_bonyeza',
3117
- focus: 'zingatia',
3118
- blur: 'poteza_macho',
3119
- change: 'badilisha',
3218
+ keypress: 'keypress',
3219
+ // V3 Batch 2: zingatia captured no event → lenga (S5b/tokenizer form);
3220
+ // poteza_macho was a null parse → `blur` (the S5b identity form — matches
3221
+ // the live corpus render `kwenye blur`); badilisha is the toggle VERB
3222
+ // homonym (a change handler captured event=`toggle` — a listener for a DOM
3223
+ // event named "toggle") → kubadilisha (S5b/tokenizer form).
3224
+ focus: 'lenga',
3225
+ blur: 'blur',
3226
+ change: 'kubadilisha',
3120
3227
  input: 'ingizo',
3121
3228
  submit: 'wasilisha',
3122
- reset: 'weka_upya',
3229
+ reset: 'reset',
3123
3230
  load: 'pakia',
3124
3231
  unload: 'shuka',
3125
3232
  resize: 'badilisha_ukubwa',
3126
3233
  scroll: 'sogeza',
3127
- touchstart: 'gusa_anza',
3128
- touchend: 'gusa_mwisho',
3129
- touchmove: 'gusa_sogea',
3130
- touchcancel: 'gusa_ghairi',
3234
+ touchstart: 'touchstart',
3235
+ touchend: 'touchend',
3236
+ touchmove: 'touchmove',
3237
+ touchcancel: 'touchcancel',
3131
3238
  },
3132
3239
  logical: {
3133
3240
  when: 'wakati',
@@ -3198,6 +3305,7 @@ const sw$1 = {
3198
3305
  properties: 'mali',
3199
3306
  },
3200
3307
  expressions: {
3308
+ characters: 'herufi',
3201
3309
  first: 'kwanza',
3202
3310
  // 'mwisho' is the END keyword (block terminator) — the tokenizer's keyword
3203
3311
  // map is last-wins, so a positional `last` emission of 'mwisho' reads as
@@ -3357,18 +3465,20 @@ const pt$1 = {
3357
3465
  mouseup: 'mouseCima',
3358
3466
  mouseenter: 'mouseEntrar',
3359
3467
  mouseleave: 'mouseSair',
3360
- mouseover: 'mouseSobre',
3361
- mouseout: 'mouseFora',
3468
+ // V3 Batch 2: camelCase forms split to the S5b/tokenizer-known forms (the
3469
+ // fused spellings captured event=expression:undefined — broken listeners)
3470
+ mouseover: 'mouse sobre',
3471
+ mouseout: 'mouse fora',
3362
3472
  mousemove: 'mouseMover',
3363
- keydown: 'teclaBaixo',
3364
- keyup: 'teclaCima',
3473
+ keydown: 'tecla baixo',
3474
+ keyup: 'tecla cima',
3365
3475
  keypress: 'teclaPressionar',
3366
3476
  focus: 'foco',
3367
3477
  blur: 'desfoque',
3368
3478
  change: 'mudança',
3369
3479
  input: 'entrada',
3370
3480
  submit: 'envio',
3371
- reset: 'reiniciar',
3481
+ reset: 'redefinir', // Batch 3: reiniciar captured on.event as expression (broken listener) — profile word round-trips
3372
3482
  load: 'carregar',
3373
3483
  unload: 'descarregar',
3374
3484
  resize: 'redimensionar',
@@ -3447,6 +3557,9 @@ const pt$1 = {
3447
3557
  properties: 'propriedades',
3448
3558
  },
3449
3559
  expressions: {
3560
+ characters: 'caracteres',
3561
+ inclusive: 'inclusivo',
3562
+ exclusive: 'exclusivo',
3450
3563
  first: 'primeiro',
3451
3564
  last: 'último',
3452
3565
  next: 'próximo',
@@ -3587,8 +3700,12 @@ const it$1 = {
3587
3700
  events: {
3588
3701
  click: 'clic',
3589
3702
  dblclick: 'doppioclic',
3590
- mousedown: 'mousegiù',
3591
- mouseup: 'mousesu',
3703
+ // mousedown/mouseup: English passthrough — no native form round-trips on the
3704
+ // parse side (V3c probe 2026-07-14: fused forms captured verbatim, split forms
3705
+ // shattered to their first word — live broken listeners in repeat-until-event).
3706
+ // The Batch-2 id-keyup precedent; revisit if S5b + tokenizer gain native entries.
3707
+ mousedown: 'mousedown',
3708
+ mouseup: 'mouseup',
3592
3709
  mouseenter: 'mouseentra',
3593
3710
  mouseleave: 'mouseesce',
3594
3711
  mouseover: 'mousesopra',
@@ -3598,11 +3715,11 @@ const it$1 = {
3598
3715
  keyup: 'tastosu',
3599
3716
  keypress: 'tastopremi',
3600
3717
  focus: 'fuoco',
3601
- blur: 'sfuocatura',
3718
+ blur: 'sfuocare', // Batch 3: the noun form parsed the blur command with no patient; the verb round-trips both event and command slots
3602
3719
  change: 'cambio',
3603
3720
  input: 'input',
3604
3721
  submit: 'invio',
3605
- reset: 'reset',
3722
+ reset: 'reimpostare', // Batch 3: English passthrough captured on.event as expression — profile verb round-trips
3606
3723
  load: 'carica',
3607
3724
  unload: 'scarica',
3608
3725
  resize: 'ridimensiona',
@@ -3681,6 +3798,9 @@ const it$1 = {
3681
3798
  properties: 'proprietà',
3682
3799
  },
3683
3800
  expressions: {
3801
+ characters: 'caratteri',
3802
+ inclusive: 'inclusivo',
3803
+ exclusive: 'esclusivo',
3684
3804
  first: 'primo',
3685
3805
  last: 'ultimo',
3686
3806
  next: 'prossimo',
@@ -3767,14 +3887,14 @@ const vi$1 = {
3767
3887
  init: 'khởi tạo',
3768
3888
  behavior: 'hành vi',
3769
3889
  install: 'cài đặt',
3770
- clone: 'sao chép',
3771
- prepend: 'thêm đầu',
3890
+ clone: 'nhân bản', // Batch 3: 'sao chép' is the copy verb — rendered clone parsed as action=copy
3891
+ prepend: 'thêm vào đầu', // Batch 3: 'thêm đầu' shatters at 'thêm' (add) — parsed as action=add
3772
3892
  else: 'không thì',
3773
3893
  breakpoint: 'điểm-dừng',
3774
3894
  clear: 'tẩy', // xóa is the remove PRIMARY in the profile
3775
3895
  close: 'đóng',
3776
3896
  open: 'mở',
3777
- select: 'chọn',
3897
+ select: 'đánh-dấu', // Batch 3: 'chọn' is the pick keyword — bare select rendered with it parses null
3778
3898
  },
3779
3899
  modifiers: {
3780
3900
  into: 'vào',
@@ -3797,21 +3917,23 @@ const vi$1 = {
3797
3917
  },
3798
3918
  events: {
3799
3919
  click: 'nhấp',
3800
- dblclick: 'nhấp đúp',
3920
+ dblclick: 'dblclick',
3801
3921
  input: 'nhập',
3802
- change: 'thay đổi',
3803
- submit: 'gửi',
3804
- keydown: 'phím xuống',
3805
- keyup: 'phím lên',
3806
- keypress: 'nhấn phím',
3807
- mouseover: 'chuột vào',
3808
- mouseout: 'chuột ra',
3809
- mouseenter: 'chuột vào',
3810
- mouseleave: 'chuột rời',
3811
- focus: 'tập trung',
3812
- blur: 'mất tập trung',
3813
- reset: 'đặt-lại',
3814
- load: 'tải',
3922
+ change: 'change',
3923
+ submit: 'nộp', // Batch 3: 'gửi' is the send verb — on-submit listener captured event "send"
3924
+ keydown: 'keydown',
3925
+ keyup: 'keyup',
3926
+ keypress: 'keypress',
3927
+ mouseover: 'mouseover',
3928
+ mouseout: 'mouseout',
3929
+ mouseenter: 'mouseenter',
3930
+ mouseleave: 'mouseleave',
3931
+ focus: 'focus',
3932
+ blur: 'blur',
3933
+ // V3c burn-down (2026-07-14): English passthrough — no native form round-trips
3934
+ // on the parse side (probe: split forms shatter, fused forms capture verbatim).
3935
+ reset: 'reset',
3936
+ load: 'load',
3815
3937
  scroll: 'cuộn',
3816
3938
  resize: 'đổi kích thước',
3817
3939
  },
@@ -3887,6 +4009,10 @@ const vi$1 = {
3887
4009
  selected: 'được chọn',
3888
4010
  },
3889
4011
  expressions: {
4012
+ random: 'ngẫu nhiên',
4013
+ characters: 'ký tự',
4014
+ inclusive: 'bao gồm',
4015
+ exclusive: 'loại trừ',
3890
4016
  me: 'tôi',
3891
4017
  my: 'của tôi',
3892
4018
  it: 'nó',
@@ -3981,7 +4107,7 @@ const pl$1 = {
3981
4107
  clear: 'zeruj', // wyczyść is a remove alternative in the profile
3982
4108
  close: 'zamknij',
3983
4109
  open: 'otwórz',
3984
- select: 'wybierz',
4110
+ select: 'zaznacz', // Batch 3: wybierz is the pick keyword — bare select rendered with it parses null
3985
4111
  clone: 'sklonuj',
3986
4112
  prepend: 'poprzedź',
3987
4113
  focus: 'skup',
@@ -4007,8 +4133,12 @@ const pl$1 = {
4007
4133
  events: {
4008
4134
  click: 'kliknięcie',
4009
4135
  dblclick: 'podwójnekliknięcie',
4010
- mousedown: 'myszdół',
4011
- mouseup: 'myszgóra',
4136
+ // mousedown/mouseup: English passthrough — no native form round-trips on the
4137
+ // parse side (V3c probe 2026-07-14: fused forms captured verbatim, split forms
4138
+ // shattered to their first word — live broken listeners in repeat-until-event).
4139
+ // The Batch-2 id-keyup precedent; revisit if S5b + tokenizer gain native entries.
4140
+ mousedown: 'mousedown',
4141
+ mouseup: 'mouseup',
4012
4142
  mouseenter: 'myszwejście',
4013
4143
  mouseleave: 'myszwyjście',
4014
4144
  mouseover: 'mysznad',
@@ -4021,8 +4151,8 @@ const pl$1 = {
4021
4151
  blur: 'rozmycie',
4022
4152
  change: 'zmiana',
4023
4153
  input: 'wejście',
4024
- submit: 'wyślij',
4025
- reset: 'resetuj',
4154
+ submit: 'wysłaniu', // Batch 3: wyślij is the send verb — on-submit listener captured event "send"
4155
+ reset: 'zresetuj', // Batch 3: resetuj captured on.event as expression (broken listener) — profile word round-trips
4026
4156
  load: 'załaduj',
4027
4157
  unload: 'wyładuj',
4028
4158
  resize: 'zmieńrozmiar',
@@ -4101,6 +4231,9 @@ const pl$1 = {
4101
4231
  properties: 'właściwości',
4102
4232
  },
4103
4233
  expressions: {
4234
+ characters: 'znaki',
4235
+ inclusive: 'włącznie',
4236
+ exclusive: 'wyłącznie',
4104
4237
  first: 'pierwszy',
4105
4238
  last: 'ostatni',
4106
4239
  next: 'następny',
@@ -4198,7 +4331,7 @@ const russianDictionary = {
4198
4331
  clear: 'очистить',
4199
4332
  close: 'закрыть',
4200
4333
  open: 'открыть',
4201
- select: 'выбрать',
4334
+ select: 'выделить', // Batch 3: выбрать is the pick keyword — bare select rendered with it parses null
4202
4335
  },
4203
4336
  modifiers: {
4204
4337
  to: 'к',
@@ -4240,7 +4373,7 @@ const russianDictionary = {
4240
4373
  change: 'изменение',
4241
4374
  input: 'ввод',
4242
4375
  submit: 'отправка',
4243
- reset: 'сброс',
4376
+ reset: 'сбросить', // Batch 3: сброс captured on.event as expression (broken listener) — profile verb round-trips
4244
4377
  load: 'загрузка',
4245
4378
  unload: 'выгрузка',
4246
4379
  resize: 'изменениеразмера', // fused (no `_`) — see mousedown note above
@@ -4339,6 +4472,9 @@ const russianDictionary = {
4339
4472
  html: 'html',
4340
4473
  },
4341
4474
  expressions: {
4475
+ characters: 'символы',
4476
+ inclusive: 'включительно',
4477
+ exclusive: 'исключительно',
4342
4478
  first: 'первый',
4343
4479
  last: 'последний',
4344
4480
  next: 'следующий',
@@ -4436,7 +4572,7 @@ const ukrainianDictionary = {
4436
4572
  clear: 'очистити',
4437
4573
  close: 'закрити',
4438
4574
  open: 'відкрити',
4439
- select: 'вибрати',
4575
+ select: 'виділити', // Batch 3: вибрати is the pick keyword — bare select rendered with it parses null
4440
4576
  },
4441
4577
  modifiers: {
4442
4578
  to: 'до',
@@ -4477,7 +4613,7 @@ const ukrainianDictionary = {
4477
4613
  change: 'зміна',
4478
4614
  input: 'введення',
4479
4615
  submit: 'надсилання',
4480
- reset: 'скидання',
4616
+ reset: 'скинути', // Batch 3: скидання captured on.event as expression (broken listener) — profile verb round-trips
4481
4617
  load: 'завантаження',
4482
4618
  unload: 'вивантаження',
4483
4619
  resize: 'змінарозміру', // fused (no `_`) — see mousedown note above
@@ -4576,6 +4712,9 @@ const ukrainianDictionary = {
4576
4712
  html: 'html',
4577
4713
  },
4578
4714
  expressions: {
4715
+ characters: 'символи',
4716
+ inclusive: 'включно',
4717
+ exclusive: 'виключно',
4579
4718
  first: 'перший',
4580
4719
  last: 'останній',
4581
4720
  next: 'наступний',
@@ -4663,13 +4802,13 @@ const hindiDictionary = {
4663
4802
  behavior: 'व्यवहार',
4664
4803
  focus: 'फोकस',
4665
4804
  blur: 'धुंधला',
4666
- clone: 'कॉपी',
4805
+ clone: 'क्लोन', // Batch 3: कॉपी is the copy verb — rendered clone parsed as action=copy
4667
4806
  prepend: 'जोड़ें_शुरू',
4668
4807
  breakpoint: 'ब्रेकप्वाइंट',
4669
4808
  clear: 'साफ़-करें',
4670
4809
  close: 'बंद-करें',
4671
4810
  open: 'खोलें',
4672
- select: 'चुनें',
4811
+ select: 'चिह्नित-करें', // Batch 3: चुनें is the pick keyword — bare select rendered with it parses null
4673
4812
  socket: 'सॉकेट',
4674
4813
  },
4675
4814
  modifiers: {
@@ -4814,6 +4953,7 @@ const hindiDictionary = {
4814
4953
  html: 'html',
4815
4954
  },
4816
4955
  expressions: {
4956
+ characters: 'अक्षर',
4817
4957
  first: 'पहला',
4818
4958
  last: 'अंतिम',
4819
4959
  next: 'अगला',
@@ -4888,7 +5028,7 @@ const bengaliDictionary = {
4888
5028
  init: 'শুরু',
4889
5029
  behavior: 'আচরণ',
4890
5030
  install: 'ইনস্টল',
4891
- clone: 'কপি',
5031
+ clone: 'ক্লোন', // Batch 3: কপি is the copy verb — rendered clone parsed as action=copy
4892
5032
  swap: 'বদল',
4893
5033
  morph: 'রূপান্তর',
4894
5034
  beep: 'বীপ',
@@ -4935,10 +5075,12 @@ const bengaliDictionary = {
4935
5075
  focus: 'ফোকাস',
4936
5076
  blur: 'ঝাপসা',
4937
5077
  reset: 'রিসেট',
4938
- keydown: 'কি-ডাউন',
4939
- keyup: 'কি-আপ',
4940
- mouseover: 'মাউস-ওভার',
4941
- mouseout: 'মাউস-আউট',
5078
+ keydown: 'keydown',
5079
+ keyup: 'keyup',
5080
+ // V3c burn-down (2026-07-14): English passthrough — no native form round-trips
5081
+ // on the parse side (probe: split forms shatter, fused forms capture verbatim).
5082
+ mouseover: 'mouseover',
5083
+ mouseout: 'mouseout',
4942
5084
  scroll: 'স্ক্রোল',
4943
5085
  resize: 'রিসাইজ',
4944
5086
  every: 'প্রতি',
@@ -5000,6 +5142,9 @@ const bengaliDictionary = {
5000
5142
  checked: 'চেক করা',
5001
5143
  },
5002
5144
  expressions: {
5145
+ characters: 'অক্ষর',
5146
+ inclusive: 'অন্তর্ভুক্ত',
5147
+ exclusive: 'বাদ',
5003
5148
  first: 'প্রথম',
5004
5149
  last: 'শেষ',
5005
5150
  next: 'পরবর্তী',
@@ -5074,7 +5219,7 @@ const thaiDictionary = {
5074
5219
  init: 'เริ่มต้น',
5075
5220
  behavior: 'พฤติกรรม',
5076
5221
  install: 'ติดตั้ง',
5077
- clone: 'คัดลอก',
5222
+ clone: 'โคลน', // Batch 3: คัดลอก is the copy verb — rendered clone parsed as action=copy
5078
5223
  swap: 'สลับที่',
5079
5224
  morph: 'แปลงร่าง',
5080
5225
  beep: 'บี๊บ',
@@ -5089,7 +5234,7 @@ const thaiDictionary = {
5089
5234
  clear: 'เคลียร์',
5090
5235
  close: 'ปิด',
5091
5236
  open: 'เปิด',
5092
- select: 'เลือก',
5237
+ select: 'ทำเครื่องหมาย', // Batch 3: เลือก is the pick keyword — bare select rendered with it parses null
5093
5238
  },
5094
5239
  modifiers: {
5095
5240
  to: 'ไปยัง',
@@ -5184,6 +5329,9 @@ const thaiDictionary = {
5184
5329
  checked: 'เลือกแล้ว',
5185
5330
  },
5186
5331
  expressions: {
5332
+ characters: 'อักขระ',
5333
+ inclusive: 'รวม',
5334
+ exclusive: 'ยกเว้น',
5187
5335
  first: 'แรก',
5188
5336
  last: 'สุดท้าย',
5189
5337
  next: 'ถัดไป',
@@ -5275,7 +5423,7 @@ const malayDictionary = {
5275
5423
  clear: 'bersihkan', // padam is a remove alternative in the profile
5276
5424
  close: 'tutup',
5277
5425
  open: 'buka',
5278
- select: 'pilih',
5426
+ select: 'tandai', // Batch 3: pilih is the pick keyword — bare select rendered with it parses null
5279
5427
  socket: 'soket', // derived from the ms profile (socket.primary); see generate-i18n-dictionaries
5280
5428
  },
5281
5429
  modifiers: {
@@ -5414,6 +5562,9 @@ const malayDictionary = {
5414
5562
  checked: 'ditanda',
5415
5563
  },
5416
5564
  expressions: {
5565
+ characters: 'aksara',
5566
+ inclusive: 'inklusif',
5567
+ exclusive: 'eksklusif',
5417
5568
  first: 'pertama',
5418
5569
  last: 'terakhir',
5419
5570
  next: 'seterusnya',
@@ -5508,7 +5659,7 @@ const tagalogDictionary = {
5508
5659
  install: 'ikabit',
5509
5660
  behavior: 'ugali',
5510
5661
  init: 'simulan',
5511
- clone: 'kopyahin',
5662
+ clone: 'i-clone', // Batch 3: kopyahin is the copy verb — rendered clone parsed as action=copy
5512
5663
  breakpoint: 'breakpoint',
5513
5664
  clear: 'burahin',
5514
5665
  close: 'isara',
@@ -5655,6 +5806,9 @@ const tagalogDictionary = {
5655
5806
  checked: 'naka_tsek',
5656
5807
  },
5657
5808
  expressions: {
5809
+ characters: 'karakter',
5810
+ inclusive: 'kasama',
5811
+ exclusive: 'bukod',
5658
5812
  first: 'una',
5659
5813
  last: 'huli',
5660
5814
  next: 'susunod',
@@ -5782,6 +5936,10 @@ const he$1 = {
5782
5936
  },
5783
5937
  attributes: {},
5784
5938
  expressions: {
5939
+ random: 'אקראי',
5940
+ characters: 'תווים',
5941
+ inclusive: 'כולל',
5942
+ exclusive: 'בלעדי',
5785
5943
  last: 'אחרון',
5786
5944
  },
5787
5945
  };
@@ -6648,6 +6806,143 @@ const englishProfile = {
6648
6806
  ],
6649
6807
  };
6650
6808
  // =============================================================================
6809
+ // Shared SOV rule factory — fused `halt the event call fn()` heads (Family H,
6810
+ // docs-internal/HANDOFF_r1-deferred-tail.md)
6811
+ // =============================================================================
6812
+ /**
6813
+ * The form-submit-prevent head fuses TWO juxtaposed commands (`on submit halt
6814
+ * the event call validateForm()`): parseEventHandler reads `halt` as the
6815
+ * action and sweeps `the event call validateForm()` into ONE patient blob, so
6816
+ * the SOV patient-first reorder fronts the whole blob — halt's operand lands
6817
+ * clause-initial far from its verb, and call's operand rides inside the blob
6818
+ * (tr bound validateForm() to halt and the event to call). No roleOrder can
6819
+ * fix a single-blob patient; this custom render splits the blob at the
6820
+ * language's call verb and emits both commands verb-final, joined by the
6821
+ * then-connective (the boundary the semantic compound-splitter already
6822
+ * respects — the event-debounce geometry):
6823
+ *
6824
+ * `<halt-operand> <pm> <event> <em> <halt-verb> <conn> <call-args> <pm> <call-verb>`
6825
+ *
6826
+ * The trailing EVENTBLOCKPLACEHOLDER (tryTransformEventWithBlockBody's mask
6827
+ * for an `if … end` tail) is re-emitted clause-final so the caller's
6828
+ * strip-and-append keeps working.
6829
+ */
6830
+ function sovHaltCallFusedRule(cfg) {
6831
+ return {
6832
+ name: 'halt-call-fused-verb-final',
6833
+ description: 'Split the swept `halt <operand> call <args>` blob; both verbs clause-final',
6834
+ priority: 95,
6835
+ match: {
6836
+ commands: ['halt'],
6837
+ requiredRoles: ['action', 'event', 'patient'],
6838
+ // Only the juxtaposed-call blob: a plain `halt`/`halt the event` handler
6839
+ // keeps its default render. The translated call verb must be present to
6840
+ // split on — if the dictionary emission drifts, the rule stands down
6841
+ // rather than emitting a mangled clause.
6842
+ predicate: parsed => {
6843
+ const p = parsed.roles.get('patient');
6844
+ return (!!p && /\bcall\b/.test(p.value) && !!p.translated && p.translated.includes(cfg.callVerb));
6845
+ },
6846
+ },
6847
+ transform: {
6848
+ roleOrder: [], // unused — custom takes over
6849
+ custom: parsed => {
6850
+ const p = parsed.roles.get('patient');
6851
+ const ev = parsed.roles.get('event');
6852
+ const act = parsed.roles.get('action');
6853
+ let blob = (p.translated ?? p.value).trim();
6854
+ const PH = 'EVENTBLOCKPLACEHOLDER';
6855
+ let tail = '';
6856
+ if (blob.endsWith(PH)) {
6857
+ blob = blob.slice(0, -PH.length).trim();
6858
+ tail = ` ${PH}`;
6859
+ }
6860
+ const at = blob.indexOf(cfg.callVerb);
6861
+ const haltOperand = blob.slice(0, at).trim();
6862
+ const callArgs = blob.slice(at + cfg.callVerb.length).trim();
6863
+ return ([
6864
+ haltOperand,
6865
+ cfg.patientMarker,
6866
+ ev.translated ?? ev.value,
6867
+ cfg.eventMarker,
6868
+ act.translated ?? act.value,
6869
+ cfg.connective,
6870
+ callArgs,
6871
+ cfg.patientMarker,
6872
+ cfg.callVerb,
6873
+ ].join(' ') + tail);
6874
+ },
6875
+ },
6876
+ };
6877
+ }
6878
+ // =============================================================================
6879
+ // Shared SOV rule factory — pick range rows (`pick characters 0 to 5 of #note`,
6880
+ // docs-internal/HANDOFF_pick-text-range-arc3.md)
6881
+ // =============================================================================
6882
+ /**
6883
+ * The canonical pick range row decomposes in parseStatement/parseEventHandler as
6884
+ * patient=`characters 0` + destination=`5 of #note` (the range's `to` reads as a
6885
+ * destination marker), so the default SOV reorder scatters the range across the
6886
+ * clause — ja emitted `文字 0 を クリック で 選択 5 の #note に` with the `5`
6887
+ * stranded after the verb, unparseable in every SOV language. No roleOrder can
6888
+ * reunite a range split across two roles; this custom render reassembles the
6889
+ * canonical surface and emits the SOV convention shape (big patient phrase
6890
+ * first, verb final):
6891
+ *
6892
+ * `<root> <gen> <unit> <a> <sep> <b> <pm> [<event> <em>] <verb>`
6893
+ *
6894
+ * e.g. ja `#note の 文字 0 から 5 を クリック で 選択`. The paired semantic
6895
+ * pick pattern (patterns/pick.ts VERB_FINAL_SPECS) parses exactly this shape
6896
+ * back, with the range fold normalizing `<a> <sep> <b>` to English `a to b` —
6897
+ * the two sides co-evolve per the i18n-renders-semantic-patterns-coevolve
6898
+ * lesson; change one and you must re-probe the other.
6899
+ *
6900
+ * Gated tight to the numeric-endpoint corpus shape (`<unit> <int>` patient +
6901
+ * `<int> of <root>` destination); word endpoints (`start`/`end`), modes
6902
+ * (`inclusive`) and anything else fall through to the default render unchanged.
6903
+ */
6904
+ function sovPickRangeRule(cfg) {
6905
+ return {
6906
+ name: 'pick-range-verb-final',
6907
+ description: 'Reassemble the pick range (unit + endpoints + root) adjacent, verb-final',
6908
+ priority: 95,
6909
+ match: {
6910
+ commands: ['pick'],
6911
+ requiredRoles: ['action', 'patient', 'destination'],
6912
+ predicate: parsed => {
6913
+ const p = parsed.roles.get('patient');
6914
+ const d = parsed.roles.get('destination');
6915
+ return (!!p &&
6916
+ !!d &&
6917
+ /^\S+\s+\d+$/.test(p.value.trim()) &&
6918
+ /^\d+\s+of\s+\S+$/.test(d.value.trim()));
6919
+ },
6920
+ },
6921
+ transform: {
6922
+ roleOrder: [], // unused — custom takes over
6923
+ custom: parsed => {
6924
+ const p = parsed.roles.get('patient');
6925
+ const d = parsed.roles.get('destination');
6926
+ const act = parsed.roles.get('action');
6927
+ const ev = parsed.roles.get('event');
6928
+ // Unit word from the TRANSLATED patient (`文字 0` → `文字`); endpoints
6929
+ // and root from the UNTRANSLATED values — integers and selectors are
6930
+ // language-invariant by construction (the R3 rule).
6931
+ const pParts = (p.translated ?? p.value).trim().split(/\s+/);
6932
+ const a = pParts[pParts.length - 1];
6933
+ const unit = pParts.slice(0, -1).join(' ');
6934
+ const dMatch = d.value.trim().match(/^(\d+)\s+of\s+(\S+)$/);
6935
+ const b = dMatch[1];
6936
+ const root = dMatch[2];
6937
+ const verb = act.translated ?? act.value;
6938
+ const head = [root, cfg.genitive, unit, a, cfg.rangeSep, b, cfg.patientMarker];
6939
+ const eventPart = ev ? [ev.translated ?? ev.value, cfg.eventMarker] : [];
6940
+ return [...head, ...eventPart, verb].join(' ');
6941
+ },
6942
+ },
6943
+ };
6944
+ }
6945
+ // =============================================================================
6651
6946
  // Japanese (SOV, Postpositions)
6652
6947
  // =============================================================================
6653
6948
  const japaneseProfile = {
@@ -6735,6 +7030,20 @@ const japaneseProfile = {
6735
7030
  insertMarkers: true,
6736
7031
  },
6737
7032
  },
7033
+ // the イベント を 送信 で 停止 それから validateForm() を 呼び出し
7034
+ sovHaltCallFusedRule({
7035
+ callVerb: '呼び出し',
7036
+ patientMarker: 'を',
7037
+ eventMarker: 'で',
7038
+ connective: 'それから',
7039
+ }),
7040
+ // #note の 文字 0 から 5 を クリック で 選択
7041
+ sovPickRangeRule({
7042
+ genitive: 'の',
7043
+ rangeSep: 'から',
7044
+ patientMarker: 'を',
7045
+ eventMarker: 'で',
7046
+ }),
6738
7047
  ],
6739
7048
  };
6740
7049
  // =============================================================================
@@ -6837,6 +7146,20 @@ const koreanProfile = {
6837
7146
  insertMarkers: true,
6838
7147
  },
6839
7148
  },
7149
+ // the 이벤트 를 제출 할 때 정지 그러면 validateForm() 를 호출
7150
+ sovHaltCallFusedRule({
7151
+ callVerb: '호출',
7152
+ patientMarker: '를',
7153
+ eventMarker: '할 때',
7154
+ connective: '그러면',
7155
+ }),
7156
+ // #note 의 문자 0 부터 5 를 클릭 할 때 선택
7157
+ sovPickRangeRule({
7158
+ genitive: '의',
7159
+ rangeSep: '부터',
7160
+ patientMarker: '를',
7161
+ eventMarker: '할 때',
7162
+ }),
6840
7163
  ],
6841
7164
  };
6842
7165
  // =============================================================================
@@ -6988,6 +7311,12 @@ const turkishProfile = {
6988
7311
  adpositionType: 'postposition',
6989
7312
  morphology: 'agglutinative',
6990
7313
  direction: 'ltr',
7314
+ // NOTE: roles absent from this order are safety-net-appended AFTER the verb
7315
+ // by reorderRoles. That stranding is what the semantic parser's generated
7316
+ // patterns + reclaim tolerances were built around, so extending this order
7317
+ // wholesale re-renders (and breaks) the whole tr corpus — fix individual
7318
+ // defective shapes with per-command `rules` below instead (see the
7319
+ // wait/repeat-until rules from the transformer-rendering arc).
6991
7320
  canonicalOrder: ['patient', 'event', 'action'],
6992
7321
  markers: [
6993
7322
  // Turkish case suffixes - using spaced form for tokenization
@@ -7082,6 +7411,58 @@ const turkishProfile = {
7082
7411
  insertMarkers: true,
7083
7412
  },
7084
7413
  },
7414
+ {
7415
+ name: 'wait-oblique-verb-final',
7416
+ description: "Keep wait's from-phrase and or-run inside the clause, verb-final",
7417
+ priority: 90,
7418
+ match: {
7419
+ commands: ['wait', 'bekle'],
7420
+ // Only the multi-argument form (`wait for X or Y from Z`) strands:
7421
+ // without this rule the safety-net appends duration+source AFTER the
7422
+ // verb (`bekle … belge den`), and the stranded from-phrase is captured
7423
+ // by the NEXT statement as a junk trigger event (behavior-sortable R3).
7424
+ requiredRoles: ['action', 'duration', 'source'],
7425
+ // Never reorder a fused single-line handler — its event must stay put.
7426
+ predicate: parsed => !parsed.roles.has('event'),
7427
+ },
7428
+ transform: {
7429
+ // belge den pointermove(clientY) veya pointerup(clientY) bekle
7430
+ roleOrder: ['patient', 'source', 'duration', 'action'],
7431
+ insertMarkers: true,
7432
+ },
7433
+ },
7434
+ {
7435
+ name: 'repeat-until-event-verb-final',
7436
+ description: "Keep repeat-until-event's from-phrase before the verb",
7437
+ priority: 90,
7438
+ match: {
7439
+ commands: ['repeat', 'tekrarla'],
7440
+ requiredRoles: ['action', 'patient', 'source'],
7441
+ // Gate to the until-event head; `repeat for item in .items` also
7442
+ // carries patient+source and must keep its (parseable) default order.
7443
+ predicate: parsed => /^repeat\s+until\s+event\b/i.test((parsed.original ?? '').trim()),
7444
+ },
7445
+ transform: {
7446
+ // kadar olay pointerup i belge den tekrarla — matched semantic-side by
7447
+ // repeat-tr-until-head-verb-final.
7448
+ roleOrder: ['patient', 'source', 'action'],
7449
+ insertMarkers: true,
7450
+ },
7451
+ },
7452
+ // the olay i gönder de durdur ardından validateForm() i çağır
7453
+ sovHaltCallFusedRule({
7454
+ callVerb: 'çağır',
7455
+ patientMarker: 'i',
7456
+ eventMarker: 'de',
7457
+ connective: 'ardından',
7458
+ }),
7459
+ // #note nin karakterler 0 ile 5 i tıklama de seç
7460
+ sovPickRangeRule({
7461
+ genitive: 'nin',
7462
+ rangeSep: 'ile',
7463
+ patientMarker: 'i',
7464
+ eventMarker: 'de',
7465
+ }),
7085
7466
  ],
7086
7467
  };
7087
7468
  // =============================================================================
@@ -7224,6 +7605,8 @@ const quechuaProfile = {
7224
7605
  adpositionType: 'postposition',
7225
7606
  morphology: 'agglutinative', // Actually polysynthetic
7226
7607
  direction: 'ltr',
7608
+ // NOTE: roles absent from this order are safety-net-appended AFTER the verb
7609
+ // (see the Turkish profile note) — extend via per-command `rules`, not here.
7227
7610
  canonicalOrder: ['patient', 'source', 'destination', 'event', 'action'],
7228
7611
  markers: [
7229
7612
  // Quechua case suffixes - using spaced form for tokenization
@@ -7236,6 +7619,41 @@ const quechuaProfile = {
7236
7619
  { form: 'wan', role: 'style', position: 'postposition', required: false },
7237
7620
  { form: 'hina', role: 'method', position: 'postposition', required: false }, // "as/like"
7238
7621
  ],
7622
+ rules: [
7623
+ {
7624
+ name: 'wait-oblique-verb-final',
7625
+ description: "Keep wait's from-phrase and or-run inside the clause, verb-final",
7626
+ priority: 90,
7627
+ match: {
7628
+ commands: ['wait', 'suyay'],
7629
+ // Only `wait for X or Y from Z` strands: duration is missing from
7630
+ // canonicalOrder, so the or-run lands AFTER the verb and the next
7631
+ // trigger glues it into its event (behavior-sortable R3). See the
7632
+ // matching Turkish rule.
7633
+ requiredRoles: ['action', 'duration', 'source'],
7634
+ predicate: parsed => !parsed.roles.has('event'),
7635
+ },
7636
+ transform: {
7637
+ // qillqa manta pointermove(clientY) utaq pointerup(clientY) suyay
7638
+ roleOrder: ['patient', 'source', 'duration', 'action'],
7639
+ insertMarkers: true,
7640
+ },
7641
+ },
7642
+ // the ruway ta kachay pi sayay chayqa validateForm() ta qayay
7643
+ sovHaltCallFusedRule({
7644
+ callVerb: 'qayay',
7645
+ patientMarker: 'ta',
7646
+ eventMarker: 'pi',
7647
+ connective: 'chayqa',
7648
+ }),
7649
+ // #note pa sanampa 0 kama 5 ta ñitiy pi akllay
7650
+ sovPickRangeRule({
7651
+ genitive: 'pa',
7652
+ rangeSep: 'kama',
7653
+ patientMarker: 'ta',
7654
+ eventMarker: 'pi',
7655
+ }),
7656
+ ],
7239
7657
  };
7240
7658
  // =============================================================================
7241
7659
  // Swahili (SVO, Bantu)
@@ -7271,6 +7689,8 @@ const bengaliProfile = {
7271
7689
  direction: 'ltr',
7272
7690
  // Bengali: Object comes before verb, postpositions follow nouns
7273
7691
  // "on click increment #count" → "#count কে ক্লিক এ বৃদ্ধি"
7692
+ // NOTE: roles absent from this order are safety-net-appended AFTER the verb
7693
+ // (see the Turkish profile note) — extend via per-command `rules`, not here.
7274
7694
  canonicalOrder: ['patient', 'event', 'action'],
7275
7695
  markers: [
7276
7696
  // Postpositions
@@ -7347,6 +7767,20 @@ const bengaliProfile = {
7347
7767
  insertMarkers: true,
7348
7768
  },
7349
7769
  },
7770
+ // the ঘটনা কে জমা এ থামুন তারপর validateForm() কে কল
7771
+ sovHaltCallFusedRule({
7772
+ callVerb: 'কল',
7773
+ patientMarker: 'কে',
7774
+ eventMarker: 'এ',
7775
+ connective: 'তারপর',
7776
+ }),
7777
+ // #note র অক্ষর 0 থেকে 5 কে ক্লিক এ বাছুন
7778
+ sovPickRangeRule({
7779
+ genitive: 'র',
7780
+ rangeSep: 'থেকে',
7781
+ patientMarker: 'কে',
7782
+ eventMarker: 'এ',
7783
+ }),
7350
7784
  ],
7351
7785
  };
7352
7786
  // =============================================================================
@@ -7534,6 +7968,20 @@ const hindiProfile = {
7534
7968
  insertMarkers: true,
7535
7969
  },
7536
7970
  },
7971
+ // the घटना को जमा पर रोकें फिर validateForm() को कॉल
7972
+ sovHaltCallFusedRule({
7973
+ callVerb: 'कॉल',
7974
+ patientMarker: 'को',
7975
+ eventMarker: 'पर',
7976
+ connective: 'फिर',
7977
+ }),
7978
+ // #note का अक्षर 0 से 5 को क्लिक पर चुनें
7979
+ sovPickRangeRule({
7980
+ genitive: 'का',
7981
+ rangeSep: 'से',
7982
+ patientMarker: 'को',
7983
+ eventMarker: 'पर',
7984
+ }),
7537
7985
  ],
7538
7986
  };
7539
7987
  // =============================================================================
@@ -9749,6 +10197,17 @@ class GrammarTransformer {
9749
10197
  if (block) {
9750
10198
  return this.transformBlock(block);
9751
10199
  }
10200
+ // 0a. Fragment carrying a stranded trailing block terminator (`wait 200ms
10201
+ // end`, `set x to y end` — what the `then`-splitter leaves from
10202
+ // `if … then <cmd> end`). `end` is not a marker, so left in place it is
10203
+ // swept into the open role's VALUE and rendered inside that phrase
10204
+ // (bn `200ms শেষ কে অপেক্ষা`). Strip it, transform the clause alone, and
10205
+ // re-append the translated terminator after the verb — the same tail
10206
+ // position transformBlockBody emits for event-headed blocks.
10207
+ const strippedEnd = this.transformWithTrailingEnd(input);
10208
+ if (strippedEnd !== null) {
10209
+ return strippedEnd;
10210
+ }
9752
10211
  // 0b. `set @attr to V on <scope>` (S1 tabs-aria): strip the trailing
9753
10212
  // `on <scope>`, transform the scope-less set normally, then re-insert
9754
10213
  // `on <scope>` where the semantic set patterns expect it.
@@ -9786,6 +10245,31 @@ class GrammarTransformer {
9786
10245
  // 7. Join without markers (still use joinTokens for consistency)
9787
10246
  return joinTokens(reordered.map(e => e.translated || e.value));
9788
10247
  }
10248
+ /**
10249
+ * `<command …> end` fragments: transform the command without its stranded
10250
+ * terminator, then re-append the translated terminator as a standalone
10251
+ * trailing token. Fragments that open a block of their own (`if … end`,
10252
+ * `repeat … end`, `js … end`) bail — their terminator belongs to them and
10253
+ * their dedicated paths handle it.
10254
+ */
10255
+ transformWithTrailingEnd(input) {
10256
+ const src = this.sourceProfile.code;
10257
+ const tokens = input.trim().split(/\s+/);
10258
+ if (tokens.length < 2) {
10259
+ return null;
10260
+ }
10261
+ const sourceEnd = translateWord('end', 'en', src).toLowerCase();
10262
+ if (tokens[tokens.length - 1].toLowerCase() !== sourceEnd) {
10263
+ return null;
10264
+ }
10265
+ const openers = new Set(['if', 'repeat', 'unless', 'while', 'when', 'live', 'js'].map(k => translateWord(k, 'en', src).toLowerCase()));
10266
+ if (tokens.slice(0, -1).some(t => openers.has(t.toLowerCase()))) {
10267
+ return null;
10268
+ }
10269
+ const inner = this.transformSingle(tokens.slice(0, -1).join(' '));
10270
+ const endT = translateWord(tokens[tokens.length - 1], src, this.targetProfile.code);
10271
+ return `${inner} ${endT}`;
10272
+ }
9789
10273
  /**
9790
10274
  * Detect and transform an inline JS block (`[on <event>] js <raw js> end`).
9791
10275
  *
@@ -10146,7 +10630,26 @@ class GrammarTransformer {
10146
10630
  const tailT = tail ? translateWord(tail, src, dst) : '';
10147
10631
  const clauseT = clause ? translateMultiWordValue(clause, src, dst) : '';
10148
10632
  const bodyT = this.transformConditionalBody(bodyTokens);
10149
- return [headT, clauseT, bodyT, tailT].filter(s => s.length > 0).join(' ');
10633
+ // SOV condition/branch boundary (R1 deferred-tail Family G): the SOV body
10634
+ // renders its first command operand-first (`最初 <button/> の中 .modal を
10635
+ // フォーカス`), so nothing marks where the condition ends and the branch
10636
+ // operand begins — the semantic fold's command-start detection needs a
10637
+ // `{value}{particle}` run followed by a verb, which a POSITIONAL-headed
10638
+ // operand (`first <button/> …`) never forms, and the condition scan
10639
+ // swallows the operand's head (focus-trap: ja focus.patient fell to the
10640
+ // `me` default, ko/qu to the `.modal` tail). Emit the target's
10641
+ // then-connective at the seam — the boundary the fold already respects
10642
+ // (isThenKeyword) — gated to exactly the blind shape: SOV target, a
10643
+ // positional keyword right after the branch's command verb, and no `then`
10644
+ // already ending the condition.
10645
+ const POSITIONAL_BRANCH_HEADS = new Set(['first', 'last', 'next', 'previous', 'closest']);
10646
+ const thenT = this.targetProfile.wordOrder === 'SOV' &&
10647
+ clauseT &&
10648
+ POSITIONAL_BRANCH_HEADS.has(bodyTokens[1]?.toLowerCase()) &&
10649
+ inner[bodyStart - 1]?.toLowerCase() !== 'then'
10650
+ ? translateWord('then', src, dst)
10651
+ : '';
10652
+ return [headT, clauseT, thenT, bodyT, tailT].filter(s => s.length > 0).join(' ');
10150
10653
  }
10151
10654
  /**
10152
10655
  * Transform an `if`/`unless` block body, splitting it at a top-level `else` into