@lokascript/i18n 2.5.1 → 2.7.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 (73) hide show
  1. package/dist/browser.cjs +1274 -154
  2. package/dist/browser.cjs.map +1 -1
  3. package/dist/browser.d.cts +3 -3
  4. package/dist/browser.d.ts +3 -3
  5. package/dist/browser.js +1274 -155
  6. package/dist/browser.js.map +1 -1
  7. package/dist/dictionaries/index.cjs +364 -133
  8. package/dist/dictionaries/index.cjs.map +1 -1
  9. package/dist/dictionaries/index.d.cts +1 -1
  10. package/dist/dictionaries/index.d.ts +1 -1
  11. package/dist/dictionaries/index.js +364 -133
  12. package/dist/dictionaries/index.js.map +1 -1
  13. package/dist/index.cjs +8860 -7740
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +3 -3
  16. package/dist/index.d.ts +3 -3
  17. package/dist/index.js +8860 -7741
  18. package/dist/index.js.map +1 -1
  19. package/dist/lokascript-i18n.min.js +1 -1
  20. package/dist/lokascript-i18n.min.js.map +1 -1
  21. package/dist/lokascript-i18n.mjs +1697 -146
  22. package/dist/lokascript-i18n.mjs.map +1 -1
  23. package/dist/plugins/vite.cjs +359 -132
  24. package/dist/plugins/vite.cjs.map +1 -1
  25. package/dist/plugins/vite.js +359 -132
  26. package/dist/plugins/vite.js.map +1 -1
  27. package/dist/plugins/webpack.cjs +359 -132
  28. package/dist/plugins/webpack.cjs.map +1 -1
  29. package/dist/plugins/webpack.js +359 -132
  30. package/dist/plugins/webpack.js.map +1 -1
  31. package/dist/{transformer-BLv389qz.d.ts → transformer-BA8YX9u1.d.ts} +120 -2
  32. package/dist/{transformer-PFsYELrc.d.cts → transformer-DM7iplCU.d.cts} +120 -2
  33. package/dist/{types-BcALAO6h.d.cts → types-BYtpqGq3.d.cts} +1 -1
  34. package/dist/{types-BcALAO6h.d.ts → types-BYtpqGq3.d.ts} +1 -1
  35. package/package.json +9 -6
  36. package/src/browser.ts +1 -0
  37. package/src/command-primary-roles.test.ts +59 -0
  38. package/src/constants.ts +51 -0
  39. package/src/dictionaries/ar.ts +8 -3
  40. package/src/dictionaries/bn.ts +3 -1
  41. package/src/dictionaries/de.ts +19 -4
  42. package/src/dictionaries/derive.ts +4 -0
  43. package/src/dictionaries/en.ts +9 -0
  44. package/src/dictionaries/es.ts +2 -2
  45. package/src/dictionaries/fr.ts +6 -2
  46. package/src/dictionaries/he.ts +7 -1
  47. package/src/dictionaries/hi.ts +17 -10
  48. package/src/dictionaries/id.ts +15 -8
  49. package/src/dictionaries/it.ts +6 -3
  50. package/src/dictionaries/ja.ts +10 -4
  51. package/src/dictionaries/ko.ts +9 -4
  52. package/src/dictionaries/ms.ts +2 -1
  53. package/src/dictionaries/pl.ts +11 -3
  54. package/src/dictionaries/pt.ts +10 -4
  55. package/src/dictionaries/qu.ts +46 -22
  56. package/src/dictionaries/ru.ts +14 -7
  57. package/src/dictionaries/sw.ts +37 -10
  58. package/src/dictionaries/th.ts +3 -1
  59. package/src/dictionaries/tl.ts +14 -7
  60. package/src/dictionaries/tr.ts +22 -9
  61. package/src/dictionaries/uk.ts +13 -7
  62. package/src/dictionaries/vi.ts +3 -3
  63. package/src/dictionaries/zh.ts +10 -2
  64. package/src/examples/new-languages.ts +1 -1
  65. package/src/grammar/grammar.test.ts +1242 -1
  66. package/src/grammar/index.ts +1 -0
  67. package/src/grammar/profiles/index.ts +358 -12
  68. package/src/grammar/transformer.ts +1053 -16
  69. package/src/grammar/types.ts +22 -1
  70. package/src/index.ts +1 -0
  71. package/src/new-languages.test.ts +7 -3
  72. package/src/parser/parser-integration.test.ts +5 -1
  73. package/src/positional-keyword-drift.test.ts +138 -0
