@lokascript/i18n 2.9.3 → 2.10.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 (40) hide show
  1. package/dist/browser.cjs +74 -10
  2. package/dist/browser.cjs.map +1 -1
  3. package/dist/browser.d.cts +2 -2
  4. package/dist/browser.d.ts +2 -2
  5. package/dist/browser.js +74 -10
  6. package/dist/browser.js.map +1 -1
  7. package/dist/dictionaries/index.cjs +18 -10
  8. package/dist/dictionaries/index.cjs.map +1 -1
  9. package/dist/dictionaries/index.js +18 -10
  10. package/dist/dictionaries/index.js.map +1 -1
  11. package/dist/index.cjs +74 -10
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +74 -10
  16. package/dist/index.js.map +1 -1
  17. package/dist/lokascript-i18n.min.js +1 -1
  18. package/dist/lokascript-i18n.min.js.map +1 -1
  19. package/dist/lokascript-i18n.mjs +110 -10
  20. package/dist/lokascript-i18n.mjs.map +1 -1
  21. package/dist/plugins/vite.cjs +18 -10
  22. package/dist/plugins/vite.cjs.map +1 -1
  23. package/dist/plugins/vite.js +18 -10
  24. package/dist/plugins/vite.js.map +1 -1
  25. package/dist/plugins/webpack.cjs +18 -10
  26. package/dist/plugins/webpack.cjs.map +1 -1
  27. package/dist/plugins/webpack.js +18 -10
  28. package/dist/plugins/webpack.js.map +1 -1
  29. package/dist/{transformer--W46PGaW.d.cts → transformer-CsOeqayN.d.cts} +18 -1
  30. package/dist/{transformer-FU06J6kG.d.ts → transformer-DWCTG1DQ.d.ts} +18 -1
  31. package/package.json +4 -4
  32. package/src/dictionaries/de.ts +3 -1
  33. package/src/dictionaries/es.ts +2 -2
  34. package/src/dictionaries/pl.ts +2 -1
  35. package/src/dictionaries/pt.ts +5 -2
  36. package/src/dictionaries/ru.ts +2 -2
  37. package/src/dictionaries/uk.ts +2 -2
  38. package/src/grammar/grammar.test.ts +87 -0
  39. package/src/grammar/transformer.ts +106 -0
  40. package/src/grammar/types.ts +4 -0
