@lokascript/hyperscript-adapter 2.9.4 → 2.11.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/CHANGELOG.md +21 -1
- package/README.md +16 -11
- package/dist/hyperscript-i18n-ar.global.js +5 -6
- package/dist/hyperscript-i18n-bn.global.js +5 -6
- package/dist/hyperscript-i18n-de.global.js +5 -6
- package/dist/hyperscript-i18n-east-asian.global.js +5 -6
- package/dist/hyperscript-i18n-en.global.js +7 -8
- package/dist/hyperscript-i18n-es.global.js +5 -6
- package/dist/hyperscript-i18n-fr.global.js +5 -6
- package/dist/hyperscript-i18n-he.global.js +5 -6
- package/dist/hyperscript-i18n-hi.global.js +5 -6
- package/dist/hyperscript-i18n-id.global.js +5 -6
- package/dist/hyperscript-i18n-it.global.js +5 -6
- package/dist/hyperscript-i18n-ja.global.js +5 -6
- package/dist/hyperscript-i18n-ko.global.js +5 -6
- package/dist/hyperscript-i18n-lite.global.js +2 -2
- package/dist/hyperscript-i18n-ms.global.js +5 -6
- package/dist/hyperscript-i18n-pl.global.js +5 -6
- package/dist/hyperscript-i18n-pt.global.js +5 -6
- package/dist/hyperscript-i18n-qu.global.js +5 -6
- package/dist/hyperscript-i18n-ru.global.js +5 -6
- package/dist/hyperscript-i18n-slavic.global.js +5 -6
- package/dist/hyperscript-i18n-south-asian.global.js +5 -6
- package/dist/hyperscript-i18n-southeast-asian.global.js +5 -6
- package/dist/hyperscript-i18n-sw.global.js +5 -6
- package/dist/hyperscript-i18n-th.global.js +5 -6
- package/dist/hyperscript-i18n-tl.global.js +5 -6
- package/dist/hyperscript-i18n-tr.global.js +5 -6
- package/dist/hyperscript-i18n-uk.global.js +5 -6
- package/dist/hyperscript-i18n-vi.global.js +5 -6
- package/dist/hyperscript-i18n-western.global.js +5 -6
- package/dist/hyperscript-i18n-zh.global.js +5 -6
- package/dist/hyperscript-i18n.global.js +27 -28
- package/dist/hyperscript-i18n.global.js.map +1 -1
- package/dist/index.cjs +107 -86
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -12
- package/dist/index.d.ts +75 -12
- package/dist/index.js +107 -93
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/attribute-translator.ts +12 -7
- package/src/browser-lite.ts +18 -3
- package/src/generated/syntax-table.ts +4 -4
- package/src/host-validate.ts +63 -0
- package/src/hyperscript-renderer.ts +64 -5
- package/src/index.ts +6 -1
- package/src/language-resolver.ts +15 -3
- package/src/plugin.ts +57 -7
- package/src/preprocessor-core.ts +217 -0
- package/src/preprocessor.ts +14 -215
- package/src/slim-plugin.ts +18 -3
- package/src/slim-preprocessor.ts +16 -154
package/src/preprocessor.ts
CHANGED
|
@@ -1,157 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Preprocessor
|
|
2
|
+
* Preprocessor (full path)
|
|
3
3
|
*
|
|
4
4
|
* Translates non-English hyperscript to English using the semantic parser
|
|
5
5
|
* (with optional i18n fallback), so the original _hyperscript can parse it.
|
|
6
|
+
*
|
|
7
|
+
* The strategy/split/strip skeleton lives in preprocessor-core.ts, shared
|
|
8
|
+
* with the slim path — only HOW one statement is parsed and rendered
|
|
9
|
+
* differs here: semantic's parseSemantic + render('en'), plus a
|
|
10
|
+
* translate() rescue when the parse is confident but yields no node.
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
|
-
import {
|
|
9
|
-
translate,
|
|
10
|
-
render,
|
|
11
|
-
parseSemantic,
|
|
12
|
-
isLanguageRegistered,
|
|
13
|
-
tryGetProfile,
|
|
14
|
-
} from '@lokascript/semantic';
|
|
15
|
-
|
|
16
|
-
export interface PreprocessorConfig {
|
|
17
|
-
/**
|
|
18
|
-
* Minimum confidence threshold for semantic parsing (0-1). Default: 0.5.
|
|
19
|
-
* Can be a single number (applies to all languages) or a per-language map.
|
|
20
|
-
* Per-language thresholds are useful because SOV languages (ja, ko, tr) produce
|
|
21
|
-
* inherently lower confidence scores than SVO languages (es, fr, de).
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* // Single threshold
|
|
25
|
-
* { confidenceThreshold: 0.5 }
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* // Per-language thresholds
|
|
29
|
-
* { confidenceThreshold: { es: 0.7, ja: 0.1, ko: 0.05, '*': 0.5 } }
|
|
30
|
-
*/
|
|
31
|
-
confidenceThreshold: number | Record<string, number>;
|
|
32
|
-
/** Strategy: 'semantic' (default), 'i18n', or 'auto' (semantic then i18n) */
|
|
33
|
-
strategy: 'semantic' | 'i18n' | 'auto';
|
|
34
|
-
/** Whether to return original text on failure. Default: true */
|
|
35
|
-
fallbackToOriginal: boolean;
|
|
36
|
-
/** Optional i18n toEnglish function (loaded dynamically if available) */
|
|
37
|
-
i18nToEnglish?: (input: string, locale: string) => string;
|
|
38
|
-
}
|
|
13
|
+
import { translate, render, parseSemantic, isLanguageRegistered } from '@lokascript/semantic';
|
|
39
14
|
|
|
40
|
-
|
|
15
|
+
import { createPreprocessToEnglish, type PreprocessorConfig } from './preprocessor-core';
|
|
41
16
|
|
|
42
|
-
|
|
43
|
-
confidenceThreshold: DEFAULT_THRESHOLD,
|
|
44
|
-
strategy: 'semantic',
|
|
45
|
-
fallbackToOriginal: true,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Resolve the confidence threshold for a specific language.
|
|
50
|
-
* Supports both a single number and a per-language map with '*' as default.
|
|
51
|
-
*/
|
|
52
|
-
function resolveThreshold(threshold: number | Record<string, number>, lang: string): number {
|
|
53
|
-
if (typeof threshold === 'number') return threshold;
|
|
54
|
-
return threshold[lang] ?? threshold['*'] ?? DEFAULT_THRESHOLD;
|
|
55
|
-
}
|
|
17
|
+
export type { PreprocessorConfig };
|
|
56
18
|
|
|
57
19
|
/**
|
|
58
20
|
* Preprocess non-English hyperscript into English.
|
|
59
21
|
*
|
|
60
22
|
* Uses the semantic parser to parse the input in the source language,
|
|
61
23
|
* then renders back to English. Falls through to i18n if configured.
|
|
62
|
-
*
|
|
63
|
-
* Handles _hyperscript feature prefixes (e.g. "on click", "on every keyup")
|
|
64
|
-
* by stripping them, translating only the command portion, then reassembling.
|
|
65
|
-
*/
|
|
66
|
-
export function preprocessToEnglish(
|
|
67
|
-
src: string,
|
|
68
|
-
lang: string,
|
|
69
|
-
config: Partial<PreprocessorConfig> = {}
|
|
70
|
-
): string {
|
|
71
|
-
// English→English is identity; skip semantic parsing which may mangle the input
|
|
72
|
-
if (lang === 'en') return src;
|
|
73
|
-
|
|
74
|
-
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
75
|
-
|
|
76
|
-
// Try translating the full string first
|
|
77
|
-
const fullResult = tryTranslateWithStrategies(src, lang, cfg);
|
|
78
|
-
if (fullResult !== null) return fullResult;
|
|
79
|
-
|
|
80
|
-
// If full translation failed, try stripping event/feature prefix.
|
|
81
|
-
// _hyperscript attributes often contain "on <event> <commands>" — the semantic
|
|
82
|
-
// parser only understands command syntax, not event declarations.
|
|
83
|
-
const stripped = stripEventPrefix(src);
|
|
84
|
-
if (stripped) {
|
|
85
|
-
const translated = tryTranslateWithStrategies(stripped.commands, lang, cfg);
|
|
86
|
-
if (translated !== null) return stripped.prefix + translated;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Fallback: return original
|
|
90
|
-
return cfg.fallbackToOriginal ? src : src;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Try all configured translation strategies on the input.
|
|
95
|
-
* Returns null if none succeed.
|
|
96
24
|
*/
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
lang
|
|
100
|
-
cfg: PreprocessorConfig
|
|
101
|
-
): string | null {
|
|
102
|
-
// Strategy: semantic-only
|
|
103
|
-
if (cfg.strategy === 'semantic' || cfg.strategy === 'auto') {
|
|
104
|
-
const threshold = resolveThreshold(cfg.confidenceThreshold, lang);
|
|
105
|
-
const result = trySemanticTranslation(src, lang, threshold);
|
|
106
|
-
if (result !== null) return result;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Strategy: i18n fallback (auto mode or i18n-only)
|
|
110
|
-
if ((cfg.strategy === 'auto' || cfg.strategy === 'i18n') && cfg.i18nToEnglish) {
|
|
111
|
-
const result = tryI18nTranslation(src, lang, cfg.i18nToEnglish);
|
|
112
|
-
if (result !== null) return result;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Match _hyperscript event handler prefix: "on [every] <event>[filter][.modifiers] "
|
|
120
|
-
*
|
|
121
|
-
* Examples:
|
|
122
|
-
* "on click toggle .active" → prefix: "on click ", commands: "toggle .active"
|
|
123
|
-
* "on every click toggle .active" → prefix: "on every click ", commands: "toggle .active"
|
|
124
|
-
* "on click.debounce(300) toggle .x" → prefix: "on click.debounce(300) ", commands: "toggle .x"
|
|
125
|
-
* "on keyup[key=='Enter'] set x to 1" → prefix: "on keyup[key=='Enter'] ", commands: "set x to 1"
|
|
126
|
-
* "on click from body toggle .active" → prefix: "on click from body ", commands: "toggle .active"
|
|
127
|
-
*/
|
|
128
|
-
const EVENT_PREFIX_RE =
|
|
129
|
-
/^(on\s+(?:every\s+)?[\w-]+(?:\[.*?\])?(?:\.[\w-]+(?:\([^)]*\))?)*(?:\s+from\s+\S+)?(?:\s+queue\s+\w+)?\s+)/;
|
|
130
|
-
|
|
131
|
-
function stripEventPrefix(src: string): { prefix: string; commands: string } | null {
|
|
132
|
-
const match = src.match(EVENT_PREFIX_RE);
|
|
133
|
-
if (!match) return null;
|
|
134
|
-
const prefix = match[1];
|
|
135
|
-
const commands = src.slice(prefix.length);
|
|
136
|
-
if (!commands) return null;
|
|
137
|
-
return { prefix, commands };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Try semantic translation: parse in source language, render to English.
|
|
142
|
-
* Returns null if confidence is below threshold.
|
|
143
|
-
*/
|
|
144
|
-
function trySemanticTranslation(src: string, lang: string, threshold: number): string | null {
|
|
145
|
-
try {
|
|
146
|
-
// Handle compound statements (split on "then" boundaries and newlines)
|
|
147
|
-
const statements = splitStatements(src, lang);
|
|
148
|
-
if (statements.length > 1) {
|
|
149
|
-
return translateCompound(statements, lang, threshold);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Check if the semantic parser can handle this
|
|
153
|
-
if (!isLanguageRegistered(lang)) return null;
|
|
154
|
-
|
|
25
|
+
export const preprocessToEnglish = createPreprocessToEnglish({
|
|
26
|
+
isLanguageRegistered,
|
|
27
|
+
translateSingle(src, lang, threshold) {
|
|
155
28
|
const result = parseSemantic(src, lang);
|
|
156
29
|
if (result.confidence < threshold || !result.node) {
|
|
157
30
|
// If we got SOME confidence but no node, fall back to translate()
|
|
@@ -163,79 +36,5 @@ function trySemanticTranslation(src: string, lang: string, threshold: number): s
|
|
|
163
36
|
|
|
164
37
|
// Render the semantic node to English
|
|
165
38
|
return render(result.node, 'en');
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Try i18n grammar transformation to English.
|
|
173
|
-
* Returns null if the result is identical to input (no translation happened).
|
|
174
|
-
*/
|
|
175
|
-
function tryI18nTranslation(
|
|
176
|
-
src: string,
|
|
177
|
-
lang: string,
|
|
178
|
-
toEnglish: (input: string, locale: string) => string
|
|
179
|
-
): string | null {
|
|
180
|
-
try {
|
|
181
|
-
const result = toEnglish(src, lang);
|
|
182
|
-
return result !== src ? result : null;
|
|
183
|
-
} catch {
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Get all "then" keyword forms for a language (primary + alternatives).
|
|
190
|
-
* Falls back to English "then" if profile is unavailable.
|
|
191
|
-
*/
|
|
192
|
-
function getThenKeywords(lang: string): string[] {
|
|
193
|
-
const keywords = ['then']; // Always include English
|
|
194
|
-
const profile = tryGetProfile(lang);
|
|
195
|
-
if (profile?.keywords?.then) {
|
|
196
|
-
const kw = profile.keywords.then;
|
|
197
|
-
if (kw.primary && kw.primary !== 'then') keywords.push(kw.primary);
|
|
198
|
-
if (kw.alternatives) {
|
|
199
|
-
for (const alt of kw.alternatives) {
|
|
200
|
-
if (!keywords.includes(alt)) keywords.push(alt);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return keywords;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Split a hyperscript source string into individual statements.
|
|
209
|
-
* Splits on language-specific "then" keywords and newlines.
|
|
210
|
-
*/
|
|
211
|
-
function splitStatements(src: string, lang: string): string[] {
|
|
212
|
-
const thenWords = getThenKeywords(lang);
|
|
213
|
-
// Escape regex special chars and build pattern
|
|
214
|
-
const escaped = thenWords.map(w => w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
215
|
-
const pattern = new RegExp(`\\s+(?:${escaped.join('|')})\\s+`, 'i');
|
|
216
|
-
|
|
217
|
-
// Split on newlines first
|
|
218
|
-
const lines = src
|
|
219
|
-
.split('\n')
|
|
220
|
-
.map(l => l.trim())
|
|
221
|
-
.filter(l => l.length > 0);
|
|
222
|
-
const result: string[] = [];
|
|
223
|
-
for (const line of lines) {
|
|
224
|
-
const parts = line.split(pattern);
|
|
225
|
-
result.push(...parts);
|
|
226
|
-
}
|
|
227
|
-
return result;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Translate compound statements individually and rejoin with " then ".
|
|
232
|
-
*/
|
|
233
|
-
function translateCompound(statements: string[], lang: string, threshold: number): string | null {
|
|
234
|
-
const translated: string[] = [];
|
|
235
|
-
for (const stmt of statements) {
|
|
236
|
-
const result = trySemanticTranslation(stmt.trim(), lang, threshold);
|
|
237
|
-
if (result === null) return null; // If any part fails, fail the whole compound
|
|
238
|
-
translated.push(result);
|
|
239
|
-
}
|
|
240
|
-
return translated.join(' then ');
|
|
241
|
-
}
|
|
39
|
+
},
|
|
40
|
+
});
|
package/src/slim-plugin.ts
CHANGED
|
@@ -10,19 +10,34 @@ import { preprocessToEnglish } from './slim-preprocessor';
|
|
|
10
10
|
import type { PreprocessorConfig } from './preprocessor';
|
|
11
11
|
import type { PluginOptions } from './plugin';
|
|
12
12
|
import { installAttributeTranslator, type HyperscriptHost } from './attribute-translator';
|
|
13
|
+
import { acceptedByHost, warnRejectedOnce, type HyperscriptParseHost } from './host-validate';
|
|
13
14
|
|
|
14
15
|
export type { PluginOptions };
|
|
15
16
|
|
|
16
17
|
export function hyperscriptI18n(options: PluginOptions = {}) {
|
|
17
18
|
return function plugin(hs: unknown): void {
|
|
18
|
-
|
|
19
|
+
const host = hs as HyperscriptHost & HyperscriptParseHost;
|
|
20
|
+
installAttributeTranslator(host, (src, elt) => {
|
|
19
21
|
const lang = resolveLanguageWithOptions(elt, options);
|
|
20
22
|
if (!lang || lang === 'en') return src;
|
|
21
23
|
|
|
22
24
|
const english = preprocessToEnglish(src, lang, options);
|
|
23
25
|
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
+
if (english !== src) {
|
|
27
|
+
// Host-parser validity gate — see plugin.ts / host-validate.ts.
|
|
28
|
+
if (options.validateWithHost !== false && !acceptedByHost(host, english)) {
|
|
29
|
+
if (options.debug) {
|
|
30
|
+
console.log(
|
|
31
|
+
`[hyperscript-i18n] ${lang}: host rejected "${english}" — keeping "${src}"`
|
|
32
|
+
);
|
|
33
|
+
} else {
|
|
34
|
+
warnRejectedOnce(lang, src, english);
|
|
35
|
+
}
|
|
36
|
+
return src;
|
|
37
|
+
}
|
|
38
|
+
if (options.debug) {
|
|
39
|
+
console.log(`[hyperscript-i18n] ${lang}: "${src}" → "${english}"`);
|
|
40
|
+
}
|
|
26
41
|
}
|
|
27
42
|
|
|
28
43
|
return english;
|
package/src/slim-preprocessor.ts
CHANGED
|
@@ -1,172 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Slim Preprocessor
|
|
3
3
|
*
|
|
4
|
-
* Same
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Same skeleton as preprocessor.ts (shared via preprocessor-core.ts) but
|
|
5
|
+
* imports from @lokascript/semantic/core and renders through the custom
|
|
6
|
+
* hyperscript renderer instead of the semantic package's render(). This
|
|
7
|
+
* avoids importing English language data (tokenizer, patterns, profile),
|
|
8
|
+
* saving ~35 KB per per-language bundle.
|
|
8
9
|
*
|
|
9
|
-
* Used by per-language browser bundles for tree-shaking.
|
|
10
|
+
* Used by per-language browser bundles for tree-shaking. Must never
|
|
11
|
+
* import from '@lokascript/semantic' (the full package) at runtime.
|
|
10
12
|
*/
|
|
11
13
|
|
|
12
14
|
// Import from /core — does NOT trigger all-language registration.
|
|
13
15
|
// Languages are registered separately via side-effect imports in bundle entries.
|
|
14
|
-
import {
|
|
15
|
-
parseWithConfidence,
|
|
16
|
-
isLanguageRegistered,
|
|
17
|
-
tryGetProfile,
|
|
18
|
-
} from '@lokascript/semantic/core';
|
|
16
|
+
import { parseWithConfidence, isLanguageRegistered } from '@lokascript/semantic/core';
|
|
19
17
|
|
|
20
18
|
import { renderToHyperscript } from './hyperscript-renderer';
|
|
21
|
-
import type
|
|
22
|
-
|
|
23
|
-
const DEFAULT_THRESHOLD = 0.5;
|
|
24
|
-
|
|
25
|
-
const DEFAULT_CONFIG: PreprocessorConfig = {
|
|
26
|
-
confidenceThreshold: DEFAULT_THRESHOLD,
|
|
27
|
-
strategy: 'semantic',
|
|
28
|
-
fallbackToOriginal: true,
|
|
29
|
-
};
|
|
19
|
+
import { createPreprocessToEnglish, type PreprocessorConfig } from './preprocessor-core';
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
if (typeof threshold === 'number') return threshold;
|
|
33
|
-
return threshold[lang] ?? threshold['*'] ?? DEFAULT_THRESHOLD;
|
|
34
|
-
}
|
|
21
|
+
export type { PreprocessorConfig };
|
|
35
22
|
|
|
36
23
|
/**
|
|
37
|
-
* Preprocess non-English hyperscript into English.
|
|
38
|
-
* Identical to preprocessToEnglish in preprocessor.ts but with slim imports.
|
|
39
|
-
*
|
|
40
|
-
* Handles _hyperscript feature prefixes (e.g. "on click", "on every keyup")
|
|
41
|
-
* by stripping them, translating only the command portion, then reassembling.
|
|
24
|
+
* Preprocess non-English hyperscript into English (slim imports).
|
|
42
25
|
*/
|
|
43
|
-
export
|
|
44
|
-
|
|
45
|
-
lang
|
|
46
|
-
config: Partial<PreprocessorConfig> = {}
|
|
47
|
-
): string {
|
|
48
|
-
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
49
|
-
|
|
50
|
-
// Try translating the full string first
|
|
51
|
-
const fullResult = tryTranslateWithStrategies(src, lang, cfg);
|
|
52
|
-
if (fullResult !== null) return fullResult;
|
|
53
|
-
|
|
54
|
-
// If full translation failed, try stripping event/feature prefix
|
|
55
|
-
const stripped = stripEventPrefix(src);
|
|
56
|
-
if (stripped) {
|
|
57
|
-
const translated = tryTranslateWithStrategies(stripped.commands, lang, cfg);
|
|
58
|
-
if (translated !== null) return stripped.prefix + translated;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return src;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function tryTranslateWithStrategies(
|
|
65
|
-
src: string,
|
|
66
|
-
lang: string,
|
|
67
|
-
cfg: PreprocessorConfig
|
|
68
|
-
): string | null {
|
|
69
|
-
if (cfg.strategy === 'semantic' || cfg.strategy === 'auto') {
|
|
70
|
-
const threshold = resolveThreshold(cfg.confidenceThreshold, lang);
|
|
71
|
-
const result = trySemanticTranslation(src, lang, threshold);
|
|
72
|
-
if (result !== null) return result;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if ((cfg.strategy === 'auto' || cfg.strategy === 'i18n') && cfg.i18nToEnglish) {
|
|
76
|
-
const result = tryI18nTranslation(src, lang, cfg.i18nToEnglish);
|
|
77
|
-
if (result !== null) return result;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Match _hyperscript event handler prefix. */
|
|
84
|
-
const EVENT_PREFIX_RE =
|
|
85
|
-
/^(on\s+(?:every\s+)?[\w-]+(?:\[.*?\])?(?:\.[\w-]+(?:\([^)]*\))?)*(?:\s+from\s+\S+)?(?:\s+queue\s+\w+)?\s+)/;
|
|
86
|
-
|
|
87
|
-
function stripEventPrefix(src: string): { prefix: string; commands: string } | null {
|
|
88
|
-
const match = src.match(EVENT_PREFIX_RE);
|
|
89
|
-
if (!match) return null;
|
|
90
|
-
const prefix = match[1];
|
|
91
|
-
const commands = src.slice(prefix.length);
|
|
92
|
-
if (!commands) return null;
|
|
93
|
-
return { prefix, commands };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function trySemanticTranslation(src: string, lang: string, threshold: number): string | null {
|
|
97
|
-
try {
|
|
98
|
-
const statements = splitStatements(src, lang);
|
|
99
|
-
if (statements.length > 1) {
|
|
100
|
-
return translateCompound(statements, lang, threshold);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (!isLanguageRegistered(lang)) return null;
|
|
104
|
-
|
|
26
|
+
export const preprocessToEnglish = createPreprocessToEnglish({
|
|
27
|
+
isLanguageRegistered,
|
|
28
|
+
translateSingle(src, lang, threshold) {
|
|
105
29
|
const result = parseWithConfidence(src, lang);
|
|
106
30
|
if (result.confidence < threshold || !result.node) return null;
|
|
107
31
|
|
|
108
32
|
return renderToHyperscript(result.node);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function tryI18nTranslation(
|
|
115
|
-
src: string,
|
|
116
|
-
lang: string,
|
|
117
|
-
toEnglish: (input: string, locale: string) => string
|
|
118
|
-
): string | null {
|
|
119
|
-
try {
|
|
120
|
-
const result = toEnglish(src, lang);
|
|
121
|
-
return result !== src ? result : null;
|
|
122
|
-
} catch {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Get all "then" keyword forms for a language (primary + alternatives).
|
|
129
|
-
* Falls back to English "then" if profile is unavailable.
|
|
130
|
-
*/
|
|
131
|
-
function getThenKeywords(lang: string): string[] {
|
|
132
|
-
const keywords = ['then']; // Always include English
|
|
133
|
-
const profile = tryGetProfile(lang);
|
|
134
|
-
if (profile?.keywords?.then) {
|
|
135
|
-
const kw = profile.keywords.then;
|
|
136
|
-
if (kw.primary && kw.primary !== 'then') keywords.push(kw.primary);
|
|
137
|
-
if (kw.alternatives) {
|
|
138
|
-
for (const alt of kw.alternatives) {
|
|
139
|
-
if (!keywords.includes(alt)) keywords.push(alt);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return keywords;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function splitStatements(src: string, lang: string): string[] {
|
|
147
|
-
const thenWords = getThenKeywords(lang);
|
|
148
|
-
// Escape regex special chars and build pattern
|
|
149
|
-
const escaped = thenWords.map(w => w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
150
|
-
const pattern = new RegExp(`\\s+(?:${escaped.join('|')})\\s+`, 'i');
|
|
151
|
-
|
|
152
|
-
const lines = src
|
|
153
|
-
.split('\n')
|
|
154
|
-
.map(l => l.trim())
|
|
155
|
-
.filter(l => l.length > 0);
|
|
156
|
-
const result: string[] = [];
|
|
157
|
-
for (const line of lines) {
|
|
158
|
-
const parts = line.split(pattern);
|
|
159
|
-
result.push(...parts);
|
|
160
|
-
}
|
|
161
|
-
return result;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function translateCompound(statements: string[], lang: string, threshold: number): string | null {
|
|
165
|
-
const translated: string[] = [];
|
|
166
|
-
for (const stmt of statements) {
|
|
167
|
-
const result = trySemanticTranslation(stmt.trim(), lang, threshold);
|
|
168
|
-
if (result === null) return null;
|
|
169
|
-
translated.push(result);
|
|
170
|
-
}
|
|
171
|
-
return translated.join(' then ');
|
|
172
|
-
}
|
|
33
|
+
},
|
|
34
|
+
});
|