@@ -35,6 +35,7 @@ export {
35
35
  frenchProfile,
36
36
  portugueseProfile,
37
37
  indonesianProfile,
38
+ malayProfile,
38
39
  quechuaProfile,
39
40
  swahiliProfile,
40
41
  bengaliProfile,
@@ -6,7 +6,8 @@
6
6
  * language-specific markers and rules.
7
7
  */
8
8
 
9
- import type { LanguageProfile } from '../types';
9
+ import type { LanguageProfile, SemanticRole } from '../types';
10
+ import { reorderRoles, insertMarkers } from '../types';
10
11
 
11
12
  // =============================================================================
12
13
  // English (Reference Language)
@@ -104,6 +105,41 @@ export const japaneseProfile: LanguageProfile = {
104
105
  insertMarkers: true,
105
106
  },
106
107
  },
108
+ {
109
+ name: 'set-to',
110
+ description: 'Transform set X to Y to Japanese verb-final order',
111
+ priority: 90,
112
+ match: {
113
+ commands: ['set', '設定'],
114
+ requiredRoles: ['action', 'patient', 'destination'],
115
+ // Skip a set whose value swept up a trailing block terminator (`set X to Y
116
+ // end` — the body of an inline `if … then set … end`): verb-final reorder
117
+ // would push the verb past `end` and break the block. These parse fine
118
+ // verb-medial (the default).
119
+ predicate: parsed => !/(^|\s)end$/i.test((parsed.original ?? '').trim()),
120
+ },
121
+ transform: {
122
+ // X を Y に 設定 — verb-final, matching the generated SOV set pattern
123
+ // (markerOverride: を on the destination / に on the value). Default reorder
124
+ // emitted verb-MEDIAL (`X を 設定 Y に`), unmatched by any set pattern.
125
+ roleOrder: ['patient', 'destination', 'action'],
126
+ insertMarkers: true,
127
+ },
128
+ },
129
+ {
130
+ name: 'bind-to',
131
+ description: 'Transform bind $var to #el to Japanese verb-final order',
132
+ priority: 90,
133
+ match: {
134
+ commands: ['bind', 'バインド'],
135
+ requiredRoles: ['action', 'patient', 'destination'],
136
+ },
137
+ transform: {
138
+ // $var を #el に バインド
139
+ roleOrder: ['patient', 'destination', 'action'],
140
+ insertMarkers: true,
141
+ },
142
+ },
107
143
  ],
108
144
  };
109
145
 
@@ -127,6 +163,10 @@ export const koreanProfile: LanguageProfile = {
127
163
  { form: '를', role: 'patient', position: 'postposition', required: true, alternatives: ['을'] },
128
164
  { form: '에', role: 'destination', position: 'postposition', required: true },
129
165
  { form: '에서', role: 'source', position: 'postposition', required: true },
166
+ // Event marker — the semantic *-event-ko-sov-* patterns anchor on 할 때
167
+ // (the generator profile's eventMarker); without it every ko handler
168
+ // emitted a bare event name no fused pattern could anchor.
169
+ { form: '할 때', role: 'event', position: 'postposition', required: true },
130
170
  {
131
171
  form: '로',
132
172
  role: 'style',
@@ -158,6 +198,56 @@ export const koreanProfile: LanguageProfile = {
158
198
  insertMarkers: true,
159
199
  },
160
200
  },
201
+ {
202
+ name: 'put-into',
203
+ description: 'Transform put X into Y to Korean verb-final order',
204
+ priority: 90,
205
+ match: {
206
+ commands: ['put', '넣다'],
207
+ requiredRoles: ['action', 'patient', 'destination'],
208
+ // Standalone put only (e.g. a then-chain clause): an event handler whose
209
+ // action is `put` must keep the event mid-stream, so don't verb-final it.
210
+ predicate: parsed => !parsed.roles.has('event'),
211
+ },
212
+ transform: {
213
+ // X 를 Y 에 넣다 (patient, destination, verb-last)
214
+ roleOrder: ['patient', 'destination', 'action'],
215
+ insertMarkers: true,
216
+ },
217
+ },
218
+ {
219
+ name: 'set-to',
220
+ description: 'Transform set X to Y to Korean verb-final order',
221
+ priority: 90,
222
+ match: {
223
+ commands: ['set', '설정'],
224
+ requiredRoles: ['action', 'patient', 'destination'],
225
+ // Skip a set whose value swept up a trailing block terminator (`set X to Y
226
+ // end` — the body of an inline `if … then set … end`): verb-final reorder
227
+ // would push the verb past `end` and break the block. These parse fine
228
+ // verb-medial (the default).
229
+ predicate: parsed => !/(^|\s)end$/i.test((parsed.original ?? '').trim()),
230
+ },
231
+ transform: {
232
+ // X 를 Y 에 설정 — verb-final, matching the generated SOV set pattern.
233
+ roleOrder: ['patient', 'destination', 'action'],
234
+ insertMarkers: true,
235
+ },
236
+ },
237
+ {
238
+ name: 'bind-to',
239
+ description: 'Transform bind $var to #el to Korean verb-final order',
240
+ priority: 90,
241
+ match: {
242
+ commands: ['bind', '바인드'],
243
+ requiredRoles: ['action', 'patient', 'destination'],
244
+ },
245
+ transform: {
246
+ // $var 를 #el 에 바인드
247
+ roleOrder: ['patient', 'destination', 'action'],
248
+ insertMarkers: true,
249
+ },
250
+ },
161
251
  ],
