@lokascript/i18n 2.9.4 → 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.
- package/dist/browser.cjs +56 -0
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +2 -2
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +56 -0
- package/dist/browser.js.map +1 -1
- package/dist/index.cjs +56 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -1
- package/dist/lokascript-i18n.min.js +1 -1
- package/dist/lokascript-i18n.min.js.map +1 -1
- package/dist/lokascript-i18n.mjs +94 -0
- package/dist/lokascript-i18n.mjs.map +1 -1
- package/dist/{transformer--W46PGaW.d.cts → transformer-CsOeqayN.d.cts} +18 -1
- package/dist/{transformer-FU06J6kG.d.ts → transformer-DWCTG1DQ.d.ts} +18 -1
- package/package.json +3 -3
- package/src/grammar/grammar.test.ts +87 -0
- package/src/grammar/transformer.ts +106 -0
- package/src/grammar/types.ts +4 -0
|
@@ -145,7 +145,7 @@ interface KeywordProviderOptions {
|
|
|
145
145
|
* - Each role has distinct grammatical markers per language (see profiles/index.ts)
|
|
146
146
|
* - Usage percentages based on pattern database analysis
|
|
147
147
|
*/
|
|
148
|
-
type SemanticRole = 'action' | 'agent' | 'patient' | 'source' | 'destination' | 'goal' | 'event' | 'condition' | 'quantity' | 'duration' | 'responseType' | 'method' | 'style' | 'manner' | 'loopType' | 'continues';
|
|
148
|
+
type SemanticRole = 'action' | 'agent' | 'patient' | 'source' | 'destination' | 'goal' | 'recipient' | 'event' | 'condition' | 'quantity' | 'duration' | 'responseType' | 'method' | 'style' | 'manner' | 'loopType' | 'continues';
|
|
149
149
|
/**
|
|
150
150
|
* Word order patterns
|
|
151
151
|
* These represent the major typological categories
|
|
@@ -907,6 +907,23 @@ declare class GrammarTransformer {
|
|
|
907
907
|
* Transform a single hyperscript statement (no compound "then" chains).
|
|
908
908
|
*/
|
|
909
909
|
private transformSingle;
|
|
910
|
+
/**
|
|
911
|
+
* Clause carrying a masked `using view transition` tail: strip the opaque
|
|
912
|
+
* token, transform the clause alone, and re-append the token at the very end.
|
|
913
|
+
*
|
|
914
|
+
* The tail is a clause-final modifier in every word order the corpus emits:
|
|
915
|
+
* the semantic side matches it as the literal `using view` marker plus a value
|
|
916
|
+
* word, and the SOV/VSO event-handler patterns admit it as an optional
|
|
917
|
+
* TRAILING group (after the with-marked operand). So the target position is
|
|
918
|
+
* "end of the transformed clause" for all 24 languages — no per-profile
|
|
919
|
+
* placement decision, which is what makes this a passthrough rather than a
|
|
920
|
+
* role.
|
|
921
|
+
*
|
|
922
|
+
* Returns null when the clause carries no masked tail, or when the token is
|
|
923
|
+
* not clause-final (nothing to reposition — leaving it in place still restores
|
|
924
|
+
* verbatim English).
|
|
925
|
+
*/
|
|
926
|
+
private transformWithViewTransitionTail;
|
|
910
927
|
/**
|
|
911
928
|
* `<command …> end` fragments: transform the command without its stranded
|
|
912
929
|
* terminator, then re-append the translated terminator as a standalone
|
|
@@ -145,7 +145,7 @@ interface KeywordProviderOptions {
|
|
|
145
145
|
* - Each role has distinct grammatical markers per language (see profiles/index.ts)
|
|
146
146
|
* - Usage percentages based on pattern database analysis
|
|
147
147
|
*/
|
|
148
|
-
type SemanticRole = 'action' | 'agent' | 'patient' | 'source' | 'destination' | 'goal' | 'event' | 'condition' | 'quantity' | 'duration' | 'responseType' | 'method' | 'style' | 'manner' | 'loopType' | 'continues';
|
|
148
|
+
type SemanticRole = 'action' | 'agent' | 'patient' | 'source' | 'destination' | 'goal' | 'recipient' | 'event' | 'condition' | 'quantity' | 'duration' | 'responseType' | 'method' | 'style' | 'manner' | 'loopType' | 'continues';
|
|
149
149
|
/**
|
|
150
150
|
* Word order patterns
|
|
151
151
|
* These represent the major typological categories
|
|
@@ -907,6 +907,23 @@ declare class GrammarTransformer {
|
|
|
907
907
|
* Transform a single hyperscript statement (no compound "then" chains).
|
|
908
908
|
*/
|
|
909
909
|
private transformSingle;
|
|
910
|
+
/**
|
|
911
|
+
* Clause carrying a masked `using view transition` tail: strip the opaque
|
|
912
|
+
* token, transform the clause alone, and re-append the token at the very end.
|
|
913
|
+
*
|
|
914
|
+
* The tail is a clause-final modifier in every word order the corpus emits:
|
|
915
|
+
* the semantic side matches it as the literal `using view` marker plus a value
|
|
916
|
+
* word, and the SOV/VSO event-handler patterns admit it as an optional
|
|
917
|
+
* TRAILING group (after the with-marked operand). So the target position is
|
|
918
|
+
* "end of the transformed clause" for all 24 languages — no per-profile
|
|
919
|
+
* placement decision, which is what makes this a passthrough rather than a
|
|
920
|
+
* role.
|
|
921
|
+
*
|
|
922
|
+
* Returns null when the clause carries no masked tail, or when the token is
|
|
923
|
+
* not clause-final (nothing to reposition — leaving it in place still restores
|
|
924
|
+
* verbatim English).
|
|
925
|
+
*/
|
|
926
|
+
private transformWithViewTransitionTail;
|
|
910
927
|
/**
|
|
911
928
|
* `<command …> end` fragments: transform the command without its stranded
|
|
912
929
|
* terminator, then re-append the translated terminator as a standalone
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lokascript/i18n",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "Grammar transformation and keyword translation for hyperscript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
},
|
|
65
65
|
"homepage": "https://github.com/codetalcott/hyperfixi/tree/main/packages/i18n#readme",
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@hyperfixi/core": "^2.
|
|
68
|
-
"@lokascript/semantic": "^2.
|
|
67
|
+
"@hyperfixi/core": "^2.10.0",
|
|
68
|
+
"@lokascript/semantic": "^2.10.0",
|
|
69
69
|
"esbuild": "^0.28.0",
|
|
70
70
|
"happy-dom": "^20.10.6",
|
|
71
71
|
"vite": "^8.1.4"
|
|
@@ -2662,3 +2662,90 @@ describe('SOV if-blocks get a then-connective before a positional branch head (F
|
|
|
2662
2662
|
expect(out).toContain('llulla "Invalid form" ta qillqakuy');
|
|
2663
2663
|
});
|
|
2664
2664
|
});
|
|
2665
|
+
|
|
2666
|
+
// `swap #a with #b using view transition` — the tail the transformer had never
|
|
2667
|
+
// seen. `using` is in no dictionary and no role table, so it swept into whatever
|
|
2668
|
+
// role phrase was open; `transition` IS a translated command keyword in every
|
|
2669
|
+
// dictionary AND is in ENGLISH_COMMANDS, so splitOnCommandBoundaries cut the
|
|
2670
|
+
// clause there and the rejoin planted a phantom translated `transition` COMMAND
|
|
2671
|
+
// after the target's then-connective (`intercambiar #a con #b using view
|
|
2672
|
+
// entonces transición`). The phrase has no native form in any of the 24
|
|
2673
|
+
// languages — semantic matches the literal English `using view` marker
|
|
2674
|
+
// everywhere (USING_VIEW_MARKER_ALL_LANGS) — so it is masked before any
|
|
2675
|
+
// splitting/translation and re-appended verbatim at the clause tail, which is
|
|
2676
|
+
// where every word order's patterns admit it.
|
|
2677
|
+
describe('`using view transition` is a passthrough tail, not translated content', () => {
|
|
2678
|
+
const SRC = 'on click swap #a with #b using view transition';
|
|
2679
|
+
|
|
2680
|
+
// One per word-order family: SVO Romance/Germanic/Slavic, V2, SOV
|
|
2681
|
+
// particle/agglutinative, VSO, and the two object-marking SVO languages.
|
|
2682
|
+
const TRANSLATED_TRANSITION: Array<[string, string]> = [
|
|
2683
|
+
['es', 'transición'],
|
|
2684
|
+
['fr', 'transition'], // fr's own verb form — the passthrough must not re-emit it as a command
|
|
2685
|
+
['de', 'übergang'],
|
|
2686
|
+
['ja', '遷移'],
|
|
2687
|
+
['ko', '전환'],
|
|
2688
|
+
['tr', 'geçiş'],
|
|
2689
|
+
['qu', 'pasay'],
|
|
2690
|
+
['bn', 'সংক্রমণ'],
|
|
2691
|
+
['hi', 'संक्रमण'],
|
|
2692
|
+
['ar', 'انتقال'],
|
|
2693
|
+
['zh', '过渡'],
|
|
2694
|
+
['he', 'מעבר'],
|
|
2695
|
+
['ru', 'анимировать'],
|
|
2696
|
+
['th', 'เปลี่ยนผ่าน'],
|
|
2697
|
+
['vi', 'chuyển tiếp'],
|
|
2698
|
+
['tl', 'transisyon'],
|
|
2699
|
+
];
|
|
2700
|
+
|
|
2701
|
+
for (const [lang, translated] of TRANSLATED_TRANSITION) {
|
|
2702
|
+
it(`[${lang}] emits the English tail verbatim at the clause end`, () => {
|
|
2703
|
+
const out = new GrammarTransformer('en', lang).transform(SRC);
|
|
2704
|
+
expect(out).toMatch(/using view transition$/);
|
|
2705
|
+
// No phantom then-connective, and no translated `transition` command.
|
|
2706
|
+
if (translated !== 'transition') {
|
|
2707
|
+
expect(out).not.toContain(translated);
|
|
2708
|
+
}
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2712
|
+
it('the tail does not displace the clause it modifies (both operands survive)', () => {
|
|
2713
|
+
for (const lang of ['es', 'ja', 'ar', 'tr', 'zh', 'qu']) {
|
|
2714
|
+
const out = new GrammarTransformer('en', lang).transform(SRC);
|
|
2715
|
+
expect(out, lang).toContain('#a');
|
|
2716
|
+
expect(out, lang).toContain('#b');
|
|
2717
|
+
}
|
|
2718
|
+
});
|
|
2719
|
+
|
|
2720
|
+
it('`transition` as a real command keyword still translates (both-ways negative)', () => {
|
|
2721
|
+
// The mask must be anchored on the literal `using view` phrase, not on the
|
|
2722
|
+
// word `transition` — the standalone transition command has to keep its
|
|
2723
|
+
// dictionary form or every animate row would render English.
|
|
2724
|
+
const es = new GrammarTransformer('en', 'es').transform(
|
|
2725
|
+
'on click transition opacity to 0 over 300ms'
|
|
2726
|
+
);
|
|
2727
|
+
expect(es).toContain('transición');
|
|
2728
|
+
const ja = new GrammarTransformer('en', 'ja').transform(
|
|
2729
|
+
'on click transition opacity to 0 over 300ms'
|
|
2730
|
+
);
|
|
2731
|
+
expect(ja).toContain('遷移');
|
|
2732
|
+
});
|
|
2733
|
+
|
|
2734
|
+
it('a `then`-chained clause after the tail still splits (the tail never swallows a boundary)', () => {
|
|
2735
|
+
const out = new GrammarTransformer('en', 'es').transform(
|
|
2736
|
+
'on click swap #a with #b using view transition then log "done"'
|
|
2737
|
+
);
|
|
2738
|
+
expect(out).toContain('using view transition');
|
|
2739
|
+
expect(out).toContain('entonces');
|
|
2740
|
+
expect(out).toContain('"done"');
|
|
2741
|
+
});
|
|
2742
|
+
|
|
2743
|
+
it('`process partials in it using view transition` keeps its tail too', () => {
|
|
2744
|
+
for (const lang of ['es', 'ja', 'tr']) {
|
|
2745
|
+
const out = new GrammarTransformer('en', lang).transform(
|
|
2746
|
+
'process partials in it using view transition'
|
|
2747
|
+
);
|
|
2748
|
+
expect(out, lang).toMatch(/using view transition$/);
|
|
2749
|
+
}
|
|
2750
|
+
});
|
|
2751
|
+
});
|
|
@@ -1778,6 +1778,59 @@ function restoreCaretScopes(input: string, scopes: string[]): string {
|
|
|
1778
1778
|
);
|
|
1779
1779
|
}
|
|
1780
1780
|
|
|
1781
|
+
// =============================================================================
|
|
1782
|
+
// View-transition tail masking (`using view transition`)
|
|
1783
|
+
// =============================================================================
|
|
1784
|
+
|
|
1785
|
+
/** Private-use sentinels bracketing a masked view-transition-tail index. */
|
|
1786
|
+
const VIEW_TAIL_OPEN = '\uE002';
|
|
1787
|
+
const VIEW_TAIL_CLOSE = '\uE003';
|
|
1788
|
+
|
|
1789
|
+
/**
|
|
1790
|
+
* Match `swap`/`process`'s trailing `using view transition` modifier.
|
|
1791
|
+
*
|
|
1792
|
+
* `using` is in no dictionary and in no role table, so it sweeps into whatever
|
|
1793
|
+
* role phrase is open; `transition` IS a translated command keyword in every
|
|
1794
|
+
* dictionary (es `transición`, de `übergang`, ja `遷移`) and is in
|
|
1795
|
+
* `ENGLISH_COMMANDS`, so `splitOnCommandBoundaries` splits the clause there and
|
|
1796
|
+
* the rejoin plants a phantom translated `transition` COMMAND after the target's
|
|
1797
|
+
* `then`-connective (`intercambiar #a con #b using view entonces transición`).
|
|
1798
|
+
*
|
|
1799
|
+
* The phrase has no native form in any of the 24 languages — semantic's
|
|
1800
|
+
* `USING_VIEW_MARKER_ALL_LANGS` matches the literal English `using view` marker
|
|
1801
|
+
* everywhere — so it is a passthrough, matched on the English surface regardless
|
|
1802
|
+
* of source locale. The value word is optional so a bare `using view` still
|
|
1803
|
+
* masks rather than half-splitting; `then` is excluded so a clause boundary is
|
|
1804
|
+
* never swallowed into the tail.
|
|
1805
|
+
*/
|
|
1806
|
+
const VIEW_TAIL_RE = /\busing\s+view\b(?:\s+(?!then\b)[A-Za-z][\w-]*)?/gi;
|
|
1807
|
+
|
|
1808
|
+
/** Whether a token is a masked view-transition tail. */
|
|
1809
|
+
const VIEW_TAIL_TOKEN_RE = new RegExp(`^${VIEW_TAIL_OPEN}(\\d+)${VIEW_TAIL_CLOSE}$`);
|
|
1810
|
+
|
|
1811
|
+
/**
|
|
1812
|
+
* Mask every `using view <value>` tail behind an opaque token, so the phrase
|
|
1813
|
+
* never reaches the splitter, the word translator, or the role parser. Returns
|
|
1814
|
+
* null when there's nothing to mask.
|
|
1815
|
+
*/
|
|
1816
|
+
function maskViewTransitionTails(input: string): { masked: string; tails: string[] } | null {
|
|
1817
|
+
const tails: string[] = [];
|
|
1818
|
+
const masked = input.replace(VIEW_TAIL_RE, match => {
|
|
1819
|
+
const idx = tails.length;
|
|
1820
|
+
tails.push(match);
|
|
1821
|
+
return `${VIEW_TAIL_OPEN}${idx}${VIEW_TAIL_CLOSE}`;
|
|
1822
|
+
});
|
|
1823
|
+
return tails.length > 0 ? { masked, tails } : null;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
/** Restore masked view-transition tokens to their verbatim English phrase. */
|
|
1827
|
+
function restoreViewTransitionTails(input: string, tails: string[]): string {
|
|
1828
|
+
return input.replace(
|
|
1829
|
+
new RegExp(`${VIEW_TAIL_OPEN}(\\d+)${VIEW_TAIL_CLOSE}`, 'g'),
|
|
1830
|
+
(_m, n: string) => tails[Number(n)] ?? ''
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1781
1834
|
// =============================================================================
|
|
1782
1835
|
// Main Transformer
|
|
1783
1836
|
// =============================================================================
|
|
@@ -1813,6 +1866,17 @@ export class GrammarTransformer {
|
|
|
1813
1866
|
}
|
|
1814
1867
|
|
|
1815
1868
|
private transformInternal(input: string): string {
|
|
1869
|
+
// `using view transition` is a passthrough phrase, not translatable content:
|
|
1870
|
+
// `using` is in no dictionary and `transition` is a COMMAND keyword, so left
|
|
1871
|
+
// in place the splitter tears the clause apart there and the tail renders as
|
|
1872
|
+
// a phantom translated command. Mask it before any splitting/translation and
|
|
1873
|
+
// restore it verbatim; transformSingle re-appends the opaque token at the
|
|
1874
|
+
// clause tail so it lands after the reorder, not inside a role phrase.
|
|
1875
|
+
const viewTails = maskViewTransitionTails(input);
|
|
1876
|
+
if (viewTails) {
|
|
1877
|
+
return restoreViewTransitionTails(this.transformInternal(viewTails.masked), viewTails.tails);
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1816
1880
|
// Caret-scoped variable reads (`^name on <selector>`) carry a second,
|
|
1817
1881
|
// overloaded `on` that the splitter/event-parser would mistake for an event
|
|
1818
1882
|
// or command boundary — mangling `put ^count on #host into me`. Mask the
|
|
@@ -1921,6 +1985,18 @@ export class GrammarTransformer {
|
|
|
1921
1985
|
return setScope;
|
|
1922
1986
|
}
|
|
1923
1987
|
|
|
1988
|
+
// 0c. Masked `using view transition` tail (see maskViewTransitionTails):
|
|
1989
|
+
// strip the opaque token, transform the clause without it, and re-append
|
|
1990
|
+
// it at the clause tail. Left in the token stream it would be swept into
|
|
1991
|
+
// the open role's VALUE and rendered ahead of that role's marker
|
|
1992
|
+
// (ja `#b using view transition で` instead of `#b で using view
|
|
1993
|
+
// transition`) — the same failure mode transformWithTrailingEnd fixes
|
|
1994
|
+
// for a stranded `end`.
|
|
1995
|
+
const viewTail = this.transformWithViewTransitionTail(input);
|
|
1996
|
+
if (viewTail !== null) {
|
|
1997
|
+
return viewTail;
|
|
1998
|
+
}
|
|
1999
|
+
|
|
1924
2000
|
// 1. Parse into semantic roles
|
|
1925
2001
|
const parsed = parseStatement(input, this.sourceProfile.code);
|
|
1926
2002
|
if (!parsed) {
|
|
@@ -1963,6 +2039,36 @@ export class GrammarTransformer {
|
|
|
1963
2039
|
return joinTokens(reordered.map(e => e.translated || e.value));
|
|
1964
2040
|
}
|
|
1965
2041
|
|
|
2042
|
+
/**
|
|
2043
|
+
* Clause carrying a masked `using view transition` tail: strip the opaque
|
|
2044
|
+
* token, transform the clause alone, and re-append the token at the very end.
|
|
2045
|
+
*
|
|
2046
|
+
* The tail is a clause-final modifier in every word order the corpus emits:
|
|
2047
|
+
* the semantic side matches it as the literal `using view` marker plus a value
|
|
2048
|
+
* word, and the SOV/VSO event-handler patterns admit it as an optional
|
|
2049
|
+
* TRAILING group (after the with-marked operand). So the target position is
|
|
2050
|
+
* "end of the transformed clause" for all 24 languages — no per-profile
|
|
2051
|
+
* placement decision, which is what makes this a passthrough rather than a
|
|
2052
|
+
* role.
|
|
2053
|
+
*
|
|
2054
|
+
* Returns null when the clause carries no masked tail, or when the token is
|
|
2055
|
+
* not clause-final (nothing to reposition — leaving it in place still restores
|
|
2056
|
+
* verbatim English).
|
|
2057
|
+
*/
|
|
2058
|
+
private transformWithViewTransitionTail(input: string): string | null {
|
|
2059
|
+
const trimmed = input.trim();
|
|
2060
|
+
const tokens = trimmed.split(/\s+/);
|
|
2061
|
+
if (tokens.length < 2) {
|
|
2062
|
+
return null;
|
|
2063
|
+
}
|
|
2064
|
+
if (!VIEW_TAIL_TOKEN_RE.test(tokens[tokens.length - 1])) {
|
|
2065
|
+
return null;
|
|
2066
|
+
}
|
|
2067
|
+
const tail = tokens[tokens.length - 1];
|
|
2068
|
+
const head = tokens.slice(0, -1).join(' ');
|
|
2069
|
+
return `${this.transformSingle(head)} ${tail}`;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
1966
2072
|
/**
|
|
1967
2073
|
* `<command …> end` fragments: transform the command without its stranded
|
|
1968
2074
|
* terminator, then re-append the translated terminator as a standalone
|
package/src/grammar/types.ts
CHANGED
|
@@ -66,6 +66,10 @@ export type SemanticRole =
|
|
|
66
66
|
| 'source' // Origin (from #input, from URL)
|
|
67
67
|
| 'destination' // Target location (into #output, to .class)
|
|
68
68
|
| 'goal' // Target value/state (to 'red', to 100)
|
|
69
|
+
| 'recipient' // Who receives what the action transfers (take .active from .tab for me).
|
|
70
|
+
// Declared for parity with the semantic union; NO i18n profile marks it yet
|
|
71
|
+
// — en still maps `for` to duration lexically and suppresses the marker for
|
|
72
|
+
// pronoun values (see insertMarkers below).
|
|
69
73
|
| 'event' // Trigger (click, input, keydown)
|
|
70
74
|
| 'condition' // Boolean expression (if x > 5)
|
|
71
75
|
// Quantitative roles
|