@lokascript/i18n 2.5.1 → 2.6.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 +37 -37
  16. package/dist/index.d.ts +37 -37
  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 +6 -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
@@ -1,4 +1,4 @@
1
- import { a as Dictionary } from './types-BcALAO6h.js';
1
+ import { D as Dictionary } from './types-BYtpqGq3.js';
2
2
 
3
3
  /**
4
4
  * KeywordProvider interface for locale-aware parsing.
@@ -869,6 +869,7 @@ declare const germanProfile: LanguageProfile;
869
869
  declare const frenchProfile: LanguageProfile;
870
870
  declare const portugueseProfile: LanguageProfile;
871
871
  declare const indonesianProfile: LanguageProfile;
872
+ declare const malayProfile: LanguageProfile;
872
873
  declare const quechuaProfile: LanguageProfile;
873
874
  declare const swahiliProfile: LanguageProfile;
874
875
  declare const hebrewProfile: LanguageProfile;
@@ -901,10 +902,127 @@ declare class GrammarTransformer {
901
902
  * For multi-line input, preserves line structure (indentation, blank lines).
902
903
  */
903
904
  transform(input: string): string;
905
+ private transformInternal;
904
906
  /**
905
907
  * Transform a single hyperscript statement (no compound "then" chains).
906
908
  */
907
909
  private transformSingle;