162
252
  };
163
253
 
@@ -198,20 +288,33 @@ export const chineseProfile: LanguageProfile = {
198
288
  requiredRoles: ['event', 'action'],
199
289
  },
200
290
  transform: {
201
- // 当 点击 时 增加 #count
291
+ // 当 EVENT 时 ACTION [patient] [destination] [source] [style] [method] ...
202
292
  roleOrder: ['event', 'action', 'patient'],
203
293
  insertMarkers: true,
204
- custom: (parsed, _profile) => {
205
- // Handle 当...时 circumfix
294
+ custom: (parsed, profile) => {
295
+ // Handle 当...时 circumfix wrapping the event, then emit the action,
296
+ // then any remaining roles in canonical order with their markers.
297
+ // Earlier this function emitted only event/action/patient and silently
298
+ // dropped destination, source, style, etc. — that produced truncated
299
+ // output for `send X to Y`, `fetch /api with method:...`, etc.
206
300
  const event = parsed.roles.get('event');
207
301
  const action = parsed.roles.get('action');
208
- const patient = parsed.roles.get('patient');
209
302
 
210
- const parts = ['当', event?.translated || event?.value, '时'];
211
- parts.push(action?.translated || action?.value || '');
212
- if (patient) {
213
- parts.push(patient.translated || patient.value);
214
- }
303
+ // Build the post-action role list using the canonical order minus
304
+ // event/action, plus a safety net for any roles not in the order.
305
+ const remaining = new Map(parsed.roles);
306
+ remaining.delete('event');
307
+ remaining.delete('action');
308
+
309
+ const remainingOrder: SemanticRole[] = (profile.canonicalOrder || []).filter(
310
+ r => r !== 'event' && r !== 'action'
311
+ );
312
+ const reordered = reorderRoles(remaining, remainingOrder);
313
+ const markedTokens = insertMarkers(reordered, profile.markers, profile.adpositionType);
314
+
315
+ const parts: string[] = ['当', event?.translated || event?.value || '', '时'];
316
+ if (action) parts.push(action.translated || action.value);
317
+ parts.push(...markedTokens);
215
318
 
216
319
  return parts.filter(Boolean).join(' ');
217
320
  },
@@ -231,6 +334,30 @@ export const chineseProfile: LanguageProfile = {
231
334
  insertMarkers: true,
232
335
  },
233
336
  },
337
+ {
338
+ name: 'bind-to',
339
+ description: 'Bind binds a variable to an element: 绑定 X 到 Y (no 把)',
340
+ priority: 90,
341
+ match: {
342
+ commands: ['bind', '绑定'],
343
+ requiredRoles: ['action', 'patient', 'destination'],
344
+ },
345
+ transform: {
346
+ // Plain SVO without the 把 object-fronting: the bound variable is the
347
+ // semantic destination (not a fronted patient), and `到` marks the
348
+ // target element. Custom so the patient does not pick up 把.
349
+ roleOrder: ['action', 'patient', 'destination'],
350
+ custom: parsed => {
351
+ const action = parsed.roles.get('action');
352
+ const patient = parsed.roles.get('patient');
353
+ const destination = parsed.roles.get('destination');
354
+ const verb = action?.translated || action?.value || '';
355
+ const v = patient?.translated || patient?.value || '';
356
+ const d = destination?.translated || destination?.value || '';
357
+ return [verb, v, '到', d].filter(Boolean).join(' ');
358
+ },
359
+ },
360
+ },
234
361
  ],
