@lokascript/hyperscript-adapter 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/dist/hyperscript-i18n-ar.global.js +4 -0
  4. package/dist/hyperscript-i18n-bn.global.js +4 -0
  5. package/dist/hyperscript-i18n-de.global.js +4 -0
  6. package/dist/hyperscript-i18n-east-asian.global.js +4 -0
  7. package/dist/hyperscript-i18n-en.global.js +6 -0
  8. package/dist/hyperscript-i18n-es.global.js +4 -0
  9. package/dist/hyperscript-i18n-fr.global.js +4 -0
  10. package/dist/hyperscript-i18n-he.global.js +4 -0
  11. package/dist/hyperscript-i18n-hi.global.js +4 -0
  12. package/dist/hyperscript-i18n-id.global.js +4 -0
  13. package/dist/hyperscript-i18n-it.global.js +4 -0
  14. package/dist/hyperscript-i18n-ja.global.js +4 -0
  15. package/dist/hyperscript-i18n-ko.global.js +4 -0
  16. package/dist/hyperscript-i18n-lite.global.js +1 -0
  17. package/dist/hyperscript-i18n-ms.global.js +4 -0
  18. package/dist/hyperscript-i18n-pl.global.js +4 -0
  19. package/dist/hyperscript-i18n-pt.global.js +4 -0
  20. package/dist/hyperscript-i18n-qu.global.js +4 -0
  21. package/dist/hyperscript-i18n-ru.global.js +4 -0
  22. package/dist/hyperscript-i18n-slavic.global.js +4 -0
  23. package/dist/hyperscript-i18n-south-asian.global.js +4 -0
  24. package/dist/hyperscript-i18n-southeast-asian.global.js +4 -0
  25. package/dist/hyperscript-i18n-sw.global.js +4 -0
  26. package/dist/hyperscript-i18n-th.global.js +4 -0
  27. package/dist/hyperscript-i18n-tl.global.js +4 -0
  28. package/dist/hyperscript-i18n-tr.global.js +4 -0
  29. package/dist/hyperscript-i18n-uk.global.js +4 -0
  30. package/dist/hyperscript-i18n-vi.global.js +4 -0
  31. package/dist/hyperscript-i18n-western.global.js +4 -0
  32. package/dist/hyperscript-i18n-zh.global.js +4 -0
  33. package/dist/hyperscript-i18n.global.js +8 -0
  34. package/dist/hyperscript-i18n.global.js.map +1 -0
  35. package/dist/index.cjs +203 -0
  36. package/dist/index.cjs.map +1 -0
  37. package/dist/index.d.cts +99 -0
  38. package/dist/index.d.ts +99 -0
  39. package/dist/index.js +179 -0
  40. package/dist/index.js.map +1 -0
  41. package/package.json +169 -0
  42. package/src/browser-lite.ts +159 -0
  43. package/src/browser.ts +74 -0
  44. package/src/bundles/ar.ts +6 -0
  45. package/src/bundles/bn.ts +6 -0
  46. package/src/bundles/de.ts +6 -0
  47. package/src/bundles/east-asian.ts +8 -0
  48. package/src/bundles/en.ts +6 -0
  49. package/src/bundles/es.ts +6 -0
  50. package/src/bundles/fr.ts +6 -0
  51. package/src/bundles/he.ts +6 -0
  52. package/src/bundles/hi.ts +6 -0
  53. package/src/bundles/id.ts +6 -0
  54. package/src/bundles/it.ts +6 -0
  55. package/src/bundles/ja.ts +6 -0
  56. package/src/bundles/ko.ts +6 -0
  57. package/src/bundles/ms.ts +6 -0
  58. package/src/bundles/pl.ts +6 -0
  59. package/src/bundles/pt.ts +6 -0
  60. package/src/bundles/qu.ts +6 -0
  61. package/src/bundles/ru.ts +6 -0
  62. package/src/bundles/shared.ts +65 -0
  63. package/src/bundles/slavic.ts +8 -0
  64. package/src/bundles/south-asian.ts +7 -0
  65. package/src/bundles/southeast-asian.ts +10 -0
  66. package/src/bundles/sw.ts +6 -0
  67. package/src/bundles/th.ts +6 -0
  68. package/src/bundles/tl.ts +6 -0
  69. package/src/bundles/tr.ts +6 -0
  70. package/src/bundles/uk.ts +6 -0
  71. package/src/bundles/vi.ts +6 -0
  72. package/src/bundles/western.ts +9 -0
  73. package/src/bundles/zh.ts +6 -0
  74. package/src/derive-syntax.ts +126 -0
  75. package/src/generated/syntax-table.ts +61 -0
  76. package/src/hyperscript-renderer.ts +150 -0
  77. package/src/index.ts +27 -0
  78. package/src/language-resolver.ts +41 -0
  79. package/src/plugin.ts +119 -0
  80. package/src/preprocessor.ts +251 -0
  81. package/src/slim-plugin.ts +60 -0
  82. package/src/slim-preprocessor.ts +190 -0