910
+ /**
911
+ * Detect and transform an inline JS block (`[on <event>] js <raw js> end`).
912
+ *
913
+ * The `js ... end` body is raw JavaScript: it must not be tokenized,
914
+ * translated, or word-order reordered. We mask the whole block with a single
915
+ * opaque placeholder, run the surrounding statement (the event-handler head,
916
+ * if any) through the normal reorder pipeline so the placeholder lands in the
917
+ * correct action position, then substitute the translated `js`/`end` keywords
918
+ * around the verbatim body.
919
+ *
920
+ * Returns `null` (fall through to the normal path) when there is no js block,
921
+ * no matching `end`, or trailing content after `end` (kept tight on purpose).
922
+ */
923
+ private tryTransformJsBlock;
924
+ /**
925
+ * Transform an event handler whose body is a block command
926
+ * (`on <event> [from <src>] {if|repeat|unless|while|for} … end`).
927
+ *
928
+ * `parseEventHandler` would treat the block keyword as the action and sweep the
929
+ * condition/body into role values, then reorder them — shredding the block
930
+ * (`if event.shiftKey call submitAndContinue() end` → scattered tokens). Instead
931
+ * we mask the whole block as an opaque action placeholder, reorder the event
932
+ * head normally, transform the block as a self-contained unit, and restitch.
933
+ *
934
+ * Returns `null` (fall through) when the input isn't an event handler, has no
935
+ * block-keyword body, or has no closing `end`.
936
+ */
937
+ private tryTransformEventWithBlockBody;
938
+ /**
939
+ * Transform an event handler whose body is an inline `unless` guard with NO
940
+ * `end` (`on <event> unless <cond> <body>` — the `unless-condition` shape).
941
+ *
942
+ * Object-marking SVO targets (he, zh). `parseEventHandler` reads `unless` as the
943
+ * action and sweeps the whole `<cond> <body>` tail into a single `patient` blob;
944
+ * the target then prefixes that blob with its object marker — Hebrew's accusative
945
+ * את (`… אלא את I match .disabled מתג .selected`) or Chinese's BA particle 把
946
+ * (`… 除非 把 I match .disabled 切换 .selected`) — and the inner toggle loses its
947
+ * own marker. The semantic parser can't recover the guard from that: the marker
948
+ * ahead of the condition blocks the `unless` pattern AND the now-markerless body
949
+ * command fails its object-marked toggle pattern, so the body collapses (`unless`
950
+ * dropped). Marker-less languages (de/it/ar/pl) tolerate the same role-blob and
951
+ * stay faithful, so this is an object-marker artifact, not a general parse gap.
952
+ *
953
+ * The standalone `unless <cond> <body>` path already produces the correct shape
954
+ * (`extractBlockStructure` → `transformBlock`: condition kept marker-free, body
955
+ * command keeps its marker — he `אלא I match .disabled מתג את .selected`, zh
956
+ * `除非 I match .disabled 切换 把 .selected`). So we split the event head off,
957
+ * transform the guard through that path, and emit the event clause first (he and
958
+ * zh are both SVO — event leads). Returns `null` (fall through) when the input
959
+ * isn't an object-marking event handler with an un-terminated inline `unless`
960
+ * guard.
961
+ */
962
+ private tryTransformEventWithUnlessGuard;
963
+ /**
964
+ * Transform an event handler whose body leads with a command-modifier
965
+ * (`on <event> [from <src>] {async|once|debounced [at N]|throttled [at N]} <body>`).
966
+ *
967
+ * `parseEventHandler` reads the first token after the event as the **action**, so
968
+ * a leading modifier is mistaken for the verb and the real verb (`fetch`/`add`) is
969
+ * swept into the patient. For SOV targets the reorder then surfaces that verb
970
+ * **first** (`取得 /api/data を クリック …`), and the semantic parser matches the
971
+ * leading `<verb> <patient>` with the low-priority `*-generated-verb-first`
972
+ * command pattern — returning a bare command and discarding the event + the rest
973
+ * of the body (degenerate parse).
974
+ *
975
+ * Instead, lift the modifier out, transform the modifier-free handler through the
976
+ * normal path (which keeps the body in canonical patient-first SOV order so the
977
+ * event sits mid-stream and the existing SOV event-extraction recovers it), then
978
+ * re-emit the modifier as a **leading English literal**. The semantic parser
979
+ * strips a leading `once`/`debounced`/`throttled` (`extractStandaloneModifiers`)
980
+ * and an `async` anywhere (`stripAsyncModifier`) before parsing, so the modifier
981
+ * is consumed as handler metadata rather than shadowing the body.
982
+ *
983
+ * Returns `null` (fall through) when the input isn't an event handler or the body
984
+ * doesn't lead with a modifier — leaving simple/Mode-B handlers byte-identical.
985
+ */
986
+ private tryTransformEventWithModifierBody;
987
+ /**
988
+ * Transform a `set <stuff> on <scope>` clause (S1 tabs-aria). The trailing
989
+ * `on <scope>` is the element(s) the attribute is set on — kept attached by
990
+ * splitOnCommandBoundaries. The semantic parser captures it as a `scope` role
991
+ * via the passthrough literal `on` (setSchema's scope markerOverride is `on`
992
+ * in every language), so `on` is emitted verbatim and only the scope *value*
993
+ * is translated (selectors pass through; `me`/`it`/`you` translate to the
994
+ * native reference, which the parser also accepts).
995
+ *
996
+ * Positioning matches where the set patterns expect the scope: at the clause
997
+ * end for verb-first orders (SVO/VSO), and immediately before the clause-final
998
+ * verb for SOV (the generated SOV pattern is `{dest} {patient} on {scope}
999
+ * {verb}`). Returns null (fall through) when there is no trailing `on <scope>`.
1000
+ *
1001
+ * Source is English in the sync-translations pipeline, so the `set` verb and
1002
+ * `on` marker are matched as English literals.
1003
+ */
1004
+ private transformSetWithScope;
1005
+ /**
1006
+ * Transform a self-contained block command (`{head} {clause?} {body} {end}`),
1007
+ * where head ∈ {if, repeat, unless, …}. The clause (condition / `until event …`)
1008
+ * runs up to the first command verb and is translated word-by-word; the body is
1009
+ * recursively transformed (so its inner commands reorder for the target); the
1010
+ * head/tail keywords are translated. The block is never word-order reordered as
1011
+ * a whole — delimiters stay at the edges regardless of target word order.
1012
+ */
1013
+ private transformBlockBody;
1014
+ /**
1015
+ * Transform an `if`/`unless` block body, splitting it at a top-level `else` into
1016
+ * a then-branch and an else-branch so each is reordered as a self-contained unit
1017
+ * and the `else` keyword itself is translated. Without this, the body is reordered
1018
+ * as one stream: `else` rides along glued to the preceding clause (and, when that
1019
+ * clause begins with a selector, is marked a selector and left *untranslated*),
1020
+ * and a spurious `then` is inserted around it — both of which break the target
1021
+ * text and the downstream parse. The split is depth-aware so an `else` belonging
1022
+ * to a nested block is not mistaken for this block's separator. Bodies without an
1023
+ * `else` transform exactly as before.
1024
+ */
1025
+ private transformConditionalBody;
908
1026
  /**
909
1027
  * Translate a reactive block by translating the head/tail/connector
910
1028
  * via the dictionary, recursively transforming the body through the
@@ -965,4 +1083,4 @@ declare const examples: {
965
1083
  };
966
1084
  };
967
1085
 
968
- export { joinTokens as $, type AdpositionType as A, frKeywords as B, frenchProfile as C, germanProfile as D, ENGLISH_COMMANDS as E, getProfile as F, type GrammarRule as G, getSupportedLocales as H, hebrewProfile as I, hiKeywords as J, type KeywordProvider as K, LANGUAGE_FAMILY_DEFAULTS as L, type MorphologyType as M, hindiDictionary as N, id as O, type ParsedElement as P, idKeywords as Q, indonesianProfile as R, type SemanticRole as S, insertMarkers as T, UNIVERSAL_ENGLISH_KEYWORDS as U, it as V, type WordOrder as W, itKeywords as X, ja as Y, jaKeywords as Z, japaneseProfile as _, ENGLISH_KEYWORDS as a, ko as a0, koKeywords as a1, koreanProfile as a2, ms as a3, msKeywords as a4, parseStatement as a5, pl as a6, plKeywords as a7, portugueseProfile as a8, profiles as a9, viKeywords as aA, zh as aB, zhKeywords as aC, pt as aa, ptKeywords as ab, qu as ac, quKeywords as ad, quechuaProfile as ae, reorderRoles as af, ruKeywords as ag, russianDictionary as ah, spanishProfile as ai, sw as aj, swKeywords as ak, swahiliProfile as al, th as am, thKeywords as an, tl as ao, tlKeywords as ap, toEnglish as aq, toLocale as ar, tr as as, trKeywords as at, transformStatement as au, translate as av, turkishProfile as aw, ukKeywords as ax, ukrainianDictionary as ay, vi as az, GrammarTransformer as b, type GrammaticalMarker as c, type KeywordProviderOptions as d, type LanguageProfile as e, LocaleManager as f, type ParsedStatement as g, type PatternMatcher as h, type PatternTransform as i, UNIVERSAL_PATTERNS as j, ar as k, arKeywords as l, arabicProfile as m, bn as n, bnKeywords as o, chineseProfile as p, createEnglishProvider as q, createKeywordProvider as r, de as s, deKeywords as t, detectBrowserLocale as u, englishProfile as v, es as w, esKeywords as x, examples as y, fr as z };
1086
+ export { joinTokens as $, type AdpositionType as A, frenchProfile as B, germanProfile as C, getProfile as D, ENGLISH_COMMANDS as E, getSupportedLocales as F, type GrammarRule as G, examples as H, hebrewProfile as I, hindiDictionary as J, type KeywordProvider as K, LANGUAGE_FAMILY_DEFAULTS as L, type MorphologyType as M, hiKeywords as N, id as O, type ParsedElement as P, idKeywords as Q, indonesianProfile as R, type SemanticRole as S, insertMarkers as T, UNIVERSAL_ENGLISH_KEYWORDS as U, it as V, type WordOrder as W, itKeywords as X, ja as Y, jaKeywords as Z, japaneseProfile as _, ENGLISH_KEYWORDS as a, ko as a0, koKeywords as a1, koreanProfile as a2, malayProfile as a3, ms as a4, msKeywords as a5, parseStatement as a6, pl as a7, plKeywords as a8, portugueseProfile as a9, vi as aA, viKeywords as aB, zh as aC, zhKeywords as aD, profiles as aa, pt as ab, ptKeywords as ac, qu as ad, quKeywords as ae, quechuaProfile as af, reorderRoles as ag, russianDictionary as ah, ruKeywords as ai, spanishProfile as aj, sw as ak, swKeywords as al, swahiliProfile as am, th as an, thKeywords as ao, tl as ap, tlKeywords as aq, toEnglish as ar, toLocale as as, tr as at, trKeywords as au, transformStatement as av, translate as aw, turkishProfile as ax, ukrainianDictionary as ay, ukKeywords as az, GrammarTransformer as b, type GrammaticalMarker as c, type KeywordProviderOptions as d, type LanguageProfile as e, LocaleManager as f, type ParsedStatement as g, type PatternMatcher as h, type PatternTransform as i, UNIVERSAL_PATTERNS as j, ar as k, arKeywords as l, arabicProfile as m, bn as n, bnKeywords as o, chineseProfile as p, createEnglishProvider as q, createKeywordProvider as r, de as s, deKeywords as t, detectBrowserLocale as u, englishProfile as v, es as w, esKeywords as x, fr as y, frKeywords as z };
@@ -1,4 +1,4 @@
1
- import { a as Dictionary } from './types-BcALAO6h.cjs';
1
+ import { D as Dictionary } from './types-BYtpqGq3.cjs';
2
2
 
3
3
  /**
4
4
  * KeywordProvider interface for locale-aware parsing.
@@ -869,6 +869,7 @@ declare const germanProfile: LanguageProfile;
869
869
  declare const frenchProfile: LanguageProfile;
870
870
  declare const portugueseProfile: LanguageProfile;
871
871
  declare const indonesianProfile: LanguageProfile;
872
+ declare const malayProfile: LanguageProfile;
872
873
  declare const quechuaProfile: LanguageProfile;
873
874
  declare const swahiliProfile: LanguageProfile;
874
875
  declare const hebrewProfile: LanguageProfile;
@@ -901,10 +902,127 @@ declare class GrammarTransformer {
901
902
  * For multi-line input, preserves line structure (indentation, blank lines).
902
903
  */