235
362
  };
236
363
 
@@ -339,6 +466,56 @@ export const turkishProfile: LanguageProfile = {
339
466
  insertMarkers: true,
340
467
  },
341
468
  },
469
+ {
470
+ name: 'put-into',
471
+ description: 'Transform put X into Y to Turkish verb-final order',
472
+ priority: 90,
473
+ match: {
474
+ commands: ['put', 'koy'],
475
+ requiredRoles: ['action', 'patient', 'destination'],
476
+ // Standalone put only (e.g. a then-chain clause): an event handler whose
477
+ // action is `put` must keep the event mid-stream, so don't verb-final it.
478
+ predicate: parsed => !parsed.roles.has('event'),
479
+ },
480
+ transform: {
481
+ // X i Y e koy (patient, destination, verb-last)
482
+ roleOrder: ['patient', 'destination', 'action'],
483
+ insertMarkers: true,
484
+ },
485
+ },
486
+ {
487
+ name: 'set-to',
488
+ description: 'Transform set X to Y to Turkish verb-final order',
489
+ priority: 90,
490
+ match: {
491
+ commands: ['set', 'ayarla'],
492
+ requiredRoles: ['action', 'patient', 'destination'],
493
+ // Skip a set whose value swept up a trailing block terminator (`set X to Y
494
+ // end` — the body of an inline `if … then set … end`): verb-final reorder
495
+ // would push the verb past `end` and break the block. These parse fine
496
+ // verb-medial (the default).
497
+ predicate: parsed => !/(^|\s)end$/i.test((parsed.original ?? '').trim()),
498
+ },
499
+ transform: {
500
+ // X i Y e ayarla — verb-final, matching the generated SOV set pattern.
501
+ roleOrder: ['patient', 'destination', 'action'],
502
+ insertMarkers: true,
503
+ },
504
+ },
505
+ {
506
+ name: 'bind-to',
507
+ description: 'Transform bind $var to #el to Turkish verb-final order',
508
+ priority: 90,
509
+ match: {
510
+ commands: ['bind'],
511
+ requiredRoles: ['action', 'patient', 'destination'],
512
+ },
513
+ transform: {
514
+ // $var i #el e bağla
515
+ roleOrder: ['patient', 'destination', 'action'],
516
+ insertMarkers: true,
517
+ },
518
+ },
342
519
  ],
343
520
  };
344
521
 
@@ -473,6 +650,35 @@ export const indonesianProfile: LanguageProfile = {
473
650
  ],
474
651
  };
475
652
 
653
+ // =============================================================================
654
+ // Malay (SVO, Austronesian) — grammar mirrors Indonesian (shared function words:
655
+ // pada / ke / dari / dengan / sebagai). Without this profile the transformer threw
656
+ // "Unknown target locale: ms", so no ms translation could be generated at all.
657
+ // =============================================================================
658
+
659
+ export const malayProfile: LanguageProfile = {
660
+ code: 'ms',
661
+ name: 'Bahasa Melayu',
662
+
663
+ wordOrder: 'SVO',
664
+ adpositionType: 'preposition',
665
+ morphology: 'agglutinative',
666
+ direction: 'ltr',
667
+
668
+ canonicalOrder: ['event', 'action', 'patient', 'destination'],
669
+
670
+ markers: [
671
+ // ms marks the event clause with `apabila` ("when/on") — matching the semantic
672
+ // ms event-handler pattern (`apabila {event} {body}`) and the ms dict's
673
+ // `on: apabila`. (Indonesian uses `pada` here; the two diverge on the event head.)
674
+ { form: 'apabila', role: 'event', position: 'preposition', required: true },
675
+ { form: 'ke', role: 'destination', position: 'preposition', required: false },
676
+ { form: 'dari', role: 'source', position: 'preposition', required: false },
677
+ { form: 'dengan', role: 'style', position: 'preposition', required: false },
678
+ { form: 'sebagai', role: 'method', position: 'preposition', required: false },
679
+ ],
680
+ };
681
+
476
682
  // =============================================================================
477
683
  // Quechua (SOV, Quechuan)
478
684
  // =============================================================================