@@ -0,0 +1,6 @@
1
+ /** Self-contained: Turkish (renders to English via custom renderer) */
2
+ import '@lokascript/semantic/languages/tr';
3
+ export { plugin, preprocess, resolveLanguage } from './shared';
4
+ export const supportedLanguages = ['tr'];
5
+ import { autoRegister } from './shared';
6
+ autoRegister();
@@ -0,0 +1,6 @@
1
+ /** Self-contained: Ukrainian (renders to English via custom renderer) */
2
+ import '@lokascript/semantic/languages/uk';
3
+ export { plugin, preprocess, resolveLanguage } from './shared';
4
+ export const supportedLanguages = ['uk'];
5
+ import { autoRegister } from './shared';
6
+ autoRegister();
@@ -0,0 +1,6 @@
1
+ /** Self-contained: Vietnamese (renders to English via custom renderer) */
2
+ import '@lokascript/semantic/languages/vi';
3
+ export { plugin, preprocess, resolveLanguage } from './shared';
4
+ export const supportedLanguages = ['vi'];
5
+ import { autoRegister } from './shared';
6
+ autoRegister();
@@ -0,0 +1,9 @@
1
+ /** Self-contained: Western/European languages (es, pt, fr, de) */
2
+ import '@lokascript/semantic/languages/es';
3
+ import '@lokascript/semantic/languages/pt';
4
+ import '@lokascript/semantic/languages/fr';
5
+ import '@lokascript/semantic/languages/de';
6
+ export { plugin, preprocess, resolveLanguage } from './shared';
7
+ export const supportedLanguages = ['es', 'pt', 'fr', 'de'];
8
+ import { autoRegister } from './shared';
9
+ autoRegister();
@@ -0,0 +1,6 @@
1
+ /** Self-contained: Chinese (renders to English via custom renderer) */
2
+ import '@lokascript/semantic/languages/zh';
3
+ export { plugin, preprocess, resolveLanguage } from './shared';
4
+ export const supportedLanguages = ['zh'];
5
+ import { autoRegister } from './shared';
6
+ autoRegister();
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Derive SYNTAX tables from CommandSchema definitions.
3
+ *
4
+ * Instead of maintaining a hand-coded SYNTAX table that can drift from
5
+ * the canonical schemas, this module generates it from the semantic
6
+ * package's command-schemas + language profile.
7
+ *
8
+ * The generated table maps each command to ordered [role, preposition]
9
+ * tuples for rendering.
10
+ */
11
+
12
+ import type { CommandSchema, RoleSpec, LanguageProfile } from '@lokascript/semantic';
13
+ import type { RoleMarker } from '@lokascript/semantic/core';
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Types
17
+ // ---------------------------------------------------------------------------
18
+
19
+ export type SyntaxEntry = readonly [string, string][];
20
+ export type SyntaxTable = Record<string, SyntaxEntry>;
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Overrides for commands whose rendering doesn't cleanly derive from
24
+ // schema roles (different role names, simplified structure, etc.)
25
+ // ---------------------------------------------------------------------------
26
+
27
+ /**
28
+ * Commands where the SYNTAX rendering uses a different mapping
29
+ * than what derives from schema roles + language profile.
30
+ *
31
+ * - repeat: Schema has loopType/quantity/event/source, but English renders
32
+ * as "repeat N" or "repeat until <condition>" using simplified roles
33
+ */
34
+ const RENDER_OVERRIDES: SyntaxTable = {
35
+ repeat: [
36
+ ['quantity', ''],
37
+ ['condition', 'until'],
38
+ ],
39
+ };
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Derivation
43
+ // ---------------------------------------------------------------------------
44
+
45
+ /**
46
+ * Resolve the preposition for a role in a specific command and language.
47
+ *
48
+ * Priority:
49
+ * 1. Render-specific override for this language (renderOverride[lang])
50
+ * 2. Command-specific marker override for this language (markerOverride[lang])
51
+ * 3. Language profile's default roleMarker for this role
52
+ * 4. '' (no preposition)
53
+ */
54
+ function resolvePreposition(roleSpec: RoleSpec, profile: LanguageProfile, lang: string): string {
55
+ // 1. Render-time override for this language
56
+ if (roleSpec.renderOverride?.[lang] !== undefined) {
57
+ return roleSpec.renderOverride[lang];
58
+ }
59
+
60
+ // 2. Command-specific marker override for this language
61
+ if (roleSpec.markerOverride?.[lang] !== undefined) {
62
+ return roleSpec.markerOverride[lang];
63
+ }
64
+
65
+ // 3. Profile default for this role
66
+ const roleMarker = (profile.roleMarkers as Record<string, RoleMarker | undefined>)[roleSpec.role];
67
+ if (roleMarker) {
68
+ return roleMarker.primary;
69
+ }
70
+
71
+ // 4. No preposition
72
+ return '';
73
+ }
74
+
75
+ /**
76
+ * Derive the SYNTAX table from command schemas and a language profile.
77
+ *
78
+ * For each command:
79
+ * - Sort roles by position (svoPosition for SVO/VSO, sovPosition for SOV)
80
+ * - Resolve each role's preposition for the given language
81
+ * - Return [role, preposition] tuples
82
+ *
83
+ * Commands in RENDER_OVERRIDES use their manual entry instead (English only).
84
+ */
85
+ export function deriveSyntax(
86
+ schemas: Record<string, CommandSchema>,
87
+ profile: LanguageProfile,
88
+ lang: string
89
+ ): SyntaxTable {
90
+ const syntax: Record<string, [string, string][]> = {};
91
+ const isSov = profile.wordOrder === 'SOV';
92
+
93
+ for (const [action, schema] of Object.entries(schemas)) {
94
+ // Use manual override for commands that don't derive cleanly (English only)
95
+ if (lang === 'en' && RENDER_OVERRIDES[action]) {
96
+ syntax[action] = RENDER_OVERRIDES[action] as [string, string][];
97
+ continue;
98
+ }
99
+
100
+ // Sort roles by position appropriate for word order
101
+ const sortedRoles = [...schema.roles].sort((a, b) => {
102
+ const posA = isSov ? (a.sovPosition ?? 99) : (a.svoPosition ?? 99);
103
+ const posB = isSov ? (b.sovPosition ?? 99) : (b.svoPosition ?? 99);
104
+ return posA - posB;
105
+ });
106
+
107
+ // Derive [role, preposition] tuples
108
+ syntax[action] = sortedRoles.map(roleSpec => [
109
+ roleSpec.role,
110
+ resolvePreposition(roleSpec, profile, lang),
111
+ ]);
112
+ }
113
+
114
+ return syntax;
115
+ }
116
+
117
+ /**
118
+ * Derive the English SYNTAX table.
119
+ * Convenience wrapper that calls deriveSyntax with 'en'.
120
+ */
121
+ export function deriveEnglishSyntax(
122
+ schemas: Record<string, CommandSchema>,
123
+ englishProfile: LanguageProfile
124
+ ): SyntaxTable {
125
+ return deriveSyntax(schemas, englishProfile, 'en');
126
+ }
@@ -0,0 +1,61 @@
1
+ // @generated — do not edit manually.
2
+ // Regenerate with: npm run generate:syntax
3
+ //
4
+ // Derived from command schemas + English profile via deriveEnglishSyntax().
5
+
6
+ export const SYNTAX: Record<string, readonly [string, string][]> = {
7
+ add: [['patient', ''], ['destination', 'to']],
8
+ append: [['patient', ''], ['destination', 'to']],
9
+ async: [],
10
+ beep: [['patient', '']],
11
+ behavior: [['patient', '']],
12
+ blur: [['patient', '']],
13
+ break: [],
14
+ call: [['patient', '']],
15
+ clone: [['patient', ''], ['destination', 'into']],
16
+ compound: [],
17
+ continue: [],
18
+ copy: [['patient', '']],
19
+ decrement: [['patient', ''], ['quantity', 'by']],
20
+ default: [['destination', ''], ['patient', 'to']],
21
+ else: [],
22
+ exit: [],
23
+ fetch: [['source', ''], ['responseType', 'as'], ['method', 'via'], ['destination', 'on']],
24
+ focus: [['patient', '']],
25
+ for: [['patient', ''], ['source', 'in']],
26
+ get: [['source', ''], ['destination', 'on']],
27
+ go: [['destination', '']],
28
+ halt: [['patient', '']],
29
+ hide: [['patient', ''], ['style', 'with']],
30
+ if: [['condition', '']],
31
+ increment: [['patient', ''], ['quantity', 'by']],
32
+ init: [],
33
+ install: [['patient', ''], ['destination', 'on']],
34
+ js: [['patient', '']],
35
+ log: [['patient', '']],
36
+ make: [['patient', '']],
37
+ measure: [['patient', ''], ['source', 'of']],
38
+ morph: [['destination', ''], ['patient', 'to']],
39
+ on: [['event', ''], ['source', 'from']],
40
+ pick: [['patient', ''], ['source', 'from']],
41
+ prepend: [['patient', ''], ['destination', 'to']],
42
+ put: [['patient', ''], ['destination', 'into']],
43
+ remove: [['patient', ''], ['source', 'from']],
44
+ render: [['patient', ''], ['style', 'with']],
45
+ repeat: [['quantity', ''], ['condition', 'until']],
46
+ return: [['patient', '']],
47
+ send: [['event', ''], ['destination', 'to']],
48
+ set: [['destination', ''], ['patient', 'to']],
49
+ settle: [['patient', '']],
50
+ show: [['patient', ''], ['style', 'with']],
51
+ swap: [['method', ''], ['destination', 'of'], ['patient', 'with']],
52
+ take: [['patient', ''], ['source', 'from']],
53
+ tell: [['destination', '']],
54
+ throw: [['patient', '']],
55
+ toggle: [['patient', ''], ['destination', 'on']],
56
+ transition: [['patient', ''], ['goal', 'to'], ['destination', 'on'], ['duration', 'over'], ['style', 'with']],
57
+ trigger: [['event', ''], ['destination', 'on']],
58
+ unless: [['condition', '']],
59
+ wait: [['patient', '']],
60
+ while: [['condition', '']],
61
+ };
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Hyperscript English Renderer
3
+ *
4
+ * Purpose-built renderer that converts SemanticNodes to standard English
5
+ * _hyperscript syntax. Unlike the semantic package's renderer (which uses
6
+ * pattern matching and requires English patterns/tokenizer/profile),
7
+ * this is a deterministic mapping — no registry lookups needed.
8
+ *
9
+ * This eliminates ~35 KB of English language data from per-language bundles.
10
+ */
11
+
12
+ import type {
13
+ SemanticNode,
14
+ SemanticValue,
15
+ SemanticRole,
16
+ EventHandlerSemanticNode,
17
+ CompoundSemanticNode,
18
+ } from '@lokascript/semantic/core';
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Per-command syntax: ordered [role, preposition] tuples.
22
+ // Empty string = no preposition (direct object).
23
+ //
24
+ // Generated from command schemas. Do not edit manually.
25
+ // Regenerate with: npm run generate:syntax
26
+ // ---------------------------------------------------------------------------
27
+
28
+ import { SYNTAX } from './generated/syntax-table';
29
+ export { SYNTAX };
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Main entry point
33
+ // ---------------------------------------------------------------------------
34
+
35
+ /**
36
+ * Render a SemanticNode to standard English _hyperscript syntax.
37
+ */
38
+ export function renderToHyperscript(node: SemanticNode): string {
39
+ switch (node.kind) {
40
+ case 'event-handler':
41
+ return renderEventHandler(node as EventHandlerSemanticNode);
42
+ case 'compound':
43
+ return renderCompound(node as CompoundSemanticNode);
44
+ default:
45
+ return renderCommand(node);
46
+ }
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Node-kind renderers
51
+ // ---------------------------------------------------------------------------
52
+
53
+ function renderEventHandler(node: EventHandlerSemanticNode): string {
54
+ const parts: string[] = ['on'];
55
+
56
+ // Event name
57
+ const event = node.roles.get('event');
58
+ if (event) {
59
+ parts.push(renderValue(event));
60
+ }
61
+
62
+ // Event source (from #element)
63
+ const source = node.roles.get('source');
64
+ if (source) {
65
+ parts.push('from', renderValue(source));
66
+ }
67
+
68
+ // Body commands
69
+ if (node.body && node.body.length > 0) {
70
+ const bodyParts = node.body.map(renderToHyperscript);
71
+ parts.push(bodyParts.join(' then '));
72
+ }
73
+
74
+ return parts.join(' ');
75
+ }
76
+
77
+ function renderCompound(node: CompoundSemanticNode): string {
78
+ const chainWord = node.chainType === 'async' ? 'async' : node.chainType;
79
+ return node.statements.map(renderToHyperscript).join(` ${chainWord} `);
80
+ }
81
+
82
+ function renderCommand(node: SemanticNode): string {
83
+ const syntax = SYNTAX[node.action];
84
+
85
+ // Known command: use syntax table
86
+ if (syntax) {
87
+ const parts: string[] = [node.action];
88
+ for (const [role, prep] of syntax) {
89
+ const value = node.roles.get(role as SemanticRole);
90
+ if (!value) continue;
91
+ // Skip implicit "me" destination (default in _hyperscript)
92
+ if (role === 'destination' && value.type === 'reference' && value.value === 'me') continue;
93
+ if (prep) parts.push(prep);
94
+ parts.push(renderValue(value));
95
+ }
96
+ return parts.join(' ');
97
+ }
98
+
99
+ // Unknown command: generic fallback — action then all roles
100
+ const parts: string[] = [node.action];
101
+ for (const [, value] of node.roles) {
102
+ parts.push(renderValue(value));
103
+ }
104
+ return parts.join(' ');
105
+ }
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Value renderers
109
+ // ---------------------------------------------------------------------------
110
+
111
+ function renderValue(value: SemanticValue): string {
112
+ switch (value.type) {
113
+ case 'literal':
114
+ if (typeof value.value === 'string' && value.dataType === 'string') {
115
+ return `"${value.value}"`;
116
+ }
117
+ return String(value.value);
118
+
119
+ case 'selector':
120
+ return value.value;
121
+
122
+ case 'reference':
123
+ return value.value;
124
+
125
+ case 'property-path':
126
+ return renderPropertyPath(value);
127
+
128
+ case 'expression':
129
+ return value.raw;
130
+ }
131
+ }
132
+
133
+ function renderPropertyPath(value: SemanticValue & { type: 'property-path' }): string {
134
+ const objectStr = renderValue(value.object);
135
+ const property = value.property;
136
+
137
+ // English possessive special forms
138
+ if (value.object.type === 'reference') {
139
+ switch (value.object.value) {
140
+ case 'me':
141
+ return `my ${property}`;
142
+ case 'it':
143
+ return `its ${property}`;
144
+ case 'you':
145
+ return `your ${property}`;
146
+ }
147
+ }
148
+
149
+ return `${objectStr}'s ${property}`;
150
+ }
package/src/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @lokascript/hyperscript-adapter
3
+ *
4
+ * Multilingual adapter plugin for the original _hyperscript.
5
+ * Enables writing hyperscript in any of 24 supported languages.
6
+ *
7
+ * @example
8
+ * import { hyperscriptI18n } from '@lokascript/hyperscript-adapter';
9
+ * _hyperscript.use(hyperscriptI18n());
10
+ *
11
+ * @example
12
+ * // With options
13
+ * _hyperscript.use(hyperscriptI18n({
14
+ * defaultLanguage: 'ja',
15
+ * debug: true,
16
+ * }));
17
+ *
18
+ * @example
19
+ * // Standalone preprocessing (for _hyperscript.evaluate() calls)
20
+ * import { preprocess } from '@lokascript/hyperscript-adapter';
21
+ * const english = preprocess('トグル .active', 'ja');
22
+ * _hyperscript(english);
23
+ */
24
+
25
+ export { hyperscriptI18n, preprocess, type PluginOptions } from './plugin';
26
+ export { preprocessToEnglish, type PreprocessorConfig } from './preprocessor';
27
+ export { resolveLanguage } from './language-resolver';
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Language Resolver
3
+ *
4
+ * Determines the language of hyperscript code on a given element
5
+ * using a cascading resolution strategy.
6
+ */
7
+
8
+ /**
9
+ * Resolve the language for a hyperscript attribute on an element.
10
+ *
11
+ * Resolution order:
12
+ * 1. `data-lang` attribute on the element itself
13
+ * 2. `data-hyperscript-lang` attribute on the element or closest ancestor
14
+ * 3. `lang` attribute on `<html>` element
15
+ * 4. null (assume English, no preprocessing needed)
16
+ */
17
+ export function resolveLanguage(elt: Element): string | null {
18
+ // 1. Explicit per-element
19
+ const dataLang = elt.getAttribute('data-lang');
20
+ if (dataLang) return normalizeLangCode(dataLang);
21
+
22
+ // 2. Inherited from ancestor
23
+ const hsLang =
24
+ elt.getAttribute('data-hyperscript-lang') ??
25
+ elt.closest?.('[data-hyperscript-lang]')?.getAttribute('data-hyperscript-lang');
26
+ if (hsLang) return normalizeLangCode(hsLang);
27
+
28
+ // 3. Document-level lang
29
+ const htmlLang = typeof document !== 'undefined' ? document.documentElement?.lang : null;
30
+ if (htmlLang && htmlLang !== 'en') return normalizeLangCode(htmlLang);
31
+
32
+ return null;
33
+ }
34
+
35
+ /**
36
+ * Normalize a BCP-47 language tag to an ISO 639-1 code.
37
+ * e.g., "ja-JP" → "ja", "zh-Hans" → "zh", "pt-BR" → "pt"
38
+ */
39
+ function normalizeLangCode(lang: string): string {
40
+ return lang.split('-')[0].toLowerCase();
41
+ }
package/src/plugin.ts ADDED
@@ -0,0 +1,119 @@
1
+ /**
2
+ * _hyperscript Plugin
3
+ *
4
+ * Registers with _hyperscript.use() to intercept getScript() and
5
+ * preprocess non-English hyperscript into English before parsing.
6
+ */
7
+
8
+ import { resolveLanguage } from './language-resolver';
9
+ import { preprocessToEnglish, type PreprocessorConfig } from './preprocessor';
10
+
11
+ /**
12
+ * The _hyperscript global object shape (minimal subset we need).
13
+ */
14
+ interface HyperscriptGlobal {
15
+ internals: {
16
+ runtime: HyperscriptRuntime;
17
+ };
18
+ config: Record<string, unknown>;
19
+ }
20
+
21
+ interface HyperscriptRuntime {
22
+ getScript(elt: Element): string | null;
23
+ }
24
+
25
+ export interface PluginOptions extends Partial<PreprocessorConfig> {
26
+ /** Default language for all elements (overridable per-element). */
27
+ defaultLanguage?: string;
28
+ /** Custom attribute name for per-element language. Default: "data-lang" */
29
+ languageAttribute?: string;
30
+ /** Enable debug logging to console. Default: false */
31
+ debug?: boolean;
32
+ }
33
+
34
+ /**
35
+ * Create a _hyperscript plugin that enables multilingual hyperscript.
36
+ *
37
+ * @example
38
+ * // Basic usage
39
+ * _hyperscript.use(hyperscriptI18n());
40
+ *
41
+ * @example
42
+ * // With options
43
+ * _hyperscript.use(hyperscriptI18n({
44
+ * defaultLanguage: 'ja',
45
+ * confidenceThreshold: 0.6,
46
+ * debug: true,
47
+ * }));
48
+ */
49
+ export function hyperscriptI18n(options: PluginOptions = {}) {
50
+ return function plugin(hs: unknown): void {
51
+ const { internals } = hs as HyperscriptGlobal;
52
+ const runtime = internals.runtime;
53
+ const originalGetScript = runtime.getScript.bind(runtime);
54
+
55
+ runtime.getScript = function (elt: Element): string | null {
56
+ const src = originalGetScript(elt);
57
+ if (!src) return null;
58
+
59
+ // Resolve language
60
+ const lang = resolveLanguageWithOptions(elt, options);
61
+
62
+ // If English or no language detected, pass through
63
+ if (!lang || lang === 'en') return src;
64
+
65
+ // Preprocess to English
66
+ const english = preprocessToEnglish(src, lang, options);
67
+
68
+ if (english !== src) {
69
+ if (options.debug) {
70
+ console.log(`[hyperscript-i18n] ${lang}: "${src}" → "${english}"`);
71
+ }
72
+ } else {
73
+ // Translation produced no change — likely a failure
74
+ console.warn(
75
+ `[hyperscript-i18n] Translation unchanged for lang="${lang}": "${src.length > 60 ? src.slice(0, 60) + '…' : src}". ` +
76
+ 'The input may not match any known pattern, or the language may not be registered. ' +
77
+ 'Original text will be passed to _hyperscript as-is.'
78
+ );
79
+ }
80
+
81
+ return english;
82
+ };
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Resolve language with plugin options factored in.
88
+ */
89
+ function resolveLanguageWithOptions(elt: Element, options: PluginOptions): string | null {
90
+ // Check custom attribute name
91
+ if (options.languageAttribute) {
92
+ const custom = elt.getAttribute(options.languageAttribute);
93
+ if (custom) return custom.split('-')[0].toLowerCase();
94
+ }
95
+
96
+ // Standard resolution
97
+ const resolved = resolveLanguage(elt);
98
+ if (resolved) return resolved;
99
+
100
+ // Fall back to default language
101
+ return options.defaultLanguage ?? null;
102
+ }
103
+
104
+ /**
105
+ * Standalone preprocessing function for programmatic use.
106
+ * Call this when using _hyperscript.evaluate() or _hyperscript("code") directly.
107
+ *
108
+ * @example
109
+ * const english = preprocess("トグル .active", "ja");
110
+ * _hyperscript(english);
111
+ */
112
+ export function preprocess(
113
+ src: string,
114
+ lang: string,
115
+ config: Partial<PreprocessorConfig> = {}
116
+ ): string {
117
+ if (lang === 'en') return src;
118
+ return preprocessToEnglish(src, lang, config);
119
+ }