903
904
  transform(input: string): string;
905
+ private transformInternal;
904
906
  /**
905
907
  * Transform a single hyperscript statement (no compound "then" chains).
906
908
  */
907
909
  private transformSingle;
910
+ /**
911
+ * Detect and transform an inline JS block (`[on <event>] js <raw js> end`).
912
+ *
913
+ * The `js ... end` body is raw JavaScript: it must not be tokenized,
914
+ * translated, or word-order reordered. We mask the whole block with a single
915
+ * opaque placeholder, run the surrounding statement (the event-handler head,
916
+ * if any) through the normal reorder pipeline so the placeholder lands in the
917
+ * correct action position, then substitute the translated `js`/`end` keywords
918
+ * around the verbatim body.
919
+ *
920
+ * Returns `null` (fall through to the normal path) when there is no js block,
921
+ * no matching `end`, or trailing content after `end` (kept tight on purpose).
922
+ */
923
+ private tryTransformJsBlock;
924
+ /**
925
+ * Transform an event handler whose body is a block command
926
+ * (`on <event> [from <src>] {if|repeat|unless|while|for} … end`).
927
+ *
928
+ * `parseEventHandler` would treat the block keyword as the action and sweep the
929
+ * condition/body into role values, then reorder them — shredding the block
930
+ * (`if event.shiftKey call submitAndContinue() end` → scattered tokens). Instead
931
+ * we mask the whole block as an opaque action placeholder, reorder the event
932
+ * head normally, transform the block as a self-contained unit, and restitch.
933
+ *
934
+ * Returns `null` (fall through) when the input isn't an event handler, has no
935
+ * block-keyword body, or has no closing `end`.
936
+ */
937
+ private tryTransformEventWithBlockBody;
938
+ /**
939
+ * Transform an event handler whose body is an inline `unless` guard with NO
940
+ * `end` (`on <event> unless <cond> <body>` — the `unless-condition` shape).
941
+ *
942
+ * Object-marking SVO targets (he, zh). `parseEventHandler` reads `unless` as the
943
+ * action and sweeps the whole `<cond> <body>` tail into a single `patient` blob;
944
+ * the target then prefixes that blob with its object marker — Hebrew's accusative
945
+ * את (`… אלא את I match .disabled מתג .selected`) or Chinese's BA particle 把
946
+ * (`… 除非 把 I match .disabled 切换 .selected`) — and the inner toggle loses its
947
+ * own marker. The semantic parser can't recover the guard from that: the marker
948
+ * ahead of the condition blocks the `unless` pattern AND the now-markerless body
949
+ * command fails its object-marked toggle pattern, so the body collapses (`unless`
950
+ * dropped). Marker-less languages (de/it/ar/pl) tolerate the same role-blob and
951
+ * stay faithful, so this is an object-marker artifact, not a general parse gap.
952
+ *
953
+ * The standalone `unless <cond> <body>` path already produces the correct shape
954
+ * (`extractBlockStructure` → `transformBlock`: condition kept marker-free, body
955
+ * command keeps its marker — he `אלא I match .disabled מתג את .selected`, zh
956
+ * `除非 I match .disabled 切换 把 .selected`). So we split the event head off,
957
+ * transform the guard through that path, and emit the event clause first (he and
958
+ * zh are both SVO — event leads). Returns `null` (fall through) when the input
959
+ * isn't an object-marking event handler with an un-terminated inline `unless`
960
+ * guard.
961
+ */
962
+ private tryTransformEventWithUnlessGuard;
963
+ /**
964
+ * Transform an event handler whose body leads with a command-modifier
965
+ * (`on <event> [from <src>] {async|once|debounced [at N]|throttled [at N]} <body>`).
966
+ *
967
+ * `parseEventHandler` reads the first token after the event as the **action**, so
968
+ * a leading modifier is mistaken for the verb and the real verb (`fetch`/`add`) is
969
+ * swept into the patient. For SOV targets the reorder then surfaces that verb
970
+ * **first** (`取得 /api/data を クリック …`), and the semantic parser matches the
971
+ * leading `<verb> <patient>` with the low-priority `*-generated-verb-first`
972
+ * command pattern — returning a bare command and discarding the event + the rest
973
+ * of the body (degenerate parse).
974
+ *
975
+ * Instead, lift the modifier out, transform the modifier-free handler through the
976
+ * normal path (which keeps the body in canonical patient-first SOV order so the
977
+ * event sits mid-stream and the existing SOV event-extraction recovers it), then
978
+ * re-emit the modifier as a **leading English literal**. The semantic parser
979
+ * strips a leading `once`/`debounced`/`throttled` (`extractStandaloneModifiers`)
980
+ * and an `async` anywhere (`stripAsyncModifier`) before parsing, so the modifier
981
+ * is consumed as handler metadata rather than shadowing the body.
982
+ *
983
+ * Returns `null` (fall through) when the input isn't an event handler or the body
984
+ * doesn't lead with a modifier — leaving simple/Mode-B handlers byte-identical.
985
+ */
986
+ private tryTransformEventWithModifierBody;
987
+ /**
988
+ * Transform a `set <stuff> on <scope>` clause (S1 tabs-aria). The trailing
989
+ * `on <scope>` is the element(s) the attribute is set on — kept attached by
990
+ * splitOnCommandBoundaries. The semantic parser captures it as a `scope` role
991
+ * via the passthrough literal `on` (setSchema's scope markerOverride is `on`
992
+ * in every language), so `on` is emitted verbatim and only the scope *value*
993
+ * is translated (selectors pass through; `me`/`it`/`you` translate to the
994
+ * native reference, which the parser also accepts).
995
+ *
996
+ * Positioning matches where the set patterns expect the scope: at the clause
997
+ * end for verb-first orders (SVO/VSO), and immediately before the clause-final
998
+ * verb for SOV (the generated SOV pattern is `{dest} {patient} on {scope}
999
+ * {verb}`). Returns null (fall through) when there is no trailing `on <scope>`.
1000
+ *
1001
+ * Source is English in the sync-translations pipeline, so the `set` verb and
1002
+ * `on` marker are matched as English literals.
1003
+ */
1004
+ private transformSetWithScope;
1005
+ /**
1006
+ * Transform a self-contained block command (`{head} {clause?} {body} {end}`),
1007
+ * where head ∈ {if, repeat, unless, …}. The clause (condition / `until event …`)
1008
+ * runs up to the first command verb and is translated word-by-word; the body is
1009
+ * recursively transformed (so its inner commands reorder for the target); the
1010
+ * head/tail keywords are translated. The block is never word-order reordered as
1011
+ * a whole — delimiters stay at the edges regardless of target word order.
1012
+ */
1013
+ private transformBlockBody;
1014
+ /**
1015
+ * Transform an `if`/`unless` block body, splitting it at a top-level `else` into
1016
+ * a then-branch and an else-branch so each is reordered as a self-contained unit
1017
+ * and the `else` keyword itself is translated. Without this, the body is reordered
1018
+ * as one stream: `else` rides along glued to the preceding clause (and, when that
1019
+ * clause begins with a selector, is marked a selector and left *untranslated*),
1020
+ * and a spurious `then` is inserted around it — both of which break the target
1021
+ * text and the downstream parse. The split is depth-aware so an `else` belonging
1022
+ * to a nested block is not mistaken for this block's separator. Bodies without an
1023
+ * `else` transform exactly as before.
1024
+ */
1025
+ private transformConditionalBody;
908
1026
  /**
909
1027
  * Translate a reactive block by translating the head/tail/connector
910
1028
  * via the dictionary, recursively transforming the body through the
@@ -965,4 +1083,4 @@ declare const examples: {
965
1083
  };
966
1084
  };
967
1085
 
968
- export { joinTokens as $, type AdpositionType as A, frKeywords as B, frenchProfile as C, germanProfile as D, ENGLISH_COMMANDS as E, getProfile as F, type GrammarRule as G, getSupportedLocales as H, hebrewProfile as I, hiKeywords as J, type KeywordProvider as K, LANGUAGE_FAMILY_DEFAULTS as L, type MorphologyType as M, hindiDictionary as N, id as O, type ParsedElement as P, idKeywords as Q, indonesianProfile as R, type SemanticRole as S, insertMarkers as T, UNIVERSAL_ENGLISH_KEYWORDS as U, it as V, type WordOrder as W, itKeywords as X, ja as Y, jaKeywords as Z, japaneseProfile as _, ENGLISH_KEYWORDS as a, ko as a0, koKeywords as a1, koreanProfile as a2, ms as a3, msKeywords as a4, parseStatement as a5, pl as a6, plKeywords as a7, portugueseProfile as a8, profiles as a9, viKeywords as aA, zh as aB, zhKeywords as aC, pt as aa, ptKeywords as ab, qu as ac, quKeywords as ad, quechuaProfile as ae, reorderRoles as af, ruKeywords as ag, russianDictionary as ah, spanishProfile as ai, sw as aj, swKeywords as ak, swahiliProfile as al, th as am, thKeywords as an, tl as ao, tlKeywords as ap, toEnglish as aq, toLocale as ar, tr as as, trKeywords as at, transformStatement as au, translate as av, turkishProfile as aw, ukKeywords as ax, ukrainianDictionary as ay, vi as az, GrammarTransformer as b, type GrammaticalMarker as c, type KeywordProviderOptions as d, type LanguageProfile as e, LocaleManager as f, type ParsedStatement as g, type PatternMatcher as h, type PatternTransform as i, UNIVERSAL_PATTERNS as j, ar as k, arKeywords as l, arabicProfile as m, bn as n, bnKeywords as o, chineseProfile as p, createEnglishProvider as q, createKeywordProvider as r, de as s, deKeywords as t, detectBrowserLocale as u, englishProfile as v, es as w, esKeywords as x, examples as y, fr as z };
1086
+ export { joinTokens as $, type AdpositionType as A, frenchProfile as B, germanProfile as C, getProfile as D, ENGLISH_COMMANDS as E, getSupportedLocales as F, type GrammarRule as G, examples as H, hebrewProfile as I, hindiDictionary as J, type KeywordProvider as K, LANGUAGE_FAMILY_DEFAULTS as L, type MorphologyType as M, hiKeywords as N, id as O, type ParsedElement as P, idKeywords as Q, indonesianProfile as R, type SemanticRole as S, insertMarkers as T, UNIVERSAL_ENGLISH_KEYWORDS as U, it as V, type WordOrder as W, itKeywords as X, ja as Y, jaKeywords as Z, japaneseProfile as _, ENGLISH_KEYWORDS as a, ko as a0, koKeywords as a1, koreanProfile as a2, malayProfile as a3, ms as a4, msKeywords as a5, parseStatement as a6, pl as a7, plKeywords as a8, portugueseProfile as a9, vi as aA, viKeywords as aB, zh as aC, zhKeywords as aD, profiles as aa, pt as ab, ptKeywords as ac, qu as ad, quKeywords as ae, quechuaProfile as af, reorderRoles as ag, russianDictionary as ah, ruKeywords as ai, spanishProfile as aj, sw as ak, swKeywords as al, swahiliProfile as am, th as an, thKeywords as ao, tl as ap, tlKeywords as aq, toEnglish as ar, toLocale as as, tr as at, trKeywords as au, transformStatement as av, translate as aw, turkishProfile as ax, ukrainianDictionary as ay, ukKeywords as az, GrammarTransformer as b, type GrammaticalMarker as c, type KeywordProviderOptions as d, type LanguageProfile as e, LocaleManager as f, type ParsedStatement as g, type PatternMatcher as h, type PatternTransform as i, UNIVERSAL_PATTERNS as j, ar as k, arKeywords as l, arabicProfile as m, bn as n, bnKeywords as o, chineseProfile as p, createEnglishProvider as q, createKeywordProvider as r, de as s, deKeywords as t, detectBrowserLocale as u, englishProfile as v, es as w, esKeywords as x, fr as y, frKeywords as z };
@@ -119,4 +119,4 @@ interface TranslationResult {
119
119
  warnings?: string[];
120
120
  }
121
121
 
122
- export { DICTIONARY_CATEGORIES as D, type I18nConfig as I, type LocaleMetadata as L, type Token as T, type ValidationError as V, type Dictionary as a, type DictionaryCategory as b, type TokenType as c, type TranslationContext as d, type TranslationOptions as e, type TranslationResult as f, type ValidationResult as g, type ValidationWarning as h, findInDictionary as i, forEachCategory as j, getDictionaryCategory as k, isDictionaryCategory as l, translateFromEnglish as t };
122
+ export { type Dictionary as D, type I18nConfig as I, type LocaleMetadata as L, type TranslationOptions as T, type ValidationResult as V, type TranslationResult as a, type Token as b, DICTIONARY_CATEGORIES as c, type DictionaryCategory as d, type TokenType as e, type TranslationContext as f, type ValidationError as g, type ValidationWarning as h, findInDictionary as i, forEachCategory as j, getDictionaryCategory as k, isDictionaryCategory as l, translateFromEnglish as t };
@@ -119,4 +119,4 @@ interface TranslationResult {
119
119
  warnings?: string[];
120
120
  }
121
121
 
122
- export { DICTIONARY_CATEGORIES as D, type I18nConfig as I, type LocaleMetadata as L, type Token as T, type ValidationError as V, type Dictionary as a, type DictionaryCategory as b, type TokenType as c, type TranslationContext as d, type TranslationOptions as e, type TranslationResult as f, type ValidationResult as g, type ValidationWarning as h, findInDictionary as i, forEachCategory as j, getDictionaryCategory as k, isDictionaryCategory as l, translateFromEnglish as t };
122
+ export { type Dictionary as D, type I18nConfig as I, type LocaleMetadata as L, type TranslationOptions as T, type ValidationResult as V, type TranslationResult as a, type Token as b, DICTIONARY_CATEGORIES as c, type DictionaryCategory as d, type TokenType as e, type TranslationContext as f, type ValidationError as g, type ValidationWarning as h, findInDictionary as i, forEachCategory as j, getDictionaryCategory as k, isDictionaryCategory as l, translateFromEnglish as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lokascript/i18n",
3
- "version": "2.5.1",
3
+ "version": "2.6.0",
4
4
  "description": "Grammar transformation and keyword translation for hyperscript",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -37,9 +37,9 @@
37
37
  "build": "tsup && npm run build:browser",
38
38
  "build:browser": "rollup -c rollup.config.mjs",
39
39
  "dev": "tsup --watch",
40
- "test": "vitest",
41
- "test:check": "vitest run --reporter=dot 2>&1 | tail -5",
42
- "test:coverage": "vitest run --coverage",
40
+ "test": "bash ../../scripts/vitest-run.sh",
41
+ "test:check": "VITEST_QUIET=1 bash ../../scripts/vitest-run.sh --reporter=dot",
42
+ "test:coverage": "bash ../../scripts/vitest-run.sh --coverage",
43
43
  "typecheck": "tsc --noEmit",
44
44
  "lint": "eslint src --ext ts,tsx",
45
45
  "validate-dictionaries": "node scripts/validate-dictionaries.js"
@@ -65,8 +65,8 @@
65
65
  },
66
66
  "homepage": "https://github.com/codetalcott/hyperfixi/tree/main/packages/i18n#readme",
67
67
  "dependencies": {
68
- "@hyperfixi/core": "^2.5.1",
69
- "@lokascript/semantic": "^2.5.1",
68
+ "@hyperfixi/core": "^2.6.0",
69
+ "@lokascript/semantic": "^2.6.0",
70
70
  "esbuild": "^0.28.0",
71
71
  "happy-dom": "^20.8.9",
72
72
  "vite": "^7.1.12"
package/src/browser.ts CHANGED
@@ -127,6 +127,7 @@ export {
127
127
  frenchProfile,
128
128
  portugueseProfile,
129
129
  indonesianProfile,
130
+ malayProfile,
130
131
  quechuaProfile,
131
132
  swahiliProfile,
132
133
  hebrewProfile,
@@ -0,0 +1,59 @@
1
+ /**
2
+ * COMMAND_PRIMARY_ROLES ↔ semantic schema alignment
3
+ *
4
+ * `COMMAND_PRIMARY_ROLES` (constants.ts) is a local mirror of the `primaryRole`
5
+ * field on each `CommandSchema` exported by `@lokascript/semantic`. It's kept
6
+ * local — rather than importing the schemas into the transformer — so the i18n
7
+ * browser bundle doesn't have to pull in the whole semantic graph just to read a
8
+ * handful of role labels.
9
+ *
10
+ * This test guarantees the local copy can't silently drift from the source of
11
+ * truth: every non-`patient` primary role in the schemas must be present (and
12
+ * identical) here, and every entry here must match a schema. The test imports
13
+ * the schemas, but tests don't ship in the bundle.
14
+ *
15
+ * @see packages/i18n/src/constants.ts
16
+ * @see docs-internal/ZH_BLOCK_BODY_SCOPE.md (#1 — transformer role model)
17
+ */
18
+
19
+ import { describe, it, expect } from 'vitest';
20
+ import { commandSchemas } from '@lokascript/semantic';
21
+ import { COMMAND_PRIMARY_ROLES } from './constants';
22
+
23
+ describe('COMMAND_PRIMARY_ROLES ↔ semantic schema alignment', () => {
24
+ const schemaEntries = Object.entries(commandSchemas) as Array<[string, { primaryRole: string }]>;
25
+
26
+ it('every non-patient schema primaryRole is mirrored locally with the same value', () => {
27
+ const missingOrWrong: string[] = [];
28
+ for (const [action, schema] of schemaEntries) {
29
+ if (schema.primaryRole === 'patient') continue;
30
+ if (COMMAND_PRIMARY_ROLES[action] !== schema.primaryRole) {
31
+ missingOrWrong.push(
32
+ `${action}: schema=${schema.primaryRole} local=${COMMAND_PRIMARY_ROLES[action] ?? '(missing)'}`
33
+ );
34
+ }
35
+ }
36
+ expect(
37
+ missingOrWrong,
38
+ `COMMAND_PRIMARY_ROLES (packages/i18n/src/constants.ts) drifted from @lokascript/semantic command schemas:\n ${missingOrWrong.join('\n ')}\nUpdate the local map to match.`
39
+ ).toEqual([]);
40
+ });
41
+
42
+ it('every local entry matches a schema primaryRole (no stale/patient entries)', () => {
43
+ const stale: string[] = [];
44
+ for (const [action, role] of Object.entries(COMMAND_PRIMARY_ROLES)) {
45
+ const schema = commandSchemas[action as keyof typeof commandSchemas];
46
+ if (!schema) {
47
+ stale.push(`${action}: no such command schema`);
48
+ } else if (schema.primaryRole === 'patient') {
49
+ stale.push(`${action}: schema primaryRole is 'patient' (the default) — drop it`);
50
+ } else if (schema.primaryRole !== role) {
51
+ stale.push(`${action}: local=${role} schema=${schema.primaryRole}`);
52
+ }
53
+ }
54
+ expect(
55
+ stale,
56
+ `COMMAND_PRIMARY_ROLES contains stale entries vs @lokascript/semantic:\n ${stale.join('\n ')}`
57
+ ).toEqual([]);
58
+ });
59
+ });
package/src/constants.ts CHANGED
@@ -29,6 +29,50 @@ export const ENGLISH_MODIFIER_ROLES: Readonly<Record<string, SemanticRole>> = {
29
29
  for: 'duration',
30
30
  } as const;