@@ -704,12 +704,12 @@ const es$1 = {
704
704
  events: {
705
705
  click: 'clic',
706
706
  dblclick: 'dobleclic',
707
- mousedown: 'rat\u00f3nabajo',
708
- mouseup: 'rat\u00f3narriba',
709
707
  mouseenter: 'rat\u00f3nentrar',
710
708
  mouseleave: 'rat\u00f3nsalir',
711
709
  // V3 Batch 2: fused forms split to the S5b/tokenizer-known forms (the fused
712
710
  // spellings captured event=expression:undefined \u2014 broken listeners)
711
+ mousedown: 'rat\u00f3n abajo',
712
+ mouseup: 'rat\u00f3n arriba',
713
713
  mouseover: 'rat\u00f3n encima',
714
714
  mouseout: 'rat\u00f3n fuera',
715
715
  mousemove: 'rat\u00f3nmover',
@@ -1452,7 +1452,9 @@ const de$1 = {
1452
1452
  reset: 'zur\u00fccksetzen',
1453
1453
  load: 'laden',
1454
1454
  unload: 'entladen',
1455
- resize: 'gr\u00f6\u00dfe\u00e4ndern',
1455
+ // 'gr\u00f6\u00dfe\u00e4ndern' was a malformed compound: German either separates the verb
1456
+ // phrase ('Gr\u00f6\u00dfe \u00e4ndern') or compounds with the linking -n- ('Gr\u00f6\u00dfen\u00e4nderung').
1457
+ resize: 'gr\u00f6\u00dfen\u00e4nderung',
1456
1458
  scroll: 'scrollen',
1457
1459
  touchstart: 'ber\u00fchrungstart',
1458
1460
  touchend: 'ber\u00fchrungend',
@@ -3461,12 +3463,15 @@ const pt$1 = {
3461
3463
  events: {
3462
3464
  click: 'clique',
3463
3465
  dblclick: 'duploClique',
3464
- mousedown: 'mouseBaixo',
3465
- mouseup: 'mouseCima',
3466
3466
  mouseenter: 'mouseEntrar',
3467
3467
  mouseleave: 'mouseSair',
3468
3468
  // V3 Batch 2: camelCase forms split to the S5b/tokenizer-known forms (the
3469
3469
  // fused spellings captured event=expression:undefined \u2014 broken listeners)
3470
+ // pt-BR idiom is "pressionado"/"solto", not the spatial baixo/cima calque \u2014
3471
+ // cf. MDN pt-BR ("um bot\u00e3o ... \u00e9 pressionado") and Scratch pt-BR
3472
+ // ("mouse pressionado?"). The calques stay as profile parse alternatives.
3473
+ mousedown: 'mouse pressionado',
3474
+ mouseup: 'mouse solto',
3470
3475
  mouseover: 'mouse sobre',
3471
3476
  mouseout: 'mouse fora',
3472
3477
  mousemove: 'mouseMover',
@@ -4155,7 +4160,8 @@ const pl$1 = {
4155
4160
  reset: 'zresetuj', // Batch 3: resetuj captured on.event as expression (broken listener) \u2014 profile word round-trips
4156
4161
  load: 'za\u0142aduj',
4157
4162
  unload: 'wy\u0142aduj',
4158
- resize: 'zmie\u0144rozmiar',
4163
+ // 'zmie\u0144rozmiar' was a fused two-word phrase; Polish writes 'zmiana rozmiaru'.
4164
+ resize: 'zmiana rozmiaru',
4159
4165
  scroll: 'przewi\u0144',
4160
4166
  touchstart: 'dotykstart',
4161
4167
  touchend: 'dotykkoniec',
@@ -4317,7 +4323,7 @@ const russianDictionary = {
4317
4323
  swap: '\u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c',
4318
4324
  morph: '\u0442\u0440\u0430\u043d\u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c',
4319
4325
  settle: '\u0441\u0442\u0430\u0431\u0438\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c',
4320
- append: '\u0434\u043e\u043f\u0438\u0441\u0430\u0442\u044c', // profile alternative; the compound splits and \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c parses as add
4326
+ append: '\u0434\u043e\u043f\u0438\u0441\u0430\u0442\u044c', // matches the ru profile primary
4321
4327
  exit: '\u0432\u044b\u0439\u0442\u0438',
4322
4328
  else: '\u0438\u043d\u0430\u0447\u0435',
4323
4329
  install: '\u0438\u043d\u0441\u0442\u0430\u043b\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c', // loanword \u2014 `\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c` is `set` (homonym); see russian profile
@@ -4326,7 +4332,7 @@ const russianDictionary = {
4326
4332
  focus: '\u0441\u0444\u043e\u043a\u0443\u0441\u0438\u0440\u043e\u0432\u0430\u0442\u044c',
4327
4333
  blur: '\u0440\u0430\u0437\u043c\u044b\u0442\u044c',
4328
4334
  clone: '\u043a\u043b\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c',
4329
- prepend: '\u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c_\u0432_\u043d\u0430\u0447\u0430\u043b\u043e',
4335
+ prepend: '\u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u043d\u0430\u0447\u0430\u043b\u043e', // natural form; the underscore compound was never natural Russian
4330
4336
  breakpoint: '\u0442\u043e\u0447\u043a\u0430-\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0430',
4331
4337
  clear: '\u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c',
4332
4338
  close: '\u0437\u0430\u043a\u0440\u044b\u0442\u044c',
@@ -4558,7 +4564,7 @@ const ukrainianDictionary = {
4558
4564
  swap: '\u043f\u043e\u043c\u0456\u043d\u044f\u0442\u0438',
4559
4565
  morph: '\u0442\u0440\u0430\u043d\u0441\u0444\u043e\u0440\u043c\u0443\u0432\u0430\u0442\u0438',
4560
4566
  settle: '\u0441\u0442\u0430\u0431\u0456\u043b\u0456\u0437\u0443\u0432\u0430\u0442\u0438',
4561
- append: '\u0434\u043e\u043f\u0438\u0441\u0430\u0442\u0438', // profile alternative; the compound splits and \u0434\u043e\u0434\u0430\u0442\u0438 parses as add
4567
+ append: '\u0434\u043e\u043f\u0438\u0441\u0430\u0442\u0438', // matches the uk profile primary
4562
4568
  exit: '\u0432\u0438\u0439\u0442\u0438',
4563
4569
  else: '\u0456\u043d\u0430\u043a\u0448\u0435',
4564
4570
  install: '\u0456\u043d\u0441\u0442\u0430\u043b\u044e\u0432\u0430\u0442\u0438', // loanword \u2014 `\u0432\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0438` is `set` (homonym); see ukrainian profile
@@ -4567,7 +4573,7 @@ const ukrainianDictionary = {
4567
4573
  focus: '\u0441\u0444\u043e\u043a\u0443\u0441\u0443\u0432\u0430\u0442\u0438',
4568
4574
  blur: '\u0440\u043e\u0437\u0444\u043e\u043a\u0443\u0441\u0443\u0432\u0430\u0442\u0438',
4569
4575
  clone: '\u043a\u043b\u043e\u043d\u0443\u0432\u0430\u0442\u0438',
4570
- prepend: '\u0434\u043e\u0434\u0430\u0442\u0438_\u043d\u0430_\u043f\u043e\u0447\u0430\u0442\u043e\u043a',
4576
+ prepend: '\u0434\u043e\u0434\u0430\u0442\u0438 \u043d\u0430 \u043f\u043e\u0447\u0430\u0442\u043e\u043a', // natural form; see ru.ts
4571
4577
  breakpoint: '\u0442\u043e\u0447\u043a\u0430-\u0437\u0443\u043f\u0438\u043d\u043a\u0438',
4572
4578
  clear: '\u043e\u0447\u0438\u0441\u0442\u0438\u0442\u0438',
4573
4579
  close: '\u0437\u0430\u043a\u0440\u0438\u0442\u0438',
@@ -10099,6 +10105,50 @@ function restoreCaretScopes(input, scopes) {
10099
10105
  return input.replace(new RegExp(`${CARET_SCOPE_OPEN}(\\d+)${CARET_SCOPE_CLOSE}`, 'g'), (_m, n) => scopes[Number(n)] ?? '');
10100
10106
  }
10101
10107
  // =============================================================================
10108
+ // View-transition tail masking (`using view transition`)
10109
+ // =============================================================================
10110
+ /** Private-use sentinels bracketing a masked view-transition-tail index. */
10111
+ const VIEW_TAIL_OPEN = '\uE002';
10112
+ const VIEW_TAIL_CLOSE = '\uE003';
10113
+ /**
10114
+ * Match `swap`/`process`'s trailing `using view transition` modifier.
10115
+ *
10116
+ * `using` is in no dictionary and in no role table, so it sweeps into whatever
10117
+ * role phrase is open; `transition` IS a translated command keyword in every
10118
+ * dictionary (es `transici\u00f3n`, de `\u00fcbergang`, ja `\u9077\u79fb`) and is in
10119
+ * `ENGLISH_COMMANDS`, so `splitOnCommandBoundaries` splits the clause there and
10120
+ * the rejoin plants a phantom translated `transition` COMMAND after the target's
10121
+ * `then`-connective (`intercambiar #a con #b using view entonces transici\u00f3n`).
10122
+ *
10123
+ * The phrase has no native form in any of the 24 languages \u2014 semantic's
10124
+ * `USING_VIEW_MARKER_ALL_LANGS` matches the literal English `using view` marker
10125
+ * everywhere \u2014 so it is a passthrough, matched on the English surface regardless
10126
+ * of source locale. The value word is optional so a bare `using view` still
10127
+ * masks rather than half-splitting; `then` is excluded so a clause boundary is
10128
+ * never swallowed into the tail.
10129
+ */
10130
+ const VIEW_TAIL_RE = /\busing\s+view\b(?:\s+(?!then\b)[A-Za-z][\w-]*)?/gi;
10131
+ /** Whether a token is a masked view-transition tail. */
10132
+ const VIEW_TAIL_TOKEN_RE = new RegExp(`^${VIEW_TAIL_OPEN}(\\d+)${VIEW_TAIL_CLOSE}$`);
10133
+ /**
10134
+ * Mask every `using view <value>` tail behind an opaque token, so the phrase
10135
+ * never reaches the splitter, the word translator, or the role parser. Returns
10136
+ * null when there's nothing to mask.
10137
+ */
10138
+ function maskViewTransitionTails(input) {
10139
+ const tails = [];
10140
+ const masked = input.replace(VIEW_TAIL_RE, match => {
10141
+ const idx = tails.length;
10142
+ tails.push(match);
10143
+ return `${VIEW_TAIL_OPEN}${idx}${VIEW_TAIL_CLOSE}`;
10144
+ });
10145
+ return tails.length > 0 ? { masked, tails } : null;
10146
+ }
10147
+ /** Restore masked view-transition tokens to their verbatim English phrase. */
10148
+ function restoreViewTransitionTails(input, tails) {
10149
+ return input.replace(new RegExp(`${VIEW_TAIL_OPEN}(\\d+)${VIEW_TAIL_CLOSE}`, 'g'), (_m, n) => tails[Number(n)] ?? '');
10150
+ }
10151
+ // =============================================================================
10102
10152
  // Main Transformer
10103
10153
  // =============================================================================
10104
10154
  class GrammarTransformer {
@@ -10127,6 +10177,16 @@ class GrammarTransformer {
10127
10177
  return this.targetProfile.code === 'he' ? repairHebrewFrontedAccusative(out) : out;
10128
10178
  }
10129
10179
  transformInternal(input) {
10180
+ // `using view transition` is a passthrough phrase, not translatable content:
10181
+ // `using` is in no dictionary and `transition` is a COMMAND keyword, so left
10182
+ // in place the splitter tears the clause apart there and the tail renders as
10183
+ // a phantom translated command. Mask it before any splitting/translation and
10184
+ // restore it verbatim; transformSingle re-appends the opaque token at the
10185
+ // clause tail so it lands after the reorder, not inside a role phrase.
10186
+ const viewTails = maskViewTransitionTails(input);
10187
+ if (viewTails) {
10188
+ return restoreViewTransitionTails(this.transformInternal(viewTails.masked), viewTails.tails);
10189
+ }
10130
10190
  // Caret-scoped variable reads (`^name on <selector>`) carry a second,
10131
10191
  // overloaded `on` that the splitter/event-parser would mistake for an event
10132
10192
  // or command boundary \u2014 mangling `put ^count on #host into me`. Mask the
@@ -10215,6 +10275,17 @@ class GrammarTransformer {
10215
10275
  if (setScope !== null) {
10216
10276
  return setScope;
10217
10277
  }
10278
+ // 0c. Masked `using view transition` tail (see maskViewTransitionTails):
10279
+ // strip the opaque token, transform the clause without it, and re-append
10280
+ // it at the clause tail. Left in the token stream it would be swept into
10281
+ // the open role's VALUE and rendered ahead of that role's marker
10282
+ // (ja `#b using view transition \u3067` instead of `#b \u3067 using view
10283
+ // transition`) \u2014 the same failure mode transformWithTrailingEnd fixes
10284
+ // for a stranded `end`.
10285
+ const viewTail = this.transformWithViewTransitionTail(input);
10286
+ if (viewTail !== null) {
10287
+ return viewTail;
10288
+ }
10218
10289
  // 1. Parse into semantic roles
10219
10290
  const parsed = parseStatement(input, this.sourceProfile.code);
10220
10291
  if (!parsed) {
@@ -10245,6 +10316,35 @@ class GrammarTransformer {
10245
10316
  // 7. Join without markers (still use joinTokens for consistency)
10246
10317
  return joinTokens(reordered.map(e => e.translated || e.value));
10247
10318
  }
10319
+ /**
10320
+ * Clause carrying a masked `using view transition` tail: strip the opaque
10321
+ * token, transform the clause alone, and re-append the token at the very end.
10322
+ *
10323
+ * The tail is a clause-final modifier in every word order the corpus emits:
10324
+ * the semantic side matches it as the literal `using view` marker plus a value
10325
+ * word, and the SOV/VSO event-handler patterns admit it as an optional
10326
+ * TRAILING group (after the with-marked operand). So the target position is
10327
+ * "end of the transformed clause" for all 24 languages \u2014 no per-profile
10328
+ * placement decision, which is what makes this a passthrough rather than a
10329
+ * role.
10330
+ *
10331
+ * Returns null when the clause carries no masked tail, or when the token is
10332
+ * not clause-final (nothing to reposition \u2014 leaving it in place still restores
10333
+ * verbatim English).
10334
+ */
10335
+ transformWithViewTransitionTail(input) {
10336
+ const trimmed = input.trim();
10337
+ const tokens = trimmed.split(/\s+/);
10338
+ if (tokens.length < 2) {
10339
+ return null;
10340
+ }
10341
+ if (!VIEW_TAIL_TOKEN_RE.test(tokens[tokens.length - 1])) {
10342
+ return null;
10343
+ }
10344
+ const tail = tokens[tokens.length - 1];
10345
+ const head = tokens.slice(0, -1).join(' ');
10346
+ return `${this.transformSingle(head)} ${tail}`;
10347
+ }
10248
10348
  /**
10249
10349
  * `<command \u2026> end` fragments: transform the command without its stranded
10250
10350
  * terminator, then re-append the translated terminator as a standalone