@@ -521,7 +727,10 @@ export const swahiliProfile: LanguageProfile = {
521
727
  { form: 'kwa', role: 'destination', position: 'preposition', required: false },
522
728
  { form: 'kutoka', role: 'source', position: 'preposition', required: false },
523
729
  { form: 'na', role: 'style', position: 'preposition', required: false },
524
- { form: 'kama', role: 'method', position: 'preposition', required: false }, // "as/like"
730
+ // `kuwa`, not `kama`: sw `kama` is the IF keyword — rendering the method
731
+ // role ("as X") with it grew a phantom `if` at semantic parse time. See the
732
+ // matching comment in dictionaries/sw.ts (modifiers.as).
733
+ { form: 'kuwa', role: 'method', position: 'preposition', required: false }, // "as/become"
525
734
  ],
526
735
  };
527
736
 
@@ -568,6 +777,56 @@ export const bengaliProfile: LanguageProfile = {
568
777
  insertMarkers: true,
569
778
  },
570
779
  },
780
+ {
781
+ name: 'put-into',
782
+ description: 'Transform put X into Y to Bengali verb-final order',
783
+ priority: 90,
784
+ match: {
785
+ commands: ['put', 'রাখুন'],
786
+ requiredRoles: ['action', 'patient', 'destination'],
787
+ // Standalone put only (e.g. a then-chain clause): an event handler whose
788
+ // action is `put` must keep the event mid-stream, so don't verb-final it.
789
+ predicate: parsed => !parsed.roles.has('event'),
790
+ },
791
+ transform: {
792
+ // X কে Y তে রাখুন (patient, destination, verb-last)
793
+ roleOrder: ['patient', 'destination', 'action'],
794
+ insertMarkers: true,
795
+ },
796
+ },
797
+ {
798
+ name: 'set-to',
799
+ description: 'Transform set X to Y to Bengali verb-final order',
800
+ priority: 90,
801
+ match: {
802
+ commands: ['set', 'সেট'],
803
+ requiredRoles: ['action', 'patient', 'destination'],
804
+ // Skip a set whose value swept up a trailing block terminator (`set X to Y
805
+ // end` — the body of an inline `if … then set … end`): verb-final reorder
806
+ // would push the verb past `end` and break the block. These parse fine
807
+ // verb-medial (the default).
808
+ predicate: parsed => !/(^|\s)end$/i.test((parsed.original ?? '').trim()),
809
+ },
810
+ transform: {
811
+ // X কে Y তে সেট — verb-final, matching the generated SOV set pattern.
812
+ roleOrder: ['patient', 'destination', 'action'],
813
+ insertMarkers: true,
814
+ },
815
+ },
816
+ {
817
+ name: 'bind-to',
818
+ description: 'Transform bind $var to #el to Bengali verb-final order',
819
+ priority: 90,
820
+ match: {
821
+ commands: ['bind', 'বাইন্ড'],
822
+ requiredRoles: ['action', 'patient', 'destination'],
823
+ },
824
+ transform: {
825
+ // $var কে #el তে বাইন্ড
826
+ roleOrder: ['patient', 'destination', 'action'],
827
+ insertMarkers: true,
828
+ },
829
+ },
571
830
  ],
572
831
  };
573
832
 
@@ -726,6 +985,61 @@ export const hindiProfile: LanguageProfile = {
726
985
  insertMarkers: true,
727
986
  },
728
987
  },