31
31
 
32
+ /**
33
+ * Maps a command verb to its **primary** semantic role — the role of the
34
+ * command's first/leading argument when no explicit modifier keyword marks it.
35
+ *
36
+ * The generic argument parser in the transformer defaults the first unmarked
37
+ * argument to `patient`, which is correct for the majority of commands
38
+ * (`toggle .x`, `add .x`, …) but wrong for commands whose leading argument is a
39
+ * non-patient — e.g. `wait <duration>`. Marking a duration as a patient emits a
40
+ * spurious object-marker in the target language (Chinese `等待 把 1s`, ungrammatical;
41
+ * Japanese `1s を 待つ`; Korean `1s 를 대기`), which the semantic parser then fails to
42
+ * match — dropping the command.
43
+ *
44
+ * Only commands whose primary role is **not** `patient` are listed here (the
45
+ * `patient` default already covers the rest). Mirrors the `primaryRole` field of
46
+ * the semantic package's command schemas (`@lokascript/semantic`); kept in sync by
47
+ * `schema-alignment.test.ts` so this local copy can't drift without the bundle
48
+ * having to pull in the whole semantic graph.
49
+ *
50
+ * @see packages/i18n/src/schema-alignment.test.ts
51
+ * @see docs-internal/ZH_BLOCK_BODY_SCOPE.md (#1 — transformer role model)
52
+ */
53
+ export const COMMAND_PRIMARY_ROLES: Readonly<Record<string, SemanticRole>> = {
54
+ set: 'destination',
55
+ on: 'event',
56
+ trigger: 'event',
57
+ send: 'event',
58
+ wait: 'duration',
59
+ fetch: 'source',
60
+ get: 'source',
61
+ if: 'condition',
62
+ unless: 'condition',
63
+ while: 'condition',
64
+ repeat: 'loopType',
65
+ go: 'destination',
66
+ scroll: 'destination',
67
+ tell: 'destination',
68
+ default: 'destination',
69
+ swap: 'destination',
70
+ // morph deliberately absent: its schema primaryRole is `patient` (the
71
+ // element being morphed — aligned with the transformer's patient marking
72
+ // in the session-9 role-layout swap), and patient is the default.
73
+ bind: 'destination',
74
+ } as const;
75
+
32
76
  /**
33
77
  * English modifier keywords (derived from ENGLISH_MODIFIER_ROLES)
34
78
  */
