@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/dist/index.cjs
CHANGED
|
@@ -23,6 +23,7 @@ __export(index_exports, {
|
|
|
23
23
|
hyperscriptI18n: () => hyperscriptI18n,
|
|
24
24
|
preprocess: () => preprocess,
|
|
25
25
|
preprocessToEnglish: () => preprocessToEnglish,
|
|
26
|
+
resetTranslationWarnings: () => resetTranslationWarnings,
|
|
26
27
|
resolveLanguage: () => resolveLanguage
|
|
27
28
|
});
|
|
28
29
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -33,6 +34,8 @@ function resolveLanguage(elt) {
|
|
|
33
34
|
if (dataLang) return normalizeLangCode(dataLang);
|
|
34
35
|
const hsLang = elt.getAttribute("data-hyperscript-lang") ?? elt.closest?.("[data-hyperscript-lang]")?.getAttribute("data-hyperscript-lang");
|
|
35
36
|
if (hsLang) return normalizeLangCode(hsLang);
|
|
37
|
+
const closestLang = elt.closest?.("[lang]")?.getAttribute("lang");
|
|
38
|
+
if (closestLang) return normalizeLangCode(closestLang);
|
|
36
39
|
const htmlLang = typeof document !== "undefined" ? document.documentElement?.lang : null;
|
|
37
40
|
if (htmlLang && htmlLang !== "en") return normalizeLangCode(htmlLang);
|
|
38
41
|
return null;
|
|
@@ -43,40 +46,17 @@ function normalizeLangCode(lang) {
|
|
|
43
46
|
|
|
44
47
|
// src/preprocessor.ts
|
|
45
48
|
var import_semantic = require("@lokascript/semantic");
|
|
49
|
+
|
|
50
|
+
// src/preprocessor-core.ts
|
|
46
51
|
var DEFAULT_THRESHOLD = 0.5;
|
|
47
52
|
var DEFAULT_CONFIG = {
|
|
48
53
|
confidenceThreshold: DEFAULT_THRESHOLD,
|
|
49
|
-
strategy: "semantic"
|
|
50
|
-
fallbackToOriginal: true
|
|
54
|
+
strategy: "semantic"
|
|
51
55
|
};
|
|
52
56
|
function resolveThreshold(threshold, lang) {
|
|
53
57
|
if (typeof threshold === "number") return threshold;
|
|
54
58
|
return threshold[lang] ?? threshold["*"] ?? DEFAULT_THRESHOLD;
|
|
55
59
|
}
|
|
56
|
-
function preprocessToEnglish(src, lang, config = {}) {
|
|
57
|
-
if (lang === "en") return src;
|
|
58
|
-
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
59
|
-
const fullResult = tryTranslateWithStrategies(src, lang, cfg);
|
|
60
|
-
if (fullResult !== null) return fullResult;
|
|
61
|
-
const stripped = stripEventPrefix(src);
|
|
62
|
-
if (stripped) {
|
|
63
|
-
const translated = tryTranslateWithStrategies(stripped.commands, lang, cfg);
|
|
64
|
-
if (translated !== null) return stripped.prefix + translated;
|
|
65
|
-
}
|
|
66
|
-
return cfg.fallbackToOriginal ? src : src;
|
|
67
|
-
}
|
|
68
|
-
function tryTranslateWithStrategies(src, lang, cfg) {
|
|
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
|
-
if ((cfg.strategy === "auto" || cfg.strategy === "i18n") && cfg.i18nToEnglish) {
|
|
75
|
-
const result = tryI18nTranslation(src, lang, cfg.i18nToEnglish);
|
|
76
|
-
if (result !== null) return result;
|
|
77
|
-
}
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
60
|
var EVENT_PREFIX_RE = /^(on\s+(?:every\s+)?[\w-]+(?:\[.*?\])?(?:\.[\w-]+(?:\([^)]*\))?)*(?:\s+from\s+\S+)?(?:\s+queue\s+\w+)?\s+)/;
|
|
81
61
|
function stripEventPrefix(src) {
|
|
82
62
|
const match = src.match(EVENT_PREFIX_RE);
|
|
@@ -86,71 +66,66 @@ function stripEventPrefix(src) {
|
|
|
86
66
|
if (!commands) return null;
|
|
87
67
|
return { prefix, commands };
|
|
88
68
|
}
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return
|
|
69
|
+
function createPreprocessToEnglish(hooks) {
|
|
70
|
+
function trySemanticTranslation(src, lang, threshold) {
|
|
71
|
+
try {
|
|
72
|
+
if (!hooks.isLanguageRegistered(lang)) return null;
|
|
73
|
+
return hooks.translateSingle(src, lang, threshold);
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
94
76
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
77
|
+
}
|
|
78
|
+
function tryI18nTranslation(src, lang, toEnglish) {
|
|
79
|
+
try {
|
|
80
|
+
const result = toEnglish(src, lang);
|
|
81
|
+
return result !== src ? result : null;
|
|
82
|
+
} catch {
|
|
101
83
|
return null;
|
|
102
84
|
}
|
|
103
|
-
return (0, import_semantic.render)(result.node, "en");
|
|
104
|
-
} catch {
|
|
105
|
-
return null;
|
|
106
85
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
86
|
+
function tryTranslateWithStrategies(src, lang, cfg) {
|
|
87
|
+
if (cfg.strategy === "semantic" || cfg.strategy === "auto") {
|
|
88
|
+
const threshold = resolveThreshold(cfg.confidenceThreshold, lang);
|
|
89
|
+
const result = trySemanticTranslation(src, lang, threshold);
|
|
90
|
+
if (result !== null) return result;
|
|
91
|
+
}
|
|
92
|
+
if ((cfg.strategy === "auto" || cfg.strategy === "i18n") && cfg.i18nToEnglish) {
|
|
93
|
+
const result = tryI18nTranslation(src, lang, cfg.i18nToEnglish);
|
|
94
|
+
if (result !== null) return result;
|
|
95
|
+
}
|
|
113
96
|
return null;
|
|
114
97
|
}
|
|
98
|
+
return function preprocessToEnglish2(src, lang, config = {}) {
|
|
99
|
+
if (lang === "en") return src;
|
|
100
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
101
|
+
const fullResult = tryTranslateWithStrategies(src, lang, cfg);
|
|
102
|
+
if (fullResult !== null) return fullResult;
|
|
103
|
+
const stripped = stripEventPrefix(src);
|
|
104
|
+
if (stripped) {
|
|
105
|
+
const translated = tryTranslateWithStrategies(stripped.commands, lang, cfg);
|
|
106
|
+
if (translated !== null) return stripped.prefix + translated;
|
|
107
|
+
}
|
|
108
|
+
return src;
|
|
109
|
+
};
|
|
115
110
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
111
|
+
|
|
112
|
+
// src/preprocessor.ts
|
|
113
|
+
var preprocessToEnglish = createPreprocessToEnglish({
|
|
114
|
+
isLanguageRegistered: import_semantic.isLanguageRegistered,
|
|
115
|
+
translateSingle(src, lang, threshold) {
|
|
116
|
+
const result = (0, import_semantic.parseSemantic)(src, lang);
|
|
117
|
+
if (result.confidence < threshold || !result.node) {
|
|
118
|
+
if (result.confidence >= threshold) {
|
|
119
|
+
return (0, import_semantic.translate)(src, lang, "en");
|
|
125
120
|
}
|
|
121
|
+
return null;
|
|
126
122
|
}
|
|
123
|
+
return (0, import_semantic.render)(result.node, "en");
|
|
127
124
|
}
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
function splitStatements(src, lang) {
|
|
131
|
-
const thenWords = getThenKeywords(lang);
|
|
132
|
-
const escaped = thenWords.map((w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
133
|
-
const pattern = new RegExp(`\\s+(?:${escaped.join("|")})\\s+`, "i");
|
|
134
|
-
const lines = src.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
135
|
-
const result = [];
|
|
136
|
-
for (const line of lines) {
|
|
137
|
-
const parts = line.split(pattern);
|
|
138
|
-
result.push(...parts);
|
|
139
|
-
}
|
|
140
|
-
return result;
|
|
141
|
-
}
|
|
142
|
-
function translateCompound(statements, lang, threshold) {
|
|
143
|
-
const translated = [];
|
|
144
|
-
for (const stmt of statements) {
|
|
145
|
-
const result = trySemanticTranslation(stmt.trim(), lang, threshold);
|
|
146
|
-
if (result === null) return null;
|
|
147
|
-
translated.push(result);
|
|
148
|
-
}
|
|
149
|
-
return translated.join(" then ");
|
|
150
|
-
}
|
|
125
|
+
});
|
|
151
126
|
|
|
152
127
|
// src/attribute-translator.ts
|
|
153
|
-
var
|
|
128
|
+
var processed = /* @__PURE__ */ new WeakSet();
|
|
154
129
|
function scriptAttributeNames(hs) {
|
|
155
130
|
const raw = hs.config?.attributes ?? "_, script, data-script";
|
|
156
131
|
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -171,12 +146,12 @@ function installAttributeTranslator(hs, translate2) {
|
|
|
171
146
|
const attrNames = scriptAttributeNames(hs);
|
|
172
147
|
const selector = [...attrNames.map((a) => `[${a}]`), 'script[type="text/hyperscript"]'].join(", ");
|
|
173
148
|
const translateOne = (elt) => {
|
|
174
|
-
if (
|
|
149
|
+
if (processed.has(elt)) return;
|
|
175
150
|
if (elt instanceof HTMLScriptElement && elt.type === "text/hyperscript") {
|
|
176
151
|
const src2 = elt.textContent ?? "";
|
|
177
152
|
if (!src2) return;
|
|
178
153
|
const english2 = translate2(src2, elt);
|
|
179
|
-
|
|
154
|
+
processed.add(elt);
|
|
180
155
|
if (english2 != null && english2 !== src2) elt.textContent = english2;
|
|
181
156
|
return;
|
|
182
157
|
}
|
|
@@ -185,7 +160,7 @@ function installAttributeTranslator(hs, translate2) {
|
|
|
185
160
|
const src = elt.getAttribute(attr);
|
|
186
161
|
if (!src) return;
|
|
187
162
|
const english = translate2(src, elt);
|
|
188
|
-
|
|
163
|
+
processed.add(elt);
|
|
189
164
|
if (english != null && english !== src) elt.setAttribute(attr, english);
|
|
190
165
|
};
|
|
191
166
|
hs.addBeforeProcessHook((root) => {
|
|
@@ -195,21 +170,66 @@ function installAttributeTranslator(hs, translate2) {
|
|
|
195
170
|
});
|
|
196
171
|
}
|
|
197
172
|
|
|
173
|
+
// src/host-validate.ts
|
|
174
|
+
function acceptedByHost(hs, src) {
|
|
175
|
+
if (typeof hs.parse !== "function") return true;
|
|
176
|
+
try {
|
|
177
|
+
const result = hs.parse(src);
|
|
178
|
+
return !result?.errors || result.errors.length === 0;
|
|
179
|
+
} catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
var warnedRejectedLang = /* @__PURE__ */ new Set();
|
|
184
|
+
function resetHostValidationWarnings() {
|
|
185
|
+
warnedRejectedLang.clear();
|
|
186
|
+
}
|
|
187
|
+
function warnRejectedOnce(lang, src, english) {
|
|
188
|
+
if (warnedRejectedLang.has(lang)) return;
|
|
189
|
+
warnedRejectedLang.add(lang);
|
|
190
|
+
console.warn(
|
|
191
|
+
`[hyperscript-i18n] Translation for lang="${lang}" rendered hyperscript the host parser rejects \u2014 falling back to the original text. Source: "${src.length > 60 ? src.slice(0, 60) + "\u2026" : src}" \u2192 "${english.length > 60 ? english.slice(0, 60) + "\u2026" : english}". Further elements in this language stay quiet \u2014 enable { debug: true } for per-element detail.`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
198
195
|
// src/plugin.ts
|
|
196
|
+
var warnedUnchangedLang = /* @__PURE__ */ new Set();
|
|
197
|
+
function resetTranslationWarnings() {
|
|
198
|
+
warnedUnchangedLang.clear();
|
|
199
|
+
resetHostValidationWarnings();
|
|
200
|
+
}
|
|
201
|
+
function warnUnchangedOnce(lang, src) {
|
|
202
|
+
if (warnedUnchangedLang.has(lang)) return;
|
|
203
|
+
warnedUnchangedLang.add(lang);
|
|
204
|
+
console.warn(
|
|
205
|
+
`[hyperscript-i18n] Translation unchanged for lang="${lang}": "${src.length > 60 ? src.slice(0, 60) + "\u2026" : src}". This is fine if the source is already canonical English; otherwise the input may not match any known pattern, or the language may not be registered. Original text is passed to _hyperscript as-is. Further elements in this language stay quiet \u2014 enable { debug: true } for per-element detail.`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
199
208
|
function hyperscriptI18n(options = {}) {
|
|
200
209
|
return function plugin(hs) {
|
|
201
|
-
|
|
210
|
+
const host = hs;
|
|
211
|
+
installAttributeTranslator(host, (src, elt) => {
|
|
202
212
|
const lang = resolveLanguageWithOptions(elt, options);
|
|
203
213
|
if (!lang || lang === "en") return src;
|
|
204
214
|
const english = preprocessToEnglish(src, lang, options);
|
|
205
215
|
if (english !== src) {
|
|
216
|
+
if (options.validateWithHost !== false && !acceptedByHost(host, english)) {
|
|
217
|
+
if (options.debug) {
|
|
218
|
+
console.log(
|
|
219
|
+
`[hyperscript-i18n] ${lang}: host rejected "${english}" \u2014 keeping "${src}"`
|
|
220
|
+
);
|
|
221
|
+
} else {
|
|
222
|
+
warnRejectedOnce(lang, src, english);
|
|
223
|
+
}
|
|
224
|
+
return src;
|
|
225
|
+
}
|
|
206
226
|
if (options.debug) {
|
|
207
227
|
console.log(`[hyperscript-i18n] ${lang}: "${src}" \u2192 "${english}"`);
|
|
208
228
|
}
|
|
229
|
+
} else if (options.debug) {
|
|
230
|
+
console.log(`[hyperscript-i18n] ${lang}: unchanged "${src}"`);
|
|
209
231
|
} else {
|
|
210
|
-
|
|
211
|
-
`[hyperscript-i18n] Translation unchanged for lang="${lang}": "${src.length > 60 ? src.slice(0, 60) + "\u2026" : src}". The input may not match any known pattern, or the language may not be registered. Original text will be passed to _hyperscript as-is.`
|
|
212
|
-
);
|
|
232
|
+
warnUnchangedOnce(lang, src);
|
|
213
233
|
}
|
|
214
234
|
return english;
|
|
215
235
|
});
|
|
@@ -233,6 +253,7 @@ function preprocess(src, lang, config = {}) {
|
|
|
233
253
|
hyperscriptI18n,
|
|
234
254
|
preprocess,
|
|
235
255
|
preprocessToEnglish,
|
|
256
|
+
resetTranslationWarnings,
|
|
236
257
|
resolveLanguage
|
|
237
258
|
});
|
|
238
259
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/language-resolver.ts","../src/preprocessor.ts","../src/attribute-translator.ts","../src/plugin.ts"],"sourcesContent":["/**\n * @lokascript/hyperscript-adapter\n *\n * Multilingual adapter plugin for the original _hyperscript.\n * Enables writing hyperscript in any of 24 supported languages.\n *\n * @example\n * import { hyperscriptI18n } from '@lokascript/hyperscript-adapter';\n * _hyperscript.use(hyperscriptI18n());\n *\n * @example\n * // With options\n * _hyperscript.use(hyperscriptI18n({\n * defaultLanguage: 'ja',\n * debug: true,\n * }));\n *\n * @example\n * // Standalone preprocessing (for _hyperscript.evaluate() calls)\n * import { preprocess } from '@lokascript/hyperscript-adapter';\n * const english = preprocess('トグル .active', 'ja');\n * _hyperscript(english);\n */\n\nexport { hyperscriptI18n, preprocess, type PluginOptions } from './plugin';\nexport { preprocessToEnglish, type PreprocessorConfig } from './preprocessor';\nexport { resolveLanguage } from './language-resolver';\n","/**\n * Language Resolver\n *\n * Determines the language of hyperscript code on a given element\n * using a cascading resolution strategy.\n */\n\n/**\n * Resolve the language for a hyperscript attribute on an element.\n *\n * Resolution order:\n * 1. `data-lang` attribute on the element itself\n * 2. `data-hyperscript-lang` attribute on the element or closest ancestor\n * 3. `lang` attribute on `<html>` element\n * 4. null (assume English, no preprocessing needed)\n */\nexport function resolveLanguage(elt: Element): string | null {\n // 1. Explicit per-element\n const dataLang = elt.getAttribute('data-lang');\n if (dataLang) return normalizeLangCode(dataLang);\n\n // 2. Inherited from ancestor\n const hsLang =\n elt.getAttribute('data-hyperscript-lang') ??\n elt.closest?.('[data-hyperscript-lang]')?.getAttribute('data-hyperscript-lang');\n if (hsLang) return normalizeLangCode(hsLang);\n\n // 3. Document-level lang\n const htmlLang = typeof document !== 'undefined' ? document.documentElement?.lang : null;\n if (htmlLang && htmlLang !== 'en') return normalizeLangCode(htmlLang);\n\n return null;\n}\n\n/**\n * Normalize a BCP-47 language tag to an ISO 639-1 code.\n * e.g., \"ja-JP\" → \"ja\", \"zh-Hans\" → \"zh\", \"pt-BR\" → \"pt\"\n */\nfunction normalizeLangCode(lang: string): string {\n return lang.split('-')[0].toLowerCase();\n}\n","/**\n * Preprocessor\n *\n * Translates non-English hyperscript to English using the semantic parser\n * (with optional i18n fallback), so the original _hyperscript can parse it.\n */\n\nimport {\n translate,\n render,\n parseSemantic,\n isLanguageRegistered,\n tryGetProfile,\n} from '@lokascript/semantic';\n\nexport interface PreprocessorConfig {\n /**\n * Minimum confidence threshold for semantic parsing (0-1). Default: 0.5.\n * Can be a single number (applies to all languages) or a per-language map.\n * Per-language thresholds are useful because SOV languages (ja, ko, tr) produce\n * inherently lower confidence scores than SVO languages (es, fr, de).\n *\n * @example\n * // Single threshold\n * { confidenceThreshold: 0.5 }\n *\n * @example\n * // Per-language thresholds\n * { confidenceThreshold: { es: 0.7, ja: 0.1, ko: 0.05, '*': 0.5 } }\n */\n confidenceThreshold: number | Record<string, number>;\n /** Strategy: 'semantic' (default), 'i18n', or 'auto' (semantic then i18n) */\n strategy: 'semantic' | 'i18n' | 'auto';\n /** Whether to return original text on failure. Default: true */\n fallbackToOriginal: boolean;\n /** Optional i18n toEnglish function (loaded dynamically if available) */\n i18nToEnglish?: (input: string, locale: string) => string;\n}\n\nconst DEFAULT_THRESHOLD = 0.5;\n\nconst DEFAULT_CONFIG: PreprocessorConfig = {\n confidenceThreshold: DEFAULT_THRESHOLD,\n strategy: 'semantic',\n fallbackToOriginal: true,\n};\n\n/**\n * Resolve the confidence threshold for a specific language.\n * Supports both a single number and a per-language map with '*' as default.\n */\nfunction resolveThreshold(threshold: number | Record<string, number>, lang: string): number {\n if (typeof threshold === 'number') return threshold;\n return threshold[lang] ?? threshold['*'] ?? DEFAULT_THRESHOLD;\n}\n\n/**\n * Preprocess non-English hyperscript into English.\n *\n * Uses the semantic parser to parse the input in the source language,\n * then renders back to English. Falls through to i18n if configured.\n *\n * Handles _hyperscript feature prefixes (e.g. \"on click\", \"on every keyup\")\n * by stripping them, translating only the command portion, then reassembling.\n */\nexport function preprocessToEnglish(\n src: string,\n lang: string,\n config: Partial<PreprocessorConfig> = {}\n): string {\n // English→English is identity; skip semantic parsing which may mangle the input\n if (lang === 'en') return src;\n\n const cfg = { ...DEFAULT_CONFIG, ...config };\n\n // Try translating the full string first\n const fullResult = tryTranslateWithStrategies(src, lang, cfg);\n if (fullResult !== null) return fullResult;\n\n // If full translation failed, try stripping event/feature prefix.\n // _hyperscript attributes often contain \"on <event> <commands>\" — the semantic\n // parser only understands command syntax, not event declarations.\n const stripped = stripEventPrefix(src);\n if (stripped) {\n const translated = tryTranslateWithStrategies(stripped.commands, lang, cfg);\n if (translated !== null) return stripped.prefix + translated;\n }\n\n // Fallback: return original\n return cfg.fallbackToOriginal ? src : src;\n}\n\n/**\n * Try all configured translation strategies on the input.\n * Returns null if none succeed.\n */\nfunction tryTranslateWithStrategies(\n src: string,\n lang: string,\n cfg: PreprocessorConfig\n): string | null {\n // Strategy: semantic-only\n if (cfg.strategy === 'semantic' || cfg.strategy === 'auto') {\n const threshold = resolveThreshold(cfg.confidenceThreshold, lang);\n const result = trySemanticTranslation(src, lang, threshold);\n if (result !== null) return result;\n }\n\n // Strategy: i18n fallback (auto mode or i18n-only)\n if ((cfg.strategy === 'auto' || cfg.strategy === 'i18n') && cfg.i18nToEnglish) {\n const result = tryI18nTranslation(src, lang, cfg.i18nToEnglish);\n if (result !== null) return result;\n }\n\n return null;\n}\n\n/**\n * Match _hyperscript event handler prefix: \"on [every] <event>[filter][.modifiers] \"\n *\n * Examples:\n * \"on click toggle .active\" → prefix: \"on click \", commands: \"toggle .active\"\n * \"on every click toggle .active\" → prefix: \"on every click \", commands: \"toggle .active\"\n * \"on click.debounce(300) toggle .x\" → prefix: \"on click.debounce(300) \", commands: \"toggle .x\"\n * \"on keyup[key=='Enter'] set x to 1\" → prefix: \"on keyup[key=='Enter'] \", commands: \"set x to 1\"\n * \"on click from body toggle .active\" → prefix: \"on click from body \", commands: \"toggle .active\"\n */\nconst EVENT_PREFIX_RE =\n /^(on\\s+(?:every\\s+)?[\\w-]+(?:\\[.*?\\])?(?:\\.[\\w-]+(?:\\([^)]*\\))?)*(?:\\s+from\\s+\\S+)?(?:\\s+queue\\s+\\w+)?\\s+)/;\n\nfunction stripEventPrefix(src: string): { prefix: string; commands: string } | null {\n const match = src.match(EVENT_PREFIX_RE);\n if (!match) return null;\n const prefix = match[1];\n const commands = src.slice(prefix.length);\n if (!commands) return null;\n return { prefix, commands };\n}\n\n/**\n * Try semantic translation: parse in source language, render to English.\n * Returns null if confidence is below threshold.\n */\nfunction trySemanticTranslation(src: string, lang: string, threshold: number): string | null {\n try {\n // Handle compound statements (split on \"then\" boundaries and newlines)\n const statements = splitStatements(src, lang);\n if (statements.length > 1) {\n return translateCompound(statements, lang, threshold);\n }\n\n // Check if the semantic parser can handle this\n if (!isLanguageRegistered(lang)) return null;\n\n const result = parseSemantic(src, lang);\n if (result.confidence < threshold || !result.node) {\n // If we got SOME confidence but no node, fall back to translate()\n if (result.confidence >= threshold) {\n return translate(src, lang, 'en');\n }\n return null;\n }\n\n // Render the semantic node to English\n return render(result.node, 'en');\n } catch {\n return null;\n }\n}\n\n/**\n * Try i18n grammar transformation to English.\n * Returns null if the result is identical to input (no translation happened).\n */\nfunction tryI18nTranslation(\n src: string,\n lang: string,\n toEnglish: (input: string, locale: string) => string\n): string | null {\n try {\n const result = toEnglish(src, lang);\n return result !== src ? result : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Get all \"then\" keyword forms for a language (primary + alternatives).\n * Falls back to English \"then\" if profile is unavailable.\n */\nfunction getThenKeywords(lang: string): string[] {\n const keywords = ['then']; // Always include English\n const profile = tryGetProfile(lang);\n if (profile?.keywords?.then) {\n const kw = profile.keywords.then;\n if (kw.primary && kw.primary !== 'then') keywords.push(kw.primary);\n if (kw.alternatives) {\n for (const alt of kw.alternatives) {\n if (!keywords.includes(alt)) keywords.push(alt);\n }\n }\n }\n return keywords;\n}\n\n/**\n * Split a hyperscript source string into individual statements.\n * Splits on language-specific \"then\" keywords and newlines.\n */\nfunction splitStatements(src: string, lang: string): string[] {\n const thenWords = getThenKeywords(lang);\n // Escape regex special chars and build pattern\n const escaped = thenWords.map(w => w.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'));\n const pattern = new RegExp(`\\\\s+(?:${escaped.join('|')})\\\\s+`, 'i');\n\n // Split on newlines first\n const lines = src\n .split('\\n')\n .map(l => l.trim())\n .filter(l => l.length > 0);\n const result: string[] = [];\n for (const line of lines) {\n const parts = line.split(pattern);\n result.push(...parts);\n }\n return result;\n}\n\n/**\n * Translate compound statements individually and rejoin with \" then \".\n */\nfunction translateCompound(statements: string[], lang: string, threshold: number): string | null {\n const translated: string[] = [];\n for (const stmt of statements) {\n const result = trySemanticTranslation(stmt.trim(), lang, threshold);\n if (result === null) return null; // If any part fails, fail the whole compound\n translated.push(result);\n }\n return translated.join(' then ');\n}\n","/**\n * Attribute translator\n *\n * _hyperscript.org's Runtime#getScript is a private class field (`#getScript`),\n * not a property reachable through `internals.runtime` — every plugin variant\n * in this package used to monkey-patch `internals.runtime.getScript`, which\n * silently no-ops against current _hyperscript.org builds (the assignment\n * creates a stray own-property that the runtime's internal `#getScript()`\n * calls never read, so translation never happens and errors are swallowed by\n * `runtime.getScript.bind` throwing before any hyperscript even parses).\n *\n * `addBeforeProcessHook` is the supported, public extension point instead: it\n * fires on the subtree root passed to `processNode()` before the runtime reads\n * whichever configured attribute (`_`, `script`, `data-script` by default) or\n * `<script type=\"text/hyperscript\">` body holds the source. Rewriting that\n * attribute/body in place — before the runtime's own scan reaches it — gets\n * the same \"translate before parse\" effect through a mechanism the runtime\n * actually calls.\n */\n\nexport interface HyperscriptHost {\n addBeforeProcessHook?: (fn: (elt: Element) => void) => void;\n config?: { attributes?: string };\n}\n\n/** Marks an element as already translated so a later `processNode()` call over\n * the same subtree (e.g. a sibling swap re-scanning a shared ancestor) doesn't\n * re-translate already-English text as if it were still the original language. */\nconst TRANSLATED_MARKER = 'data-hyperscript-i18n-translated';\n\nfunction scriptAttributeNames(hs: HyperscriptHost): string[] {\n const raw = hs.config?.attributes ?? '_, script, data-script';\n return raw\n .split(',')\n .map(s => s.trim())\n .filter(Boolean);\n}\n\nfunction findScriptAttribute(elt: Element, attrNames: string[]): string | null {\n for (const name of attrNames) {\n if (elt.hasAttribute(name)) return name;\n }\n return null;\n}\n\n/**\n * Install a translator that rewrites non-English script attributes to English\n * in place, before `_hyperscript.org` parses them.\n *\n * @param translate Given the raw source and its element, return the English\n * translation, or `null`/the same string to leave the element untouched\n * (English input, unresolved language, translation failure, etc).\n */\nexport function installAttributeTranslator(\n hs: HyperscriptHost,\n translate: (src: string, elt: Element) => string | null\n): void {\n if (typeof hs.addBeforeProcessHook !== 'function') {\n console.warn(\n '[hyperscript-i18n] _hyperscript.addBeforeProcessHook is unavailable — ' +\n 'this build of _hyperscript.org is not supported. Load a current version ' +\n 'from https://unpkg.com/hyperscript.org.'\n );\n return;\n }\n\n const attrNames = scriptAttributeNames(hs);\n const selector = [...attrNames.map(a => `[${a}]`), 'script[type=\"text/hyperscript\"]'].join(', ');\n\n const translateOne = (elt: Element): void => {\n if (elt.hasAttribute(TRANSLATED_MARKER)) return;\n\n if (elt instanceof HTMLScriptElement && elt.type === 'text/hyperscript') {\n const src = elt.textContent ?? '';\n if (!src) return;\n const english = translate(src, elt);\n elt.setAttribute(TRANSLATED_MARKER, '');\n if (english != null && english !== src) elt.textContent = english;\n return;\n }\n\n const attr = findScriptAttribute(elt, attrNames);\n if (!attr) return;\n const src = elt.getAttribute(attr);\n if (!src) return;\n const english = translate(src, elt);\n elt.setAttribute(TRANSLATED_MARKER, '');\n if (english != null && english !== src) elt.setAttribute(attr, english);\n };\n\n hs.addBeforeProcessHook((root: Element) => {\n if (!root || typeof root.querySelectorAll !== 'function') return;\n if (typeof root.matches === 'function' && root.matches(selector)) translateOne(root);\n root.querySelectorAll(selector).forEach(translateOne);\n });\n}\n","/**\n * _hyperscript Plugin\n *\n * Registers with _hyperscript.use() to rewrite non-English hyperscript\n * attributes into English before _hyperscript.org parses them.\n */\n\nimport { resolveLanguage } from './language-resolver';\nimport { preprocessToEnglish, type PreprocessorConfig } from './preprocessor';\nimport { installAttributeTranslator, type HyperscriptHost } from './attribute-translator';\n\nexport interface PluginOptions extends Partial<PreprocessorConfig> {\n /** Default language for all elements (overridable per-element). */\n defaultLanguage?: string;\n /** Custom attribute name for per-element language. Default: \"data-lang\" */\n languageAttribute?: string;\n /** Enable debug logging to console. Default: false */\n debug?: boolean;\n}\n\n/**\n * Create a _hyperscript plugin that enables multilingual hyperscript.\n *\n * @example\n * // Basic usage\n * _hyperscript.use(hyperscriptI18n());\n *\n * @example\n * // With options\n * _hyperscript.use(hyperscriptI18n({\n * defaultLanguage: 'ja',\n * confidenceThreshold: 0.6,\n * debug: true,\n * }));\n */\nexport function hyperscriptI18n(options: PluginOptions = {}) {\n return function plugin(hs: unknown): void {\n installAttributeTranslator(hs as HyperscriptHost, (src, elt) => {\n // Resolve language\n const lang = resolveLanguageWithOptions(elt, options);\n\n // If English or no language detected, pass through\n if (!lang || lang === 'en') return src;\n\n // Preprocess to English\n const english = preprocessToEnglish(src, lang, options);\n\n if (english !== src) {\n if (options.debug) {\n console.log(`[hyperscript-i18n] ${lang}: \"${src}\" → \"${english}\"`);\n }\n } else {\n // Translation produced no change — likely a failure\n console.warn(\n `[hyperscript-i18n] Translation unchanged for lang=\"${lang}\": \"${src.length > 60 ? src.slice(0, 60) + '…' : src}\". ` +\n 'The input may not match any known pattern, or the language may not be registered. ' +\n 'Original text will be passed to _hyperscript as-is.'\n );\n }\n\n return english;\n });\n };\n}\n\n/**\n * Resolve language with plugin options factored in.\n */\nfunction resolveLanguageWithOptions(elt: Element, options: PluginOptions): string | null {\n // Check custom attribute name\n if (options.languageAttribute) {\n const custom = elt.getAttribute(options.languageAttribute);\n if (custom) return custom.split('-')[0].toLowerCase();\n }\n\n // Standard resolution\n const resolved = resolveLanguage(elt);\n if (resolved) return resolved;\n\n // Fall back to default language\n return options.defaultLanguage ?? null;\n}\n\n/**\n * Standalone preprocessing function for programmatic use.\n * Call this when using _hyperscript.evaluate() or _hyperscript(\"code\") directly.\n *\n * @example\n * const english = preprocess(\"トグル .active\", \"ja\");\n * _hyperscript(english);\n */\nexport function preprocess(\n src: string,\n lang: string,\n config: Partial<PreprocessorConfig> = {}\n): string {\n if (lang === 'en') return src;\n return preprocessToEnglish(src, lang, config);\n}\n"],"mappings":"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","names":["translate","src","english"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/language-resolver.ts","../src/preprocessor.ts","../src/preprocessor-core.ts","../src/attribute-translator.ts","../src/host-validate.ts","../src/plugin.ts"],"sourcesContent":["/**\n * @lokascript/hyperscript-adapter\n *\n * Multilingual adapter plugin for the original _hyperscript.\n * Enables writing hyperscript in any of 24 supported languages.\n *\n * @example\n * import { hyperscriptI18n } from '@lokascript/hyperscript-adapter';\n * _hyperscript.use(hyperscriptI18n());\n *\n * @example\n * // With options\n * _hyperscript.use(hyperscriptI18n({\n * defaultLanguage: 'ja',\n * debug: true,\n * }));\n *\n * @example\n * // Standalone preprocessing (for _hyperscript.evaluate() calls)\n * import { preprocess } from '@lokascript/hyperscript-adapter';\n * const english = preprocess('トグル .active', 'ja');\n * _hyperscript(english);\n */\n\nexport {\n hyperscriptI18n,\n preprocess,\n resetTranslationWarnings,\n type PluginOptions,\n} from './plugin';\nexport { preprocessToEnglish, type PreprocessorConfig } from './preprocessor';\nexport { resolveLanguage } from './language-resolver';\n","/**\n * Language Resolver\n *\n * Determines the language of hyperscript code on a given element\n * using a cascading resolution strategy.\n */\n\n/**\n * Resolve the language for a hyperscript attribute on an element.\n *\n * Resolution order:\n * 1. `data-lang` attribute on the element itself\n * 2. `data-hyperscript-lang` attribute on the element or closest ancestor\n * 3. `lang` attribute on the element or closest ancestor (the HTML-standard\n * cascade — a `<section lang=\"es\">` localizes everything inside it, and a\n * nested `lang=\"en\"` opts back out). This is how the paired htmx-adapter\n * (`langOf()`) and loka-js resolve language, so `hx-*` and `_` attributes\n * on the same element agree.\n * 4. `lang` on `<html>` via `document.documentElement` — only reachable when\n * the element is DETACHED (step 3's ancestor walk covers `<html>` for\n * attached elements): a hook processing a not-yet-inserted fragment still\n * picks up the page default.\n * 5. null (assume English, no preprocessing needed)\n */\nexport function resolveLanguage(elt: Element): string | null {\n // 1. Explicit per-element\n const dataLang = elt.getAttribute('data-lang');\n if (dataLang) return normalizeLangCode(dataLang);\n\n // 2. Inherited from ancestor\n const hsLang =\n elt.getAttribute('data-hyperscript-lang') ??\n elt.closest?.('[data-hyperscript-lang]')?.getAttribute('data-hyperscript-lang');\n if (hsLang) return normalizeLangCode(hsLang);\n\n // 3. Standard lang cascade (nearest ancestor wins)\n const closestLang = elt.closest?.('[lang]')?.getAttribute('lang');\n if (closestLang) return normalizeLangCode(closestLang);\n\n // 4. Document-level lang (detached elements only — see doc comment)\n const htmlLang = typeof document !== 'undefined' ? document.documentElement?.lang : null;\n if (htmlLang && htmlLang !== 'en') return normalizeLangCode(htmlLang);\n\n return null;\n}\n\n/**\n * Normalize a BCP-47 language tag to an ISO 639-1 code.\n * e.g., \"ja-JP\" → \"ja\", \"zh-Hans\" → \"zh\", \"pt-BR\" → \"pt\"\n */\nfunction normalizeLangCode(lang: string): string {\n return lang.split('-')[0].toLowerCase();\n}\n","/**\n * Preprocessor (full path)\n *\n * Translates non-English hyperscript to English using the semantic parser\n * (with optional i18n fallback), so the original _hyperscript can parse it.\n *\n * The strategy/split/strip skeleton lives in preprocessor-core.ts, shared\n * with the slim path — only HOW one statement is parsed and rendered\n * differs here: semantic's parseSemantic + render('en'), plus a\n * translate() rescue when the parse is confident but yields no node.\n */\n\nimport { translate, render, parseSemantic, isLanguageRegistered } from '@lokascript/semantic';\n\nimport { createPreprocessToEnglish, type PreprocessorConfig } from './preprocessor-core';\n\nexport type { PreprocessorConfig };\n\n/**\n * Preprocess non-English hyperscript into English.\n *\n * Uses the semantic parser to parse the input in the source language,\n * then renders back to English. Falls through to i18n if configured.\n */\nexport const preprocessToEnglish = createPreprocessToEnglish({\n isLanguageRegistered,\n translateSingle(src, lang, threshold) {\n const result = parseSemantic(src, lang);\n if (result.confidence < threshold || !result.node) {\n // If we got SOME confidence but no node, fall back to translate()\n if (result.confidence >= threshold) {\n return translate(src, lang, 'en');\n }\n return null;\n }\n\n // Render the semantic node to English\n return render(result.node, 'en');\n },\n});\n","/**\n * Preprocessor core — the shared skeleton of the full and slim\n * preprocessors.\n *\n * Everything that is NOT genuinely different between the two paths lives\n * here exactly once: config defaults/threshold resolution, strategy\n * ordering (semantic → optional i18n), event-prefix stripping, and the\n * return-original fallback. The two real differences stay with each path,\n * injected as hooks:\n *\n * - HOW one statement is parsed and rendered to English\n * (`translateSingle`): the full path uses semantic's parseSemantic +\n * render + a translate() rescue for confident-but-nodeless parses;\n * the slim path uses parseWithConfidence + the custom\n * hyperscript-renderer (no English language data).\n * - WHERE the registry lookup comes from (`isLanguageRegistered`):\n * `@lokascript/semantic` vs `…/core` — under tsup's split dist these\n * are separate registry instances, so each path must bring its own.\n *\n * Behavior is pinned by the parity ratchet\n * (test/preprocessor-parity.*.test.ts + the committed fixture): both paths\n * are byte-identical to their pre-extraction outputs over the corpus.\n *\n * HISTORY: the skeleton used to carry a compound-splitting fallback\n * (`splitStatements` on localized `then` keywords + newlines,\n * `translateCompound` rejoining pieces with a hardcoded ` then `), run when\n * the whole-string parse declined. Deleted 2026-08-07 after measurement:\n * over all 3703 patterns.db corpus translations plus the parity corpus, on\n * BOTH paths, the split arm produced ZERO final outputs (full: 115\n * invocations, all returned null; slim: 32, all null) — post-whole-string-\n * first (#899), an input whose pieces each parse would have parsed whole,\n * so the arm's success condition is self-defeating. It was also defective\n * twice over: the regex split was string-literal-blind (`say 'now then\n * later'` split inside the literal) and the ` then ` rejoin is invalid\n * immediately after block headers. Deleting it erased both defect classes\n * wholesale; the parity fixture regenerated byte-identical.\n */\n\nexport interface PreprocessorConfig {\n /**\n * Minimum confidence threshold for semantic parsing (0-1). Default: 0.5.\n * Can be a single number (applies to all languages) or a per-language map.\n * Per-language thresholds are useful because SOV languages (ja, ko, tr) produce\n * inherently lower confidence scores than SVO languages (es, fr, de).\n *\n * @example\n * // Single threshold\n * { confidenceThreshold: 0.5 }\n *\n * @example\n * // Per-language thresholds\n * { confidenceThreshold: { es: 0.7, ja: 0.1, ko: 0.05, '*': 0.5 } }\n */\n confidenceThreshold: number | Record<string, number>;\n /** Strategy: 'semantic' (default), 'i18n', or 'auto' (semantic then i18n) */\n strategy: 'semantic' | 'i18n' | 'auto';\n /**\n * @deprecated Never implemented — `preprocessToEnglish` always returns a\n * string, and on translation failure that string is the original source\n * (there is nothing else it could return). The option has had no effect in\n * any released version and is ignored; it will be removed in a future major.\n */\n fallbackToOriginal?: boolean;\n /** Optional i18n toEnglish function (loaded dynamically if available) */\n i18nToEnglish?: (input: string, locale: string) => string;\n}\n\n/** The per-path seam — see module doc. */\nexport interface PreprocessorHooks {\n isLanguageRegistered(lang: string): boolean;\n /**\n * Parse ONE statement in `lang` and render it to English. Return null\n * when it cannot be translated (below-threshold confidence, no node, …);\n * the skeleton then tries the remaining strategies / falls back.\n */\n translateSingle(src: string, lang: string, threshold: number): string | null;\n}\n\nconst DEFAULT_THRESHOLD = 0.5;\n\nconst DEFAULT_CONFIG: PreprocessorConfig = {\n confidenceThreshold: DEFAULT_THRESHOLD,\n strategy: 'semantic',\n};\n\n/**\n * Resolve the confidence threshold for a specific language.\n * Supports both a single number and a per-language map with '*' as default.\n */\nfunction resolveThreshold(threshold: number | Record<string, number>, lang: string): number {\n if (typeof threshold === 'number') return threshold;\n return threshold[lang] ?? threshold['*'] ?? DEFAULT_THRESHOLD;\n}\n\n/**\n * Match _hyperscript event handler prefix: \"on [every] <event>[filter][.modifiers] \"\n *\n * Examples:\n * \"on click toggle .active\" → prefix: \"on click \", commands: \"toggle .active\"\n * \"on every click toggle .active\" → prefix: \"on every click \", commands: \"toggle .active\"\n * \"on click.debounce(300) toggle .x\" → prefix: \"on click.debounce(300) \", commands: \"toggle .x\"\n * \"on keyup[key=='Enter'] set x to 1\" → prefix: \"on keyup[key=='Enter'] \", commands: \"set x to 1\"\n * \"on click from body toggle .active\" → prefix: \"on click from body \", commands: \"toggle .active\"\n */\nconst EVENT_PREFIX_RE =\n /^(on\\s+(?:every\\s+)?[\\w-]+(?:\\[.*?\\])?(?:\\.[\\w-]+(?:\\([^)]*\\))?)*(?:\\s+from\\s+\\S+)?(?:\\s+queue\\s+\\w+)?\\s+)/;\n\nfunction stripEventPrefix(src: string): { prefix: string; commands: string } | null {\n const match = src.match(EVENT_PREFIX_RE);\n if (!match) return null;\n const prefix = match[1];\n const commands = src.slice(prefix.length);\n if (!commands) return null;\n return { prefix, commands };\n}\n\n/**\n * Build a `preprocessToEnglish` function from the per-path hooks.\n *\n * Handles _hyperscript feature prefixes (e.g. \"on click\", \"on every keyup\")\n * by stripping them, translating only the command portion, then reassembling.\n */\nexport function createPreprocessToEnglish(\n hooks: PreprocessorHooks\n): (src: string, lang: string, config?: Partial<PreprocessorConfig>) => string {\n /**\n * Try semantic translation: parse the WHOLE string in the source\n * language, render it to English. Returns null if confidence is below\n * threshold. The semantic parser handles `then`-sequences, newlines,\n * loop/tell bodies and behavior blocks natively (top-level\n * command-sequence support landed with the multiset-recall arc), so\n * there is deliberately no pre-splitting here — see the module doc's\n * HISTORY note for the measured deletion of the old split fallback.\n */\n function trySemanticTranslation(src: string, lang: string, threshold: number): string | null {\n try {\n // Check if the semantic parser can handle this\n if (!hooks.isLanguageRegistered(lang)) return null;\n\n return hooks.translateSingle(src, lang, threshold);\n } catch {\n return null;\n }\n }\n\n /**\n * Try i18n grammar transformation to English.\n * Returns null if the result is identical to input (no translation happened).\n */\n function tryI18nTranslation(\n src: string,\n lang: string,\n toEnglish: (input: string, locale: string) => string\n ): string | null {\n try {\n const result = toEnglish(src, lang);\n return result !== src ? result : null;\n } catch {\n return null;\n }\n }\n\n /**\n * Try all configured translation strategies on the input.\n * Returns null if none succeed.\n */\n function tryTranslateWithStrategies(\n src: string,\n lang: string,\n cfg: PreprocessorConfig\n ): string | null {\n // Strategy: semantic-only\n if (cfg.strategy === 'semantic' || cfg.strategy === 'auto') {\n const threshold = resolveThreshold(cfg.confidenceThreshold, lang);\n const result = trySemanticTranslation(src, lang, threshold);\n if (result !== null) return result;\n }\n\n // Strategy: i18n fallback (auto mode or i18n-only)\n if ((cfg.strategy === 'auto' || cfg.strategy === 'i18n') && cfg.i18nToEnglish) {\n const result = tryI18nTranslation(src, lang, cfg.i18nToEnglish);\n if (result !== null) return result;\n }\n\n return null;\n }\n\n return function preprocessToEnglish(\n src: string,\n lang: string,\n config: Partial<PreprocessorConfig> = {}\n ): string {\n // English→English is identity; skip semantic parsing which may mangle\n // the input. (Historically full-path only — the slim path gained it in\n // the shared-skeleton extraction, deliberately: it is a mangle guard.)\n if (lang === 'en') return src;\n\n const cfg = { ...DEFAULT_CONFIG, ...config };\n\n // Try translating the full string first\n const fullResult = tryTranslateWithStrategies(src, lang, cfg);\n if (fullResult !== null) return fullResult;\n\n // If full translation failed, try stripping event/feature prefix.\n // _hyperscript attributes often contain \"on <event> <commands>\" — the semantic\n // parser only understands command syntax, not event declarations.\n const stripped = stripEventPrefix(src);\n if (stripped) {\n const translated = tryTranslateWithStrategies(stripped.commands, lang, cfg);\n if (translated !== null) return stripped.prefix + translated;\n }\n\n // Fallback: return original (unconditional — see fallbackToOriginal's\n // deprecation note; the string contract leaves nothing else to return)\n return src;\n };\n}\n","/**\n * Attribute translator\n *\n * _hyperscript.org's Runtime#getScript is a private class field (`#getScript`),\n * not a property reachable through `internals.runtime` — every plugin variant\n * in this package used to monkey-patch `internals.runtime.getScript`, which\n * silently no-ops against current _hyperscript.org builds (the assignment\n * creates a stray own-property that the runtime's internal `#getScript()`\n * calls never read, so translation never happens and errors are swallowed by\n * `runtime.getScript.bind` throwing before any hyperscript even parses).\n *\n * `addBeforeProcessHook` is the supported, public extension point instead: it\n * fires on the subtree root passed to `processNode()` before the runtime reads\n * whichever configured attribute (`_`, `script`, `data-script` by default) or\n * `<script type=\"text/hyperscript\">` body holds the source. Rewriting that\n * attribute/body in place — before the runtime's own scan reaches it — gets\n * the same \"translate before parse\" effect through a mechanism the runtime\n * actually calls.\n */\n\nexport interface HyperscriptHost {\n addBeforeProcessHook?: (fn: (elt: Element) => void) => void;\n config?: { attributes?: string };\n}\n\n/** Elements already processed, so a later `processNode()` call over the same\n * subtree (e.g. a sibling swap re-scanning a shared ancestor) doesn't\n * re-translate already-English text as if it were still the original language.\n * A WeakSet instead of a marker attribute: the same idempotency with zero DOM\n * mutation (devtools/serialization show exactly what the author wrote).\n * Serialize→reparse (e.g. an innerHTML round-trip) produces NEW elements that\n * are re-processed — safe, because re-translating already-English text is\n * confidence-gated into a no-op. */\nconst processed = new WeakSet<Element>();\n\nfunction scriptAttributeNames(hs: HyperscriptHost): string[] {\n const raw = hs.config?.attributes ?? '_, script, data-script';\n return raw\n .split(',')\n .map(s => s.trim())\n .filter(Boolean);\n}\n\nfunction findScriptAttribute(elt: Element, attrNames: string[]): string | null {\n for (const name of attrNames) {\n if (elt.hasAttribute(name)) return name;\n }\n return null;\n}\n\n/**\n * Install a translator that rewrites non-English script attributes to English\n * in place, before `_hyperscript.org` parses them.\n *\n * @param translate Given the raw source and its element, return the English\n * translation, or `null`/the same string to leave the element untouched\n * (English input, unresolved language, translation failure, etc).\n */\nexport function installAttributeTranslator(\n hs: HyperscriptHost,\n translate: (src: string, elt: Element) => string | null\n): void {\n if (typeof hs.addBeforeProcessHook !== 'function') {\n console.warn(\n '[hyperscript-i18n] _hyperscript.addBeforeProcessHook is unavailable — ' +\n 'this build of _hyperscript.org is not supported. Load a current version ' +\n 'from https://unpkg.com/hyperscript.org.'\n );\n return;\n }\n\n const attrNames = scriptAttributeNames(hs);\n const selector = [...attrNames.map(a => `[${a}]`), 'script[type=\"text/hyperscript\"]'].join(', ');\n\n const translateOne = (elt: Element): void => {\n if (processed.has(elt)) return;\n\n if (elt instanceof HTMLScriptElement && elt.type === 'text/hyperscript') {\n const src = elt.textContent ?? '';\n if (!src) return;\n const english = translate(src, elt);\n processed.add(elt);\n if (english != null && english !== src) elt.textContent = english;\n return;\n }\n\n const attr = findScriptAttribute(elt, attrNames);\n if (!attr) return;\n const src = elt.getAttribute(attr);\n if (!src) return;\n const english = translate(src, elt);\n processed.add(elt);\n if (english != null && english !== src) elt.setAttribute(attr, english);\n };\n\n hs.addBeforeProcessHook((root: Element) => {\n if (!root || typeof root.querySelectorAll !== 'function') return;\n if (typeof root.matches === 'function' && root.matches(selector)) translateOne(root);\n root.querySelectorAll(selector).forEach(translateOne);\n });\n}\n","/**\n * Host-parser validity gate (review item F8).\n *\n * After the preprocessor rewrites an attribute to English, the plugin asks\n * the HOST _hyperscript runtime — the same parser that will consume the\n * rewrite moments later — whether the result actually parses. On rejection\n * the plugin falls back to the author's original text, so any parse error\n * the author then sees names code they wrote, not invisible generated\n * English. This is the runtime analog of the offline R4 canonical-validity\n * gate, and the F5 arc measured its failure class shipping in practice:\n * until the whole-string-first reorder (#899), 256 corpus rows rendered\n * English the engine rejects, with no warning anywhere.\n *\n * The engine has two failure channels — `parse().errors` collects grammar\n * errors, and the tokenizer THROWS on an unknown character — folded here\n * the same way test/whole-string-first.test.ts folds them.\n *\n * Zero-dependency module: shared by the full, slim, and lite plugin\n * variants, which must not share heavier import chains (the slim/lite\n * bundles exclude the full semantic package by construction).\n */\n\nexport interface HyperscriptParseHost {\n parse?: (src: string) => { errors?: unknown[] } | null | undefined;\n}\n\n/**\n * True when the host's parser accepts `src`. Also true when the host\n * exposes no `parse()` — with nothing to validate against, the gate\n * degrades to a no-op rather than suppressing translation on unusual\n * builds (same graceful posture as the `addBeforeProcessHook` check).\n */\nexport function acceptedByHost(hs: HyperscriptParseHost, src: string): boolean {\n if (typeof hs.parse !== 'function') return true;\n try {\n const result = hs.parse(src);\n return !result?.errors || result.errors.length === 0;\n } catch {\n return false;\n }\n}\n\n/** Languages already warned about a rejected translation this page load —\n * same warn-once-per-lang convention as the unchanged-translation warning\n * (and htmx-adapter's warnMissingLangOnce). */\nconst warnedRejectedLang = new Set<string>();\n\n/** Reset the warn-once state. Mainly for tests. */\nexport function resetHostValidationWarnings(): void {\n warnedRejectedLang.clear();\n}\n\nexport function warnRejectedOnce(lang: string, src: string, english: string): void {\n if (warnedRejectedLang.has(lang)) return;\n warnedRejectedLang.add(lang);\n console.warn(\n `[hyperscript-i18n] Translation for lang=\"${lang}\" rendered hyperscript the host parser ` +\n `rejects — falling back to the original text. ` +\n `Source: \"${src.length > 60 ? src.slice(0, 60) + '…' : src}\" → ` +\n `\"${english.length > 60 ? english.slice(0, 60) + '…' : english}\". ` +\n 'Further elements in this language stay quiet — enable { debug: true } for per-element detail.'\n );\n}\n","/**\n * _hyperscript Plugin\n *\n * Registers with _hyperscript.use() to rewrite non-English hyperscript\n * attributes into English before _hyperscript.org parses them.\n */\n\nimport { resolveLanguage } from './language-resolver';\nimport { preprocessToEnglish, type PreprocessorConfig } from './preprocessor';\nimport { installAttributeTranslator, type HyperscriptHost } from './attribute-translator';\nimport {\n acceptedByHost,\n warnRejectedOnce,\n resetHostValidationWarnings,\n type HyperscriptParseHost,\n} from './host-validate';\n\nexport interface PluginOptions extends Partial<PreprocessorConfig> {\n /** Default language for all elements (overridable per-element). */\n defaultLanguage?: string;\n /** Custom attribute name for per-element language. Default: \"data-lang\" */\n languageAttribute?: string;\n /** Enable debug logging to console. Default: false */\n debug?: boolean;\n /**\n * Validate rendered English on the HOST parser before committing the\n * rewrite; on rejection, fall back to the original text (so parse errors\n * name the author's code, not generated English). Default: true.\n * No-op on host builds that expose no `parse()`.\n */\n validateWithHost?: boolean;\n}\n\n/** Languages already warned about an unchanged translation this page load.\n * Unchanged output is common and often legitimate (canonical-English\n * hyperscript under a non-en lang scope), so warning per element per\n * processNode was pure noise — mirror htmx-adapter's warn-once-per-lang\n * convention and leave per-element detail to `debug: true`. */\nconst warnedUnchangedLang = new Set<string>();\n\n/** Reset the warn-once state (unchanged + host-rejected). Mainly for tests. */\nexport function resetTranslationWarnings(): void {\n warnedUnchangedLang.clear();\n resetHostValidationWarnings();\n}\n\nfunction warnUnchangedOnce(lang: string, src: string): void {\n if (warnedUnchangedLang.has(lang)) return;\n warnedUnchangedLang.add(lang);\n console.warn(\n `[hyperscript-i18n] Translation unchanged for lang=\"${lang}\": \"${src.length > 60 ? src.slice(0, 60) + '…' : src}\". ` +\n 'This is fine if the source is already canonical English; otherwise the input may not match ' +\n 'any known pattern, or the language may not be registered. Original text is passed to ' +\n '_hyperscript as-is. Further elements in this language stay quiet — enable { debug: true } ' +\n 'for per-element detail.'\n );\n}\n\n/**\n * Create a _hyperscript plugin that enables multilingual hyperscript.\n *\n * @example\n * // Basic usage\n * _hyperscript.use(hyperscriptI18n());\n *\n * @example\n * // With options\n * _hyperscript.use(hyperscriptI18n({\n * defaultLanguage: 'ja',\n * confidenceThreshold: 0.6,\n * debug: true,\n * }));\n */\nexport function hyperscriptI18n(options: PluginOptions = {}) {\n return function plugin(hs: unknown): void {\n const host = hs as HyperscriptHost & HyperscriptParseHost;\n installAttributeTranslator(host, (src, elt) => {\n // Resolve language\n const lang = resolveLanguageWithOptions(elt, options);\n\n // If English or no language detected, pass through\n if (!lang || lang === 'en') return src;\n\n // Preprocess to English\n const english = preprocessToEnglish(src, lang, options);\n\n if (english !== src) {\n // Validity gate: the host parser is the consumer of this rewrite —\n // if it rejects the English, committing it would only trade a\n // translation gap for a parse error naming code the author never\n // wrote. Fall back to the original text instead.\n if (options.validateWithHost !== false && !acceptedByHost(host, english)) {\n if (options.debug) {\n console.log(\n `[hyperscript-i18n] ${lang}: host rejected \"${english}\" — keeping \"${src}\"`\n );\n } else {\n warnRejectedOnce(lang, src, english);\n }\n return src;\n }\n if (options.debug) {\n console.log(`[hyperscript-i18n] ${lang}: \"${src}\" → \"${english}\"`);\n }\n } else if (options.debug) {\n console.log(`[hyperscript-i18n] ${lang}: unchanged \"${src}\"`);\n } else {\n warnUnchangedOnce(lang, src);\n }\n\n return english;\n });\n };\n}\n\n/**\n * Resolve language with plugin options factored in.\n */\nfunction resolveLanguageWithOptions(elt: Element, options: PluginOptions): string | null {\n // Check custom attribute name\n if (options.languageAttribute) {\n const custom = elt.getAttribute(options.languageAttribute);\n if (custom) return custom.split('-')[0].toLowerCase();\n }\n\n // Standard resolution\n const resolved = resolveLanguage(elt);\n if (resolved) return resolved;\n\n // Fall back to default language\n return options.defaultLanguage ?? null;\n}\n\n/**\n * Standalone preprocessing function for programmatic use.\n * Call this when using _hyperscript.evaluate() or _hyperscript(\"code\") directly.\n *\n * @example\n * const english = preprocess(\"トグル .active\", \"ja\");\n * _hyperscript(english);\n */\nexport function preprocess(\n src: string,\n lang: string,\n config: Partial<PreprocessorConfig> = {}\n): string {\n if (lang === 'en') return src;\n return preprocessToEnglish(src, lang, config);\n}\n"],"mappings":"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","names":["preprocessToEnglish","translate","src","english"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Preprocessor
|
|
2
|
+
* Preprocessor core — the shared skeleton of the full and slim
|
|
3
|
+
* preprocessors.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
5
|
+
* Everything that is NOT genuinely different between the two paths lives
|
|
6
|
+
* here exactly once: config defaults/threshold resolution, strategy
|
|
7
|
+
* ordering (semantic → optional i18n), event-prefix stripping, and the
|
|
8
|
+
* return-original fallback. The two real differences stay with each path,
|
|
9
|
+
* injected as hooks:
|
|
10
|
+
*
|
|
11
|
+
* - HOW one statement is parsed and rendered to English
|
|
12
|
+
* (`translateSingle`): the full path uses semantic's parseSemantic +
|
|
13
|
+
* render + a translate() rescue for confident-but-nodeless parses;
|
|
14
|
+
* the slim path uses parseWithConfidence + the custom
|
|
15
|
+
* hyperscript-renderer (no English language data).
|
|
16
|
+
* - WHERE the registry lookup comes from (`isLanguageRegistered`):
|
|
17
|
+
* `@lokascript/semantic` vs `…/core` — under tsup's split dist these
|
|
18
|
+
* are separate registry instances, so each path must bring its own.
|
|
19
|
+
*
|
|
20
|
+
* Behavior is pinned by the parity ratchet
|
|
21
|
+
* (test/preprocessor-parity.*.test.ts + the committed fixture): both paths
|
|
22
|
+
* are byte-identical to their pre-extraction outputs over the corpus.
|
|
23
|
+
*
|
|
24
|
+
* HISTORY: the skeleton used to carry a compound-splitting fallback
|
|
25
|
+
* (`splitStatements` on localized `then` keywords + newlines,
|
|
26
|
+
* `translateCompound` rejoining pieces with a hardcoded ` then `), run when
|
|
27
|
+
* the whole-string parse declined. Deleted 2026-08-07 after measurement:
|
|
28
|
+
* over all 3703 patterns.db corpus translations plus the parity corpus, on
|
|
29
|
+
* BOTH paths, the split arm produced ZERO final outputs (full: 115
|
|
30
|
+
* invocations, all returned null; slim: 32, all null) — post-whole-string-
|
|
31
|
+
* first (#899), an input whose pieces each parse would have parsed whole,
|
|
32
|
+
* so the arm's success condition is self-defeating. It was also defective
|
|
33
|
+
* twice over: the regex split was string-literal-blind (`say 'now then
|
|
34
|
+
* later'` split inside the literal) and the ` then ` rejoin is invalid
|
|
35
|
+
* immediately after block headers. Deleting it erased both defect classes
|
|
36
|
+
* wholesale; the parity fixture regenerated byte-identical.
|
|
6
37
|
*/
|
|
7
38
|
interface PreprocessorConfig {
|
|
8
39
|
/**
|
|
@@ -22,21 +53,36 @@ interface PreprocessorConfig {
|
|
|
22
53
|
confidenceThreshold: number | Record<string, number>;
|
|
23
54
|
/** Strategy: 'semantic' (default), 'i18n', or 'auto' (semantic then i18n) */
|
|
24
55
|
strategy: 'semantic' | 'i18n' | 'auto';
|
|
25
|
-
/**
|
|
26
|
-
|
|
56
|
+
/**
|
|
57
|
+
* @deprecated Never implemented — `preprocessToEnglish` always returns a
|
|
58
|
+
* string, and on translation failure that string is the original source
|
|
59
|
+
* (there is nothing else it could return). The option has had no effect in
|
|
60
|
+
* any released version and is ignored; it will be removed in a future major.
|
|
61
|
+
*/
|
|
62
|
+
fallbackToOriginal?: boolean;
|
|
27
63
|
/** Optional i18n toEnglish function (loaded dynamically if available) */
|
|
28
64
|
i18nToEnglish?: (input: string, locale: string) => string;
|
|
29
65
|
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Preprocessor (full path)
|
|
69
|
+
*
|
|
70
|
+
* Translates non-English hyperscript to English using the semantic parser
|
|
71
|
+
* (with optional i18n fallback), so the original _hyperscript can parse it.
|
|
72
|
+
*
|
|
73
|
+
* The strategy/split/strip skeleton lives in preprocessor-core.ts, shared
|
|
74
|
+
* with the slim path — only HOW one statement is parsed and rendered
|
|
75
|
+
* differs here: semantic's parseSemantic + render('en'), plus a
|
|
76
|
+
* translate() rescue when the parse is confident but yields no node.
|
|
77
|
+
*/
|
|
78
|
+
|
|
30
79
|
/**
|
|
31
80
|
* Preprocess non-English hyperscript into English.
|
|
32
81
|
*
|
|
33
82
|
* Uses the semantic parser to parse the input in the source language,
|
|
34
83
|
* then renders back to English. Falls through to i18n if configured.
|
|
35
|
-
*
|
|
36
|
-
* Handles _hyperscript feature prefixes (e.g. "on click", "on every keyup")
|
|
37
|
-
* by stripping them, translating only the command portion, then reassembling.
|
|
38
84
|
*/
|
|
39
|
-
declare
|
|
85
|
+
declare const preprocessToEnglish: (src: string, lang: string, config?: Partial<PreprocessorConfig>) => string;
|
|
40
86
|
|
|
41
87
|
/**
|
|
42
88
|
* _hyperscript Plugin
|
|
@@ -52,7 +98,16 @@ interface PluginOptions extends Partial<PreprocessorConfig> {
|
|
|
52
98
|
languageAttribute?: string;
|
|
53
99
|
/** Enable debug logging to console. Default: false */
|
|
54
100
|
debug?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Validate rendered English on the HOST parser before committing the
|
|
103
|
+
* rewrite; on rejection, fall back to the original text (so parse errors
|
|
104
|
+
* name the author's code, not generated English). Default: true.
|
|
105
|
+
* No-op on host builds that expose no `parse()`.
|
|
106
|
+
*/
|
|
107
|
+
validateWithHost?: boolean;
|
|
55
108
|
}
|
|
109
|
+
/** Reset the warn-once state (unchanged + host-rejected). Mainly for tests. */
|
|
110
|
+
declare function resetTranslationWarnings(): void;
|
|
56
111
|
/**
|
|
57
112
|
* Create a _hyperscript plugin that enables multilingual hyperscript.
|
|
58
113
|
*
|
|
@@ -91,9 +146,17 @@ declare function preprocess(src: string, lang: string, config?: Partial<Preproce
|
|
|
91
146
|
* Resolution order:
|
|
92
147
|
* 1. `data-lang` attribute on the element itself
|
|
93
148
|
* 2. `data-hyperscript-lang` attribute on the element or closest ancestor
|
|
94
|
-
* 3. `lang` attribute on
|
|
95
|
-
*
|
|
149
|
+
* 3. `lang` attribute on the element or closest ancestor (the HTML-standard
|
|
150
|
+
* cascade — a `<section lang="es">` localizes everything inside it, and a
|
|
151
|
+
* nested `lang="en"` opts back out). This is how the paired htmx-adapter
|
|
152
|
+
* (`langOf()`) and loka-js resolve language, so `hx-*` and `_` attributes
|
|
153
|
+
* on the same element agree.
|
|
154
|
+
* 4. `lang` on `<html>` via `document.documentElement` — only reachable when
|
|
155
|
+
* the element is DETACHED (step 3's ancestor walk covers `<html>` for
|
|
156
|
+
* attached elements): a hook processing a not-yet-inserted fragment still
|
|
157
|
+
* picks up the page default.
|
|
158
|
+
* 5. null (assume English, no preprocessing needed)
|
|
96
159
|
*/
|
|
97
160
|
declare function resolveLanguage(elt: Element): string | null;
|
|
98
161
|
|
|
99
|
-
export { type PluginOptions, type PreprocessorConfig, hyperscriptI18n, preprocess, preprocessToEnglish, resolveLanguage };
|
|
162
|
+
export { type PluginOptions, type PreprocessorConfig, hyperscriptI18n, preprocess, preprocessToEnglish, resetTranslationWarnings, resolveLanguage };
|