988
+ {
989
+ name: 'put-into',
990
+ description: 'Transform put X into Y to Hindi verb-final order',
991
+ priority: 90,
992
+ match: {
993
+ commands: ['put', 'रखें'],
994
+ requiredRoles: ['action', 'patient', 'destination'],
995
+ },
996
+ transform: {
997
+ // "hi" को #out में रखें
998
+ roleOrder: ['patient', 'destination', 'action'],
999
+ insertMarkers: true,
1000
+ },
1001
+ },
1002
+ {
1003
+ name: 'set-to',
1004
+ description: 'Transform set X to Y to Hindi verb-final order',
1005
+ priority: 90,
1006
+ match: {
1007
+ commands: ['set', 'सेट'],
1008
+ requiredRoles: ['action', 'patient', 'destination'],
1009
+ // Skip a set whose value swept up a trailing block terminator (`set X to Y
1010
+ // end` — the body of an inline `if … then set … end`): verb-final reorder
1011
+ // would push the verb past `end` and break the block. These parse fine
1012
+ // verb-medial (the default).
1013
+ predicate: parsed => !/(^|\s)end$/i.test((parsed.original ?? '').trim()),
1014
+ },
1015
+ transform: {
1016
+ // #x.innerText को इसका.name में सेट — verb-final, matching the generated
1017
+ // SOV set pattern (set schema markerOverride puts को on the destination /
1018
+ // में on the value). The default reorder emitted verb-MEDIAL
1019
+ // (`#x को सेट … में`), which no set pattern matched → bare-event fallback.
1020
+ roleOrder: ['patient', 'destination', 'action'],
1021
+ insertMarkers: true,
1022
+ },
1023
+ },
1024
+ {
1025
+ name: 'bind-to',
1026
+ description: 'Transform bind $var to #el to Hindi verb-final order',
1027
+ priority: 90,
1028
+ match: {
1029
+ commands: ['bind', 'बाइंड', 'बांधें'],
1030
+ requiredRoles: ['action', 'patient', 'destination'],
1031
+ },
1032
+ transform: {
1033
+ // $greeting को #name-input में bind — verb-final, matching the generated SOV
1034
+ // bind pattern (`{destination} को {source} में bind`). The default reorder
1035
+ // emitted verb-MEDIAL (`$greeting को bind #name-input में`), which the
1036
+ // verb-final pattern never matched → the bare-event fallback mis-anchored
1037
+ // the fronted `$greeting` as a phantom `on` event (the rf=0.00 bind residue).
1038
+ // ja/ko/zh/tr/bn already carry this rule; hi was the only SOV gap.
1039
+ roleOrder: ['patient', 'destination', 'action'],
1040
+ insertMarkers: true,
1041
+ },
1042
+ },
729
1043
  ],
730
1044
  };
731
1045
 
@@ -748,7 +1062,11 @@ export const tagalogProfile: LanguageProfile = {
748
1062
  markers: [
749
1063
  { form: 'kapag', role: 'event', position: 'preposition', required: true },
750
1064
  { form: 'sa', role: 'destination', position: 'preposition', required: false },
751
- { form: 'mula sa', role: 'source', position: 'preposition', required: false },
1065
+ // mula_sa (underscore) matches the tl dict and the semantic profile's
1066
+ // source marker — the spaced form emitted two tokens the generated
1067
+ // patterns' single mula_sa literal could never match, so `remove X
1068
+ // mula sa Y` lost its source and the schema default fabricated me.
1069
+ { form: 'mula_sa', role: 'source', position: 'preposition', required: false },
752
1070
  { form: 'nang', role: 'style', position: 'preposition', required: false },
753
1071
  { form: 'bilang', role: 'method', position: 'preposition', required: false },
754
1072
  ],
@@ -854,6 +1172,33 @@ export const hebrewProfile: LanguageProfile = {
854
1172
  { form: 'עם', role: 'style', position: 'preposition', required: false },
855
1173
  { form: 'כ', role: 'method', position: 'preposition', required: false },
856
1174
  ],
1175
+
1176
+ rules: [
1177
+ {
1178
+ name: 'bind-to',
1179
+ description: 'Bind binds a variable to an element: קשור X ל Y',
1180
+ priority: 90,
1181
+ match: {
1182
+ commands: ['bind', 'קשור'],
1183
+ requiredRoles: ['action', 'patient', 'destination'],
1184
+ },
1185
+ transform: {
1186
+ // The bound variable is the semantic destination (kept bare, no את
1187
+ // object marker), and ל marks the target element. Custom so the
1188
+ // variable does not pick up את and the element uses ל (not על).
1189
+ roleOrder: ['action', 'patient', 'destination'],
1190
+ custom: parsed => {
1191
+ const action = parsed.roles.get('action');
1192
+ const patient = parsed.roles.get('patient');
1193
+ const destination = parsed.roles.get('destination');
1194
+ const verb = action?.translated || action?.value || '';
1195
+ const v = patient?.translated || patient?.value || '';
1196
+ const d = destination?.translated || destination?.value || '';
1197
+ return [verb, v, 'ל', d].filter(Boolean).join(' ');
1198
+ },
1199
+ },
1200
+ },
1201
+ ],
857
1202
  };
858
1203
 
859
1204
  // =============================================================================
@@ -872,6 +1217,7 @@ export const profiles: Record<string, LanguageProfile> = {
872
1217
  fr: frenchProfile,
873
1218
  pt: portugueseProfile,
874
1219
  id: indonesianProfile,
1220
+ ms: malayProfile,
875
1221
  qu: quechuaProfile,
876
1222
  sw: swahiliProfile,
877
1223
  bn: bengaliProfile,