@@ -110,6 +154,13 @@ export const ENGLISH_COMMANDS: Set<string> = new Set([
110
154
  'trigger',
111
155
  'unless',
112
156
  'wait',
157
+ // v2.x reactive + realtime + service-worker DSL commands
158
+ 'live',
159
+ 'bind',
160
+ 'eventsource',
161
+ 'socket',
162
+ 'worker',
163
+ 'intercept',
113
164
  ]);
114
165
 
115
166
  // =============================================================================
@@ -19,7 +19,10 @@ export const ar: Dictionary = {
19
19
  hide: 'اخف',
20
20
  show: 'اظهر',
21
21
  if: 'إذا',
22
- unless: 'إلا إذا',
22
+ // Single-token form: the semantic ar tokenizer splits the two-word `إلا إذا`
23
+ // into `إلا` + `إذا`(if), which derails the `unless` block. `إلا` alone is the
24
+ // conventional Arabic shorthand and matches the ar profile's unless keyword.
25
+ unless: 'إلا',
23
26
  repeat: 'كرر',
24
27
  for: 'لكل',
25
28
  while: 'بينما',
@@ -49,19 +52,20 @@ export const ar: Dictionary = {
49
52
  js: 'جافاسكربت',
50
53
  async: 'متزامن',
51
54
  render: 'ارسم',
52
- swap: 'بدّل',
55
+ swap: 'استبدل', // profile primary; 'بدّل' is toggle's word (parsed as toggle)
53
56
  morph: 'حوّل',
54
57
  settle: 'استقر',
55
58
  append: 'ألحق',
56
59
  exit: 'اخرج',
57
60
  install: 'ثبّت',
58
61
  breakpoint: 'نقطة-توقف',
59
- clear: 'امسح',
62
+ clear: 'نظّف', // امسح is a remove alternative in the profile
60
63
  close: 'أغلق',
61
64
  open: 'افتح',
62
65
  select: 'اختر',
63
66
  clone: 'استنسخ',
64
67
  prepend: 'سبق',
68
+ socket: 'مقبس',
65
69
  },
66
70
 
67
71
  modifiers: {
@@ -131,6 +135,7 @@ export const ar: Dictionary = {
131
135
  otherwise: 'خلاف ذلك',
132
136
  end: 'النهاية',
133
137
  live: 'حي',
138
+ bind: 'اربط',
134
139
  changes: 'يتغير',
135
140
  },
136
141
 
@@ -34,7 +34,7 @@ export const bengaliDictionary: Dictionary = {
34
34
  throw: 'নিক্ষেপ',
35
35
  continue: 'চালিয়ে যান',
36
36
  make: 'তৈরি করুন',
37
- append: 'শেষে যোগ',
37
+ append: 'জুড়ুন',
38
38
  prepend: 'শুরুতে যোগ',
39
39
  focus: 'ফোকাস',
40
40
  blur: 'ঝাপসা',
@@ -63,6 +63,7 @@ export const bengaliDictionary: Dictionary = {
63
63
  close: 'বন্ধ',
64
64
  open: 'খুলুন',
65
65
  select: 'নির্বাচন',
66
+ socket: 'সকেট',
66
67
  },
67
68
 
68
69
  modifiers: {
@@ -124,6 +125,7 @@ export const bengaliDictionary: Dictionary = {
124
125
  end: 'শেষ',
125
126
  then: 'তারপর',
126
127
  live: 'লাইভ',
128
+ bind: 'বাইন্ড',
127
129
  changes: 'পরিবর্তিত হলে',
128
130
  },
129
131
 
@@ -16,9 +16,17 @@ export const de: Dictionary = {
16
16
  add: 'hinzufügen',
17
17
  remove: 'entfernen',
18
18
  toggle: 'umschalten',
19
+ // command blur (the events section has the blur EVENT word; without this
20
+ // entry the transformer fell back to it and no profile read it as the verb)
21
+ blur: 'defokussieren',
19
22
  hide: 'verstecken',
20
23
  show: 'zeigen',
21
- if: 'wenn',
24
+ // Align with the semantic German profile's `if` primary (`falls`). The previous
25
+ // `wenn` collides with the profile's `when` keyword (German `wenn` = both
26
+ // "if" and "when"), so a transformed `if` resolved to `when` and the conditional
27
+ // wrapper never formed (`if`/`put` dropped). Same dict↔profile alignment as id
28
+ // `toggle` / get-value. `when` is unaffected (separate dict entry).
29
+ if: 'falls',
22
30
  unless: 'wennnicht',
23
31
  repeat: 'wiederholen',
24
32
  for: 'für',
@@ -28,7 +36,7 @@ export const de: Dictionary = {
28
36
  break: 'unterbrechen',
29
37
  halt: 'anhalten',
30
38
  wait: 'warten',
31
- fetch: 'holen',
39
+ fetch: 'abrufen',
32
40
  call: 'aufrufen',
33
41
  return: 'zurückgeben',
34
42
  make: 'erstellen',
@@ -56,12 +64,13 @@ export const de: Dictionary = {
56
64
  exit: 'beenden',
57
65
  install: 'installieren',
58
66
  breakpoint: 'haltepunkt',
59
- clear: 'löschen',
67
+ clear: 'bereinigen', // löschen is a remove alternative in the profile
60
68
  close: 'schließen',
61
69
  open: 'öffnen',
62
70
  select: 'auswählen',
63
71
  clone: 'klonen',
64
72
  prepend: 'voranstellen',
73
+ focus: 'fokussieren',
65
74
  },
66
75
 
67
76
  modifiers: {
@@ -192,7 +201,13 @@ export const de: Dictionary = {
192
201
  prev: 'vorh',
193
202
  at: 'bei',
194
203
  random: 'zufällig',
195
- closest: 'nächste',
204
+ // `nächste` already serves `next` (line above). German `nächste` is
205
+ // genuinely ambiguous (next/nearest), so emitting it for `closest` too
206
+ // collided: the semantic tokenizer normalizes `nächste`→next (a deliberate
207
+ // last-wins choice — see german.ts), and a closest reading is impossible to
208
+ // recover. Use the unambiguous `nächstgelegene` ("nearest-located") for
209
+ // closest so the two readings stay distinct end-to-end.
210
+ closest: 'nächstgelegene',
196
211
  parent: 'elternteil',
197
212
  children: 'kinder',
198
213
  within: 'innerhalb',
@@ -106,6 +106,10 @@ const COMMAND_KEYWORDS = [
106
106
  // Focus
107
107
  'focus',
108
108
  'blur',
109
+ // Streaming / concurrency (postdated the original list — keep in sync with profiles)
110
+ 'socket',
111
+ 'eventsource',
112
+ 'worker',
109
113
  ] as const;
110
114
 
111
115
  /**
@@ -55,6 +55,15 @@ export const en: Dictionary = {
55
55
  decrement: 'decrement',
56
56
  default: 'default',
57
57
 
58
+ // Reactivity Commands
59
+ bind: 'bind',
60
+
61
+ // Realtime / service-worker Commands
62
+ eventsource: 'eventsource',
63
+ socket: 'socket',
64
+ worker: 'worker',
65
+ intercept: 'intercept',
66
+
58
67
  // Navigation Commands
59
68
  go: 'go',
60
69
  pushUrl: 'pushUrl',