@hyperscript-tools/i18n 2.7.2 → 2.9.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 +393 -0
- package/README.md +58 -6
- package/dist/chunk-7ZOFDQYR.js +57 -0
- package/dist/chunk-SYQOFAWY.js +89 -0
- package/dist/cli.js +64 -4
- package/dist/eleventy.d.ts +17 -1
- package/dist/eleventy.js +45 -4
- package/dist/index.d.ts +33 -1
- package/dist/index.js +14 -2
- package/dist/validate.d.ts +44 -0
- package/dist/validate.js +14 -0
- package/package.json +12 -6
- package/dist/chunk-XIEA5H7Z.js +0 -37
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### ⚠ BREAKING (types)
|
|
11
|
+
|
|
12
|
+
- **Domain renderers return `string | null`.** `renderSQL`, `renderJSX`, `renderTodo`,
|
|
13
|
+
`renderLLM`, `renderFlow`, `renderBDD`, `renderVoice` and `renderBehaviorSpec`
|
|
14
|
+
previously signalled "I don't know this action" with a successful-looking
|
|
15
|
+
sentinel string — `` `-- Unknown: ${node.action}` `` (or `// Unknown:` in three
|
|
16
|
+
domains). Consumers were forced into string matching, and any legitimately
|
|
17
|
+
rendered sentence starting with `--` was silently dropped. They now return
|
|
18
|
+
`null`.
|
|
19
|
+
|
|
20
|
+
**Rendered output for every previously-known action is byte-identical** — all
|
|
21
|
+
1,939 existing domain tests pass unchanged.
|
|
22
|
+
|
|
23
|
+
_Migration:_ replace sentinel sniffing with a null check. A guard of the shape
|
|
24
|
+
`if (!sentence || sentence.startsWith('--')) return null;` already handles it.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **Domains are open for extension** (`@lokascript/framework` + all 8 domain packages).
|
|
29
|
+
A consumer can add a command to a domain **without editing that package**, by
|
|
30
|
+
passing a `DomainExtension` to `createXDSL({ extensions })`. A schema plus one
|
|
31
|
+
vocabulary entry per language is enough to parse, render and compile it in all
|
|
32
|
+
of them — word order, marker placement and keyword position derive from the
|
|
33
|
+
schema and the language profiles. Vocabulary may cover a subset of the DSL's
|
|
34
|
+
languages, so it can be filled in over time. See
|
|
35
|
+
[Extending an Existing Domain](packages/framework/docs/DOMAIN_AUTHOR_GUIDE.md#extending-an-existing-domain).
|
|
36
|
+
- **`createDomainRenderer`** (`@lokascript/framework`) composes a domain's
|
|
37
|
+
hand-written per-action renderers with a schema-driven fallback, returning
|
|
38
|
+
`null` for actions it has neither for. It is what each domain's `renderX` now
|
|
39
|
+
runs, and what makes an extension command render without the domain knowing it
|
|
40
|
+
exists.
|
|
41
|
+
- **`MultilingualDSL.render(node, language)`** — render a parsed node back to
|
|
42
|
+
natural language. Chains extension renderer → domain renderer → schema
|
|
43
|
+
fallback → `null`. `parse()` + `render()` is the language-to-language path that
|
|
44
|
+
does not need a `grammarProfile`.
|
|
45
|
+
- **`getAllTranslationsWithStatus()`** (`@lokascript/semantic`) — the same result
|
|
46
|
+
as `getAllTranslations()`, plus a `failed` map naming each language that could
|
|
47
|
+
not render and why.
|
|
48
|
+
- **`createMultilingualDSL` is now a named export** of `@lokascript/framework`
|
|
49
|
+
rather than reaching consumers only through a wildcard re-export. Domains also
|
|
50
|
+
export `allProfiles`, which extension authors need.
|
|
51
|
+
- **A CHANGELOG now ships in every published package.** Determining what changed
|
|
52
|
+
between two releases previously meant probing the installed code.
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- **Directional markers for `add` / `put` / `go` in es, ar, zh, fr, de, pt**
|
|
57
|
+
(`@lokascript/semantic`). Every language profile's `destination` marker is
|
|
58
|
+
locative (`on` / `على` / `在` / `sur` / `auf`) because it also serves
|
|
59
|
+
`toggle`/`show`; only English overrode it for the directional commands, so the
|
|
60
|
+
rest rendered "add .active ON #box". Now: add → es `a`, ar `إلى`, zh `到`,
|
|
61
|
+
fr `à`, de `zu`, pt `a`; put → ar `في`, zh `到`, fr `dans`, de `in`; go → es `a`,
|
|
62
|
+
ar `إلى`, fr `à`, de `zu`, pt `para`. ja `に` / ko `에` / tr `e` were already
|
|
63
|
+
directional and are unchanged.
|
|
64
|
+
|
|
65
|
+
**Parsing accepts a strict superset**: a new `RoleSpec.markerLegacy` keeps every
|
|
66
|
+
previously-rendered marker parsing, so source written against ≤2.8 keeps working.
|
|
67
|
+
Downstream consumers maintaining correction tables for these markers (as
|
|
68
|
+
`lokascript-learn` does) can now delete those entries.
|
|
69
|
+
|
|
70
|
+
- **`createSchemaRenderer` honors `svoPosition` / `sovPosition`.** It previously
|
|
71
|
+
iterated roles in declaration order and never read the positions, so a schema
|
|
72
|
+
whose declared order differed from its declaration order rendered a surface its
|
|
73
|
+
own generated pattern could not re-parse. It now sorts with the same comparator
|
|
74
|
+
pattern generation uses (**descending — higher values render earlier**). Affects
|
|
75
|
+
only custom schemas; no in-repo consumer relied on the old behavior.
|
|
76
|
+
- **Absent roles no longer leave a dangling marker.** `createSchemaRenderer` emitted
|
|
77
|
+
a role's marker even with no value, producing `analyze #content as` /
|
|
78
|
+
`#content として 分析`.
|
|
79
|
+
- **`getAllTranslations()` covers every registered language** (24) instead of a
|
|
80
|
+
frozen 13-language list, and a language that fails to render is skipped rather
|
|
81
|
+
than throwing. The full browser bundle now registers Hebrew, which was
|
|
82
|
+
registered everywhere except there.
|
|
83
|
+
- **Schema-validation diagnostics are opt-in.** `import '@lokascript/domain-llm'`
|
|
84
|
+
printed ~44 lines of `[SCHEMA VALIDATION]` stderr into every downstream test run
|
|
85
|
+
and CI log. Set `LOKASCRIPT_SCHEMA_VALIDATION=1` to see them; a standing test in
|
|
86
|
+
`@lokascript/semantic` keeps them honest.
|
|
87
|
+
- **`dsl.translate()` names the missing config field** when a language has no
|
|
88
|
+
`grammarProfile`, instead of failing deep in the transformer with
|
|
89
|
+
"No profile found for language: en". Anyone matching that exact message must update.
|
|
90
|
+
- **`defineCommand` / `getRoleSpec` validate their input.** A malformed schema now
|
|
91
|
+
fails with a message naming the field, instead of
|
|
92
|
+
`TypeError: undefined is not an object (evaluating 'schema.roles[0]')` from
|
|
93
|
+
inside the package.
|
|
94
|
+
|
|
95
|
+
### Fixed
|
|
96
|
+
|
|
97
|
+
- **A corrected marker stopped parsing in nested contexts.** Three separate
|
|
98
|
+
override branches — the SOV and VSO event-handler generators and the shared
|
|
99
|
+
marker resolver — each dropped marker alternatives, so Arabic
|
|
100
|
+
`ضع هو إلى #chat` parsed standalone but not inside a `socket` block. They now
|
|
101
|
+
share one definition of what an override still accepts.
|
|
102
|
+
|
|
103
|
+
## [2.8.0] - 2026-07-25
|
|
104
|
+
|
|
105
|
+
> **Gap note:** 2.6.0 through 2.7.2 shipped without entries in this file; their
|
|
106
|
+
> details live in [GitHub Releases](https://github.com/codetalcott/hyperfixi/releases).
|
|
107
|
+
> Full notes for this release: `docs-internal/RELEASE_NOTES_v2.8.0-draft.md` (swapped
|
|
108
|
+
> into the GitHub release on publish day).
|
|
109
|
+
|
|
110
|
+
A size and correctness release: the full bundles shed a duplicated core+semantic copy (~534 → ~299 KB gz), the semantic parser closes out several multilingual correctness arcs, and the publish pipeline's gates are now honest end-to-end.
|
|
111
|
+
|
|
112
|
+
### Highlights
|
|
113
|
+
|
|
114
|
+
- **Full bundles nearly halved** (`@hyperfixi/core`): since 2.7.0 the full bundles shipped two copies of the core runtime and multilingual parser (the bundled reactivity/realtime plugins resolved `@hyperfixi/core` to its prebuilt dist alongside the bundle's own source graph). A rollup alias folds everything onto one graph: `hyperfixi.js` ~534 → **~299 KB gz**, `hyperfixi-hx-v4.js` ~540 → **~311 KB gz**. Same features and pre-installed plugins; CI size ceilings ratcheted down to catch re-duplication.
|
|
115
|
+
- **`fetch … with { … }` options in all 24 languages** (#662): request options (braced bodies, `method:` / `headers:` / `body:` named args) are captured by the semantic parser in every supported language — previously most non-English languages silently dropped the clause and issued a bare GET.
|
|
116
|
+
- **Event modifiers in all 24 languages** (#673): `once`, `debounce(N)`, `throttle(N)` and their translated forms flow from every language's event-handler head to the runtime.
|
|
117
|
+
- **Foreign→English canonical validity: 3059/3059** (#724–#732, pick text-range #733/#734/#736): every authored foreign translation now renders English that the canonical hyperscript.org parser accepts — both canonical-validity allowlists are empty — and a new R4 canonical-validity ratchet (#727) plus a ninth `--regression` signal keep it that way.
|
|
118
|
+
- **New package: `@lokascript/htmx-adapter`** (#735): multilingual adapter for upstream htmx v4 (canonicalizing extension).
|
|
119
|
+
|
|
120
|
+
### Fixed
|
|
121
|
+
|
|
122
|
+
- **`go to url "/page"` no longer drops the URL** (#680) — the destination was silently lost in every language (the English reference itself was affected, which masked it).
|
|
123
|
+
- **Broken event listeners in six languages** (#681) — de/fr/id/it/pl/zh dictionaries rendered `mousedown`/`mouseup` as words the parser could not resolve; a V3c vocabulary check now verifies every dictionary event word round-trips on the parse side.
|
|
124
|
+
- **"Unknown command: compound" on semantic-path bundles** (#675) — multi-command handler bodies could throw at runtime on the full bundles; the per-segment semantic adapter now defers non-command parses to the traditional parser.
|
|
125
|
+
|
|
126
|
+
### Infrastructure
|
|
127
|
+
|
|
128
|
+
- Publish pipeline hardening (#672, #674): reproducible `npm ci` installs, `pre-publish-check` with real exit codes end-to-end, export validation after bundle builds, complete BUILD_ORDER, and a final verdict step that always runs.
|
|
129
|
+
- Vocab consistency gate and total input-coverage instrumentation in CI; the multilingual fidelity ratchet holds the 2026-07-11 high-water marks (fidelity 1.000 on all 3,696 corpus rows across 24 languages).
|
|
130
|
+
|
|
131
|
+
## [2.5.1] - 2026-05-24
|
|
132
|
+
|
|
133
|
+
A single-bug patch for a v2.5.0 publishing regression that broke every localized htmx attribute.
|
|
134
|
+
|
|
135
|
+
### Fixed
|
|
136
|
+
|
|
137
|
+
- **`hyperfixi-hx.js` / `hyperfixi-hx-v4.js`: `window.__hyperfixi_i18n` lost to terser** (`@hyperfixi/core`). The Phase 8 orchestrator's public-API singleton (`window.__hyperfixi_i18n = { register }`) was being eliminated by terser in the published v2.5.0 hybrid-hx and hybrid-hx-v4 bundles, silently breaking every `vocab/htmx/{lang}.js` module on load with `"loaded before the htmx-compat orchestrator"`. Two layers fixed: (1) the module-level `installPublicAPI()` invocation is now exported and called explicitly from each bundle entry that includes htmx-compat, so terser's `unused: true, toplevel: true` pass can't drop it; (2) the API is exposed via bracket-access (`window['__hyperfixi_i18n']`) to bypass terser's `properties.regex: /^_/` mangling. Localized htmx attribute names (`hx-obtener`, `hx-取得`, `hx-احصل`, …) now resolve correctly in the published bundles, matching their already-working behavior under the source-aliased vitest suite.
|
|
138
|
+
|
|
139
|
+
### Tests
|
|
140
|
+
|
|
141
|
+
- **Orchestrator public-API gate** in pre-publish-check + release-smoke. New 2-test Playwright spec (`packages/core/src/compatibility/browser-tests/i18n-orchestrator-api.spec.ts`, ~600 ms) asserts `typeof window.__hyperfixi_i18n.register === 'function'` in both shipped htmx bundles. Wired into `.github/workflows/pre-publish-check.yml` (fails the workflow on regression) and the `--matrix` stage of `examples/release-smoke/run.mjs` (so the gate fires against the registry-installed tarball, not only the locally-built dist). Surgical — no swap-pipeline dependency. The broader `i18n-htmx.spec.ts` remains un-wired pending two pre-existing bugs unrelated to this release: a reactivity / notify-hook bug breaking localized `hx-live` re-renders, and a swap-pipeline bug stringifying `DocumentFragment`s in `fetch ... as html` → `put it into target`.
|
|
142
|
+
|
|
143
|
+
## [2.5.0] - 2026-05-22
|
|
144
|
+
|
|
145
|
+
A focused release: a new LLM-domain introspection API, plus correctness fixes for the parser, the multilingual renderers, and how published packages declare their internal dependencies.
|
|
146
|
+
|
|
147
|
+
### Added
|
|
148
|
+
|
|
149
|
+
- **`describeCommands()` / `describeCommand()`** in `@lokascript/domain-llm` — a JSON-serializable description of every LLM command: its roles, per-language marker keywords, and a verified runnable example in each of the 8 supported languages. Adds the `LLM_LANGUAGE_CODES` export. One source of truth for docs, MCP tool schemas, and LLM-agent discovery. `@lokascript/domain-llm` also gains a package README.
|
|
150
|
+
|
|
151
|
+
### Fixed
|
|
152
|
+
|
|
153
|
+
- **`toggle @attribute`** (`@hyperfixi/core`): `toggle @disabled`, `toggle @required`, and similar threw `toggle command: no valid class names found` in the browser. `toggle` now skips semantic parsing (like its sibling DOM commands `add` / `remove`), so the `@attr` / `.class` / `*property` argument forms all work.
|
|
154
|
+
- **SOV word order in the multilingual renderers** (`@lokascript/domain-bdd`, `@lokascript/domain-llm`): Japanese / Korean / Turkish output from `renderBDD` and `renderLLM` was not parseable — particle placement and word order were wrong, so `translate_bdd` / `translate_llm` produced broken SOV text. Fixed, with render→compile round-trip guardrail tests.
|
|
155
|
+
- **Internal dependency versions**: published packages declared their internal workspace dependencies as `"*"`, which shipped literally to npm and could let a consumer resolve a mismatched (older) internal package. Internal deps now carry exact caret ranges.
|
|
156
|
+
|
|
157
|
+
## [2.4.0] - 2026-05-20
|
|
158
|
+
|
|
159
|
+
Two parallel arcs landed since v2.3.1: **upstream `_hyperscript` 0.9.90 parity** (9 phases — commands, comparators, collection ops, event modifiers, plugin system, 3 new public plugin packages, i18n) and **htmx v4 compatibility** (reactive `hx-live`, SSE/WebSocket streaming, per-element localized attribute names, the size-busting `hyperfixi-hx-v4.js` bundle).
|
|
160
|
+
|
|
161
|
+
### Added
|
|
162
|
+
|
|
163
|
+
#### Core language (upstream \_hyperscript 0.9.90 compat)
|
|
164
|
+
|
|
165
|
+
- **9 new commands**: `focus`, `blur`, `empty`, `open`, `close`, `select`, `clear`, `reset`, `breakpoint` — covers DOM focus management, form state, window control, and debugger integration.
|
|
166
|
+
- **4 comparator expressions**: `X starts with Y`, `X ends with Y`, `X is between A and B`, and the postfix `... ignoring case` modifier for case-insensitive string comparison. `between` is inclusive and auto-orders bounds.
|
|
167
|
+
- **5 collection infix operators**: `collection where <pred>`, `collection sorted by <key>`, `collection mapped to <expr>`, `string split by <sep>`, `array joined by <sep>`. `where`/`sorted by`/`mapped to` bind `it` per element for inline predicates/keys.
|
|
168
|
+
- **Event modifiers**: `on first <event>` alias for `.once`, plus synthetic `on resize` for HTMLElements backed by `ResizeObserver` (browser standard `resize` is window-only).
|
|
169
|
+
|
|
170
|
+
#### Plugin system
|
|
171
|
+
|
|
172
|
+
- **`HyperfixiPlugin` public API**: `installPlugin(plugin)` on Runtime, `ParserExtensionRegistry` singleton with snapshot/restore for test isolation. Five plugin seams: `registerFeature`, `registerNodeEvaluator`, `registerGlobalWriteHook`, `registerGlobalReadHook`, and `HyperfixiPluginContext.runtime`. `$name` globals canonicalized to bare-key storage across `setVariableValue` and both identifier evaluators.
|
|
173
|
+
|
|
174
|
+
#### New plugin packages
|
|
175
|
+
|
|
176
|
+
- **`@hyperfixi/speech`**: Voice I/O via Web Speech API — `speak "text"` (with optional `rate`/`pitch`/`voice`/`volume`), `ask "question"`, `answer with "value"`. Idempotent installation; no-ops cleanly when the Web Speech API or `window.prompt()` is unavailable.
|
|
177
|
+
- **`@hyperfixi/reactivity`**: Reactive signals — `live { ... }` blocks, `bind` expressions (now also accepts explicit property via `'s` or `.` syntax), `when X changes`, `^name` reactive reads. Microtask-flushed scheduler with cycle guard and auto-stop on element disconnect.
|
|
178
|
+
- **`@hyperfixi/components`**: Custom Element registration from `<template component="tag">` / `<script type="text/hyperscript-template" component="tag">`. **v2 shipped this release** — reactive `^var` rendering (via `@hyperfixi/reactivity`), `#if` / `#for` directives, slot substitution, `${expr}` interpolation. Init scripts (`_=` on the `<template>`) run on each stamp.
|
|
179
|
+
|
|
180
|
+
#### Internationalization (i18n)
|
|
181
|
+
|
|
182
|
+
- Phase 1 commands translated across all 23 COMPLETE_LANGUAGES (7 new schemas in `@lokascript/semantic`: `empty`, `open`, `close`, `select`, `clear`, `reset`, `breakpoint`).
|
|
183
|
+
- Phase 2 comparators (`starts with`, `ends with`, `between`, `ignoring case`) translated across all 23 languages (with `starts with`/`ends with`/`between` backfills in bn, th, vi).
|
|
184
|
+
- Phase 3 collection ops (`sorted by`, `mapped to`, `split by`, `joined by`) translated across all 23 languages (`where` predated this release).
|
|
185
|
+
- New `packages/i18n/src/schema-alignment.test.ts` — guards drift: every `CommandSchema` must have an `en.ts` entry, Phase 1/2/3 operators must exist across all complete dictionaries.
|
|
186
|
+
|
|
187
|
+
#### htmx v4 integration
|
|
188
|
+
|
|
189
|
+
- **`hx-live` reactive expressions**: When `@hyperfixi/reactivity` is installed, the htmx-compat layer recognizes htmx v4's `hx-live` attribute and translates it to a `live ... end` block. The expression body is hyperscript (not JavaScript like upstream htmx v4), so it gets fine-grained dependency tracking + inherits multilingual support. If reactivity isn't installed, the element is skipped with a clear console error.
|
|
190
|
+
- **`sse-connect` / `sse-swap`**: Long-lived `EventSource` per element. Bounded exponential reconnect backoff (1s → 2s → 4s … capped at 30s, 5 retries). Auto-close on DOM removal via `MutationObserver`. Lifecycle events: `htmx:sseOpen`, `htmx:sseMessage`, `htmx:sseError`, `htmx:sseClose`. Multiple named events per connection. Swap targets resolve `closest`/`find`/`next`/`previous`.
|
|
191
|
+
- **`ws-connect` / `ws-send`**: Bidirectional WebSocket per element. JSON envelope routing (`{ target, swap?, data }`) drives surgical updates through the existing `hx-target`/`hx-swap` machinery; raw messages dispatch as `htmx:wsMessage`. Outbound sends queue while connecting, flush on `htmx:wsOpen`. Same reconnect backoff as SSE.
|
|
192
|
+
- **Localized htmx attribute names (Phases 8–10)**: Per-element `lang=` resolution. Authors can write `hx-obtener` / `hx-objetivo` / `sse-conectar` (Spanish), `hx-取得` / `hx-ターゲット` (Japanese), `hx-احصل` / `hx-هدف` (Arabic), etc. — the orchestrator translates them to canonical English before they hit the existing processor. 8 priority vocab modules ship with the bundle (en, es, fr, ja, zh, ar, ko, de). Missing-vocab languages log a one-time warning per language and fall back to English. `eventNameOf` / `selectorFor` / `nameOf` hooks are namespaced (`hx`, `sse`, `ws`) so `hx-on:*` localizes correctly.
|
|
193
|
+
- **htmx event lifecycle**: New CustomEvents `htmx:configuring` (cancelable, mutate `e.detail.config`), `htmx:beforeRequest` (cancelable), `htmx:afterSettle`, `htmx:error`, plus the SSE/WS lifecycle events listed above.
|
|
194
|
+
|
|
195
|
+
#### Bundles & build tooling
|
|
196
|
+
|
|
197
|
+
- **`hyperfixi-hx-v4.js`** (~257 KB gz): Full runtime + `@hyperfixi/reactivity` (auto-installed) + htmx-compat + SSE/WS support, all in a single script tag. The "batteries-included" choice for projects using htmx v4 reactive/streaming features. The slim `hyperfixi-hx.js` (~13 KB gz) does NOT ship reactivity/SSE/WS — choose hx-v4 when you reach for `hx-live` / `sse-connect` / `ws-connect`, hx otherwise.
|
|
198
|
+
- **Vite plugin: htmx v4 surface detection** — scans HTML for `hx-live` / `sse-*` / `ws-*` attributes and auto-routes to `hyperfixi-hx-v4.js`; otherwise emits a minimal handcrafted bundle as before.
|
|
199
|
+
- **`getDefaultRuntime()`** exposed for forcing lazy runtime construction (useful for bundles that defer wiring until first DOM event).
|
|
200
|
+
|
|
201
|
+
### Fixed
|
|
202
|
+
|
|
203
|
+
- **Runtime**: `it`/`its`/`result` identifier cache no longer returns stale values across loop iterations — required for Phase 3 collection operators that rebind `it` per element (16 ms TTL cache had masked all Phase 3 tests before fix).
|
|
204
|
+
- **Runtime**: method-call `this` binding preserved on member callees — `items mapped to it.toUpperCase()` no longer throws "called on null or undefined". Uses `.apply(thisArg, args)` for member expressions.
|
|
205
|
+
- **Parser**: `is not` binary operator now routes to `notEquals.evaluate` — pre-existing gap where tokens parsed but runtime dispatch threw `Unknown binary operator: is not`.
|
|
206
|
+
- **Runtime**: `execute()` now mutates the caller's `ExecutionContext` in place when injecting the `ExpressionRegistry`, rather than shallow-cloning. Commands writing to `context.result` / `context.it` (e.g. the new `answer` command) now propagate those writes back to the caller — matching the long-standing `context.locals.set(...)` / `context.globals.set(...)` mutation pattern in the same function. `ExecutionContext.registry` is now mutable in the type signature (the runtime is the only legitimate writer; the `readonly` modifier was a defensive holdover from an earlier consolidation arc).
|
|
207
|
+
- **Runtime**: `or` / `and` short-circuit now returns the operand value (truthy/falsy), not a coerced boolean — matches upstream `_hyperscript` semantics.
|
|
208
|
+
- **Runtime**: `put` / `set` / `toggle` accept plain property names as write targets (not just `the X.Y` chains).
|
|
209
|
+
- **Runtime**: `evalHyperScript()` defaults to shared globals when none provided.
|
|
210
|
+
- **Runtime**: scoped `notifyLocalRead()` now fires when reading scoped locals (was missing — broke reactive `bind` to local vars).
|
|
211
|
+
- **Math**: binary-arithmetic operands accept DOM elements (coerced through the consolidated "is this convertible" path).
|
|
212
|
+
- **Parser**: `set the X.Y to Z` dotted property chains (e.g. `set the event.detail.result to ...`) now use the full expression parser rather than a fixed 2-token lookahead — handles arbitrary-depth chains and `of` expressions uniformly.
|
|
213
|
+
- **Parser**: `pick first 3 of arr` and other keyword-led command syntaxes (commands whose argument grammar starts with a keyword) now route through dedicated parsers and preserve trailing args. Added to `COMPOUND_COMMANDS` with a dedicated `parseXxxCommand` per case.
|
|
214
|
+
- **Browser bundle**: `ExpressionRegistry` threaded through the command-execution path (`processCommand` → `adapter.execute` → `parseInput`). Without this, commands like `tell`/`send`/`toggle` whose `parseInput()` evaluates AST nodes failed in the example gallery with "Expression X not in ExecutionContext.registry".
|
|
215
|
+
- **htmx-compat**: `hx-on:*` registers real event listeners directly from the processor (the translator never sees them, so it can stay declarative-only). Refresh observer wired correctly; localized `hx-on:*` prefix resolved via the namespaced i18n hooks.
|
|
216
|
+
- **i18n**: Reactive blocks (`live`, `when X changes`) route around `parseStatement` to fix `when`/`unless` + SOV-language `live` block parsing. Suppressed spurious "then" injection inside live blocks.
|
|
217
|
+
- **patterns-reference**: HTML patterns marked non-translatable (they're DOM markup, not source code); cleaned orphan languages from the database seed.
|
|
218
|
+
- **Semantic**: registry singleton shared across tsup entries (multi-entry split was forking the registry, breaking cross-entry pattern lookups).
|
|
219
|
+
|
|
220
|
+
### Changed (internal)
|
|
221
|
+
|
|
222
|
+
- **Evaluator consolidation**: Retired the `BaseExpressionEvaluator` class hierarchy and 4 helper modules (~2,700 LOC removed) in favor of the single canonical evaluator at `parser/runtime.ts:evaluateAST` with the `ExpressionRegistry` on `ExecutionContext`. Both consolidation arcs (α + β) closed.
|
|
223
|
+
- **Domain DSLs**: Extracted a shared config package (`@hyperfixi/domain-config`) consumed by all 8 domain DSLs.
|
|
224
|
+
- **Published infrastructure packages**: `@lokascript/intent`, `@hyperfixi/domain-config`, `@hyperfixi/planner`, and `@hyperfixi/intent-element` are published to npm as transitive dependencies (required for `@hyperfixi/core` and the domain DSLs to install cleanly from the registry). They are infra deps — users do not install them directly. (These were published from post-tag fix-forward commits; the `v2.4.0` git tag predates them.)
|
|
225
|
+
|
|
226
|
+
## [2.3.1] - 2026-04-23
|
|
227
|
+
|
|
228
|
+
### Fixed
|
|
229
|
+
|
|
230
|
+
- **CI**: Added `planner` package to the build step (before `mcp-server`, which depends on it) across `ci.yml`, `publish.yml`, and `pre-publish-check.yml`; added `planner/dist` to the build-artifacts upload.
|
|
231
|
+
- **hybrid-complete bundle**: `toggle @attribute` and positional selectors (`first .x`, `last .y` in selector position) now work — closed a parser gap surfaced by the README "try it live" examples.
|
|
232
|
+
|
|
233
|
+
### Added
|
|
234
|
+
|
|
235
|
+
- **README**: "Try it live" link to the gallery, broken-up counter example, tier links.
|
|
236
|
+
|
|
237
|
+
### Changed
|
|
238
|
+
|
|
239
|
+
- **`planner` package**: Marked private (workspace-only — published packages should not list private deps).
|
|
240
|
+
|
|
241
|
+
## [2.3.0] - 2026-03-17
|
|
242
|
+
|
|
243
|
+
### Added
|
|
244
|
+
|
|
245
|
+
- **Lazy behavior resolver**: `install X` just works — behaviors resolve on demand without manual registration
|
|
246
|
+
- **Hyperscript-native behaviors**: Behaviors defined as hyperscript source strings via patterns-reference schemas
|
|
247
|
+
- **Dynamic class selectors**: `.{varName}` syntax in `toggle`, `add`, and `remove` resolves variables as CSS class names
|
|
248
|
+
- **Variable/expression durations**: `wait` command now accepts variable and expression durations (e.g., `wait feedbackDuration ms`)
|
|
249
|
+
- **Behavior schema sources**: Single source of truth for behavior schemas in patterns-reference
|
|
250
|
+
|
|
251
|
+
### Fixed
|
|
252
|
+
|
|
253
|
+
- **Parser**: `js()` single-quote parsing, `set the X.Y` dotted property chains (e.g., `event.detail.result`), improved `set` error messages
|
|
254
|
+
- **Parser**: Namespaced events (`custom:activate`) and `trigger`/`on` inside `repeat` blocks in behaviors
|
|
255
|
+
- **Parser**: Adjacent dot after keywords treated as property access, not CSS selector
|
|
256
|
+
- **Behaviors**: Clipboard `wait feedbackDuration`, AutoDismiss wait timing, Removable confirm dialog
|
|
257
|
+
- **Browser**: Resolver bundle timing and Playwright test stability
|
|
258
|
+
- **CI**: Behaviors package added to build pipeline, artifact upload, and pre-publish-check build order
|
|
259
|
+
|
|
260
|
+
## [2.2.1] - 2026-03-16
|
|
261
|
+
|
|
262
|
+
### Fixed
|
|
263
|
+
|
|
264
|
+
- Parser: adjacent dot after keywords treated as property access, not CSS selector
|
|
265
|
+
- Dependency updates (build-tools group)
|
|
266
|
+
|
|
267
|
+
## [2.2.0] - 2026-03-15
|
|
268
|
+
|
|
269
|
+
### Fixed
|
|
270
|
+
|
|
271
|
+
- Dependency security: resolved 27 of 33 Dependabot vulnerabilities
|
|
272
|
+
- CI: copy examples gallery into repo, remove external clone dependency
|
|
273
|
+
- CI: suppress already-published errors in publish dry run
|
|
274
|
+
|
|
275
|
+
## [2.1.0] - 2026-02-20
|
|
276
|
+
|
|
277
|
+
### Added
|
|
278
|
+
|
|
279
|
+
- **domain-llm**: Expanded from 4 to 8 languages (added Korean, Chinese, Turkish, French) — 68 tests
|
|
280
|
+
- **domain-flow**: Expanded from 4 to 8 languages (added Korean, Chinese, Turkish, French) — 108 tests
|
|
281
|
+
- **domain-flow**: First npm publication as `@lokascript/domain-flow`
|
|
282
|
+
|
|
283
|
+
### Changed
|
|
284
|
+
|
|
285
|
+
- All 7 domain DSLs now consistently support 8 languages (en, es, ja, ar, ko, zh, tr, fr)
|
|
286
|
+
- MCP server registry updated for new domain languages
|
|
287
|
+
|
|
288
|
+
### Fixed
|
|
289
|
+
|
|
290
|
+
- Resolved test failures in 4 packages (19 tests)
|
|
291
|
+
- Scoped `first .X in me` to context element instead of document
|
|
292
|
+
- Property target bugs: disabled on button, tabIndex as number
|
|
293
|
+
|
|
294
|
+
## [2.0.0] - 2026-02-15
|
|
295
|
+
|
|
296
|
+
### Changed
|
|
297
|
+
|
|
298
|
+
- **Rebrand**: Renamed from LokaScript to HyperFixi for engine packages (`@lokascript/core` → `@hyperfixi/core`)
|
|
299
|
+
- Multilingual packages remain under `@lokascript/*` scope
|
|
300
|
+
- Synchronized all package versions to 2.0.0
|
|
301
|
+
|
|
302
|
+
## [1.4.0] - 2026-02-10
|
|
303
|
+
|
|
304
|
+
### Added
|
|
305
|
+
|
|
306
|
+
- **Language Server & VSCode Extension**: Full LSP with Go to Definition, Find References, multilingual hover, syntax highlighting, and HTML region extraction
|
|
307
|
+
- **AOT Compiler** (internal): Ahead-of-time compiler with 45 command codegens, expression transforms, 4 optimization passes, and 533 tests
|
|
308
|
+
- **Compilation Service** (internal): HTTP service for multilingual compilation with React renderer, test generation, and semantic diffing
|
|
309
|
+
- **Hyperscript Adapter** (@lokascript/hyperscript-adapter): Multilingual preprocessor plugin for original \_hyperscript with 24 per-language bundles
|
|
310
|
+
- **Semantic**: Russian/Ukrainian normalizers, improved SOV pass rates (JA 99%, KO 96%, TR 96%)
|
|
311
|
+
- **Vite Plugin**: htmx/fixi attribute scanning for zero-config support, hybrid-plus bundle commands
|
|
312
|
+
|
|
313
|
+
### Changed
|
|
314
|
+
|
|
315
|
+
- **Monorepo cleanup**: Moved experimental packages (analytics, server-integration, multi-tenant, ssr-support, siren) to `experiments/`
|
|
316
|
+
- **Code audits**: Comprehensive audits across core (+58 tests), runtime (+34 tests), behaviors (+66 tests), expressions (9 security fixes), validation (+20 tests), features (+19 tests), i18n (+33 tests)
|
|
317
|
+
- **Semantic refactoring**: Modularized tokenizer, split pattern generator by word order, eliminated all `as any` casts
|
|
318
|
+
- **CI**: Consolidated workflows, switched to OIDC trusted publishing, upgraded to Node 24 LTS
|
|
319
|
+
- **Test count**: 4046 → 8100+ tests
|
|
320
|
+
|
|
321
|
+
### Fixed
|
|
322
|
+
|
|
323
|
+
- Runtime correctness: GC fix, timeout handling, expression security hardening
|
|
324
|
+
- Browser test timeouts and false CI failures
|
|
325
|
+
- TypeScript errors across 6 packages for clean workspace typecheck
|
|
326
|
+
- Debug console.log leak in set command
|
|
327
|
+
|
|
328
|
+
## [1.3.0] - 2026-01-23
|
|
329
|
+
|
|
330
|
+
_Synchronized version release. See git history for details._
|
|
331
|
+
|
|
332
|
+
## [1.2.0] - 2026-01-21
|
|
333
|
+
|
|
334
|
+
_Synchronized version release. See git history for details._
|
|
335
|
+
|
|
336
|
+
## [1.0.0] - 2026-01-19
|
|
337
|
+
|
|
338
|
+
### Added
|
|
339
|
+
|
|
340
|
+
- **Initial Release**: First public release of LokaScript
|
|
341
|
+
- **Core Package** (@lokascript/core): Full hyperscript runtime with 43 commands
|
|
342
|
+
- **Semantic Package** (@lokascript/semantic): Multilingual parsing for 23 languages
|
|
343
|
+
- **I18n Package** (@lokascript/i18n): Grammar transformation for SOV/VSO/SVO word orders
|
|
344
|
+
- **Vite Plugin** (@lokascript/vite-plugin): Zero-config Vite integration
|
|
345
|
+
- **MCP Server** (@lokascript/mcp-server): Model Context Protocol server for LLM integration
|
|
346
|
+
- **Browser Bundles**: 7 size-optimized bundles (lite, lite-plus, hybrid-complete, hybrid-hx, minimal, standard, full)
|
|
347
|
+
- **23 Language Support**: English, Spanish, Japanese, Korean, Arabic, Chinese, French, German, Portuguese, Indonesian, Turkish, Swahili, Quechua, and more
|
|
348
|
+
- **4046 Tests**: Comprehensive test suite with >95% coverage
|
|
349
|
+
- **Etymology**: "LokaScript" from Sanskrit "loka" (world/realm/universe) reflecting multilingual scope
|
|
350
|
+
|
|
351
|
+
### Changed
|
|
352
|
+
|
|
353
|
+
- **Rebrand**: Project renamed from HyperFixi to LokaScript
|
|
354
|
+
- **NPM Organization**: Published under @lokascript/\* scope
|
|
355
|
+
- **Browser API**: Primary global changed to window.lokascript (with window.hyperfixi backward compatibility)
|
|
356
|
+
- **Lifecycle Events**: Renamed to lokascript:_ prefix (dual dispatch with hyperfixi:_ for compatibility)
|
|
357
|
+
|
|
358
|
+
### Fixed
|
|
359
|
+
|
|
360
|
+
- Workspace dependency resolution using wildcard versions (\*)
|
|
361
|
+
- TypeScript compilation across all 20+ packages
|
|
362
|
+
- Build system for browser bundles
|
|
363
|
+
|
|
364
|
+
### Backward Compatibility
|
|
365
|
+
|
|
366
|
+
- window.hyperfixi available as deprecated alias to window.lokascript
|
|
367
|
+
- hyperfixi:_ events still dispatched alongside lokascript:_ events
|
|
368
|
+
- File names kept for compatibility (e.g., hyperfixi-browser.js)
|
|
369
|
+
|
|
370
|
+
### Documentation
|
|
371
|
+
|
|
372
|
+
- Complete rebrand of all README files
|
|
373
|
+
- Updated CLAUDE.md with project context
|
|
374
|
+
- NPM organization setup guide
|
|
375
|
+
- Version management documentation
|
|
376
|
+
|
|
377
|
+
### Security
|
|
378
|
+
|
|
379
|
+
- npm access token stored in GitHub Secrets
|
|
380
|
+
- 2FA recommended for npm organization
|
|
381
|
+
|
|
382
|
+
[Unreleased]: https://github.com/codetalcott/hyperfixi/compare/v2.4.0...HEAD
|
|
383
|
+
[2.4.0]: https://github.com/codetalcott/hyperfixi/compare/v2.3.1...v2.4.0
|
|
384
|
+
[2.3.1]: https://github.com/codetalcott/hyperfixi/compare/v2.3.0...v2.3.1
|
|
385
|
+
[2.3.0]: https://github.com/codetalcott/hyperfixi/compare/v2.2.1...v2.3.0
|
|
386
|
+
[2.2.1]: https://github.com/codetalcott/hyperfixi/compare/v2.2.0...v2.2.1
|
|
387
|
+
[2.2.0]: https://github.com/codetalcott/hyperfixi/compare/v2.1.0...v2.2.0
|
|
388
|
+
[2.1.0]: https://github.com/codetalcott/hyperfixi/compare/v2.0.0...v2.1.0
|
|
389
|
+
[2.0.0]: https://github.com/codetalcott/hyperfixi/compare/v1.4.0...v2.0.0
|
|
390
|
+
[1.4.0]: https://github.com/codetalcott/hyperfixi/compare/v1.3.0...v1.4.0
|
|
391
|
+
[1.3.0]: https://github.com/codetalcott/hyperfixi/compare/v1.2.0...v1.3.0
|
|
392
|
+
[1.2.0]: https://github.com/codetalcott/hyperfixi/compare/v1.0.0...v1.2.0
|
|
393
|
+
[1.0.0]: https://github.com/codetalcott/hyperfixi/releases/tag/v1.0.0
|
package/README.md
CHANGED
|
@@ -21,16 +21,68 @@ HTML structure is preserved.
|
|
|
21
21
|
|
|
22
22
|
### Flags
|
|
23
23
|
|
|
24
|
-
| Flag | Description
|
|
25
|
-
| --------------- |
|
|
26
|
-
| `--langs`, `-l` | Comma-separated target language codes (`ja,es,ko`).
|
|
27
|
-
| `--out`, `-o` | Output directory. Defaults to `.`.
|
|
28
|
-
| `--from`, `-f` | Source locale of the input. Defaults to `en`.
|
|
29
|
-
| `--strict` | Fail the run on any single attribute that can't be translated. Default is lenient.
|
|
24
|
+
| Flag | Description |
|
|
25
|
+
| --------------- | ------------------------------------------------------------------------------------------------ |
|
|
26
|
+
| `--langs`, `-l` | Comma-separated target language codes (`ja,es,ko`). |
|
|
27
|
+
| `--out`, `-o` | Output directory. Defaults to `.`. |
|
|
28
|
+
| `--from`, `-f` | Source locale of the input. Defaults to `en`. |
|
|
29
|
+
| `--strict` | Fail the run on any single attribute that can't be translated. Default is lenient. |
|
|
30
|
+
| `--check` | Parse-check the English hyperscript on the real engine and exit 3 if any is invalid (see below). |
|
|
30
31
|
|
|
31
32
|
Inputs may be individual files or directories — every `.html` in a directory
|
|
32
33
|
is processed.
|
|
33
34
|
|
|
35
|
+
## Canonical parse-check
|
|
36
|
+
|
|
37
|
+
Every `_="..."` attribute this tool touches on the **English side** can be
|
|
38
|
+
validated against the real [`hyperscript.org`](https://hyperscript.org) parser —
|
|
39
|
+
the same engine the browser runs. This is checked for:
|
|
40
|
+
|
|
41
|
+
- **input** when translating _from_ English (`--from en`, the default) — catches
|
|
42
|
+
an invalid canonical source before it ships in all 24 languages, and
|
|
43
|
+
- **output** when a target language is `en` (foreign → English).
|
|
44
|
+
|
|
45
|
+
Foreign-language _output_ is deliberately **not** round-trip-checked here: this
|
|
46
|
+
package's grammar transformer is lossy in reverse, so a round-trip gate would be
|
|
47
|
+
noise. Faithful foreign-output gating belongs to the semantic-engine transpiler
|
|
48
|
+
(roadmap §5, "later").
|
|
49
|
+
|
|
50
|
+
By default, invalid English prints a deduped warning and the run continues:
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
sample.html: invalid hyperscript (English input -> ja): _="on click qqqq zzzz"
|
|
54
|
+
- Unexpected Token : qqqq
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Pass `--check` to escalate to a build failure (exit code **3**) after all files
|
|
58
|
+
are processed and written:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx @hyperscript-tools/i18n translate src/page.html --langs ja,es --out dist/ --check
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Exit codes:** `0` success · `1` no inputs matched (or `--check` but the parser
|
|
65
|
+
failed to load) · `2` usage error · `3` `--check` found invalid hyperscript.
|
|
66
|
+
|
|
67
|
+
In the Eleventy plugin, use the `parseCheck` option (`'off' | 'warn' | 'error'`,
|
|
68
|
+
default `'warn'`):
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
eleventyConfig.addPlugin(hyperscriptI18n, { parseCheck: 'error' });
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Programmatically:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { validateHyperscript } from '@hyperscript-tools/i18n/validate';
|
|
78
|
+
|
|
79
|
+
const errors = await validateHyperscript('on click toggle .active'); // [] = valid
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
> **Caveat:** the attribute scanner is a regex over `_="..."`, so hyperscript
|
|
83
|
+
> inside HTML comments or `<script>` bodies is checked too. Warn-by-default keeps
|
|
84
|
+
> that from breaking builds.
|
|
85
|
+
|
|
34
86
|
## Eleventy plugin
|
|
35
87
|
|
|
36
88
|
```js
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/validate.ts
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
import { pathToFileURL } from "url";
|
|
4
|
+
import path from "path";
|
|
5
|
+
var cached;
|
|
6
|
+
function loadValidator() {
|
|
7
|
+
if (!cached) {
|
|
8
|
+
cached = (async () => {
|
|
9
|
+
const require2 = createRequire(import.meta.url);
|
|
10
|
+
const iife = require2.resolve("hyperscript.org");
|
|
11
|
+
const esm = path.join(path.dirname(iife), "_hyperscript.esm.js");
|
|
12
|
+
const mod = await import(pathToFileURL(esm).href);
|
|
13
|
+
const hs = mod.default ?? mod;
|
|
14
|
+
if (typeof hs?.parse !== "function") {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"Loaded hyperscript.org but it did not expose parse(); the package layout may have changed."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return (src) => {
|
|
20
|
+
try {
|
|
21
|
+
return (hs.parse(src)?.errors ?? []).map((e) => e.message.split("\n")[0]);
|
|
22
|
+
} catch (e) {
|
|
23
|
+
return ["threw: " + e.message.split("\n")[0]];
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
})();
|
|
27
|
+
}
|
|
28
|
+
return cached;
|
|
29
|
+
}
|
|
30
|
+
async function validateHyperscript(src) {
|
|
31
|
+
return (await loadValidator())(src);
|
|
32
|
+
}
|
|
33
|
+
function formatParseCheckReport(r) {
|
|
34
|
+
const code = r.code.length > 80 ? r.code.slice(0, 77) + "..." : r.code;
|
|
35
|
+
const where = r.stage === "input" ? `English input -> ${r.to}` : `English output <- ${r.from}`;
|
|
36
|
+
return [`invalid hyperscript (${where}): _="${code}"`, ...r.errors.map((e) => ` - ${e}`)].join(
|
|
37
|
+
"\n"
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
var warned = /* @__PURE__ */ new Set();
|
|
41
|
+
function warnInvalidOnce(r) {
|
|
42
|
+
if (warned.has(r.code)) return false;
|
|
43
|
+
warned.add(r.code);
|
|
44
|
+
console.warn(`[hyperscript-i18n] ${formatParseCheckReport(r)}`);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
function __resetParseCheckWarnings() {
|
|
48
|
+
warned.clear();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
loadValidator,
|
|
53
|
+
validateHyperscript,
|
|
54
|
+
formatParseCheckReport,
|
|
55
|
+
warnInvalidOnce,
|
|
56
|
+
__resetParseCheckWarnings
|
|
57
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatParseCheckReport,
|
|
3
|
+
warnInvalidOnce
|
|
4
|
+
} from "./chunk-7ZOFDQYR.js";
|
|
5
|
+
|
|
6
|
+
// src/html.ts
|
|
7
|
+
import { translate } from "@lokascript/i18n";
|
|
8
|
+
var ATTR_PATTERNS = [
|
|
9
|
+
/(_\s*=\s*")([^"]+)(")/g,
|
|
10
|
+
/(_\s*=\s*')([^']+)(')/g,
|
|
11
|
+
/(_\s*=\s*`)([^`]+)(`)/g
|
|
12
|
+
];
|
|
13
|
+
function extractHyperscriptAttributes(html) {
|
|
14
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15
|
+
for (const pattern of ATTR_PATTERNS) {
|
|
16
|
+
for (const m of html.matchAll(pattern)) {
|
|
17
|
+
seen.add(m[2]);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return [...seen];
|
|
21
|
+
}
|
|
22
|
+
function checkHtmlInput(html, validate, to = "*") {
|
|
23
|
+
const reports = [];
|
|
24
|
+
for (const code of extractHyperscriptAttributes(html)) {
|
|
25
|
+
const errors = validate(code);
|
|
26
|
+
if (errors.length > 0) {
|
|
27
|
+
reports.push({ stage: "input", code, errors, from: "en", to });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return reports;
|
|
31
|
+
}
|
|
32
|
+
function dispatch(report, onInvalid) {
|
|
33
|
+
if (typeof onInvalid === "function") onInvalid(report);
|
|
34
|
+
else if (onInvalid === "error")
|
|
35
|
+
throw new Error(`[hyperscript-i18n] ${formatParseCheckReport(report)}`);
|
|
36
|
+
else warnInvalidOnce(report);
|
|
37
|
+
}
|
|
38
|
+
function translateHtml(html, to, options = {}) {
|
|
39
|
+
const from = options.from ?? "en";
|
|
40
|
+
const lenient = options.lenient ?? true;
|
|
41
|
+
const { validate } = options;
|
|
42
|
+
const onInvalid = options.onInvalid ?? "warn";
|
|
43
|
+
if (from === to) return html;
|
|
44
|
+
if (validate && options.checkInput !== false && from === "en") {
|
|
45
|
+
for (const report of checkHtmlInput(html, validate, to)) dispatch(report, onInvalid);
|
|
46
|
+
}
|
|
47
|
+
let out = html;
|
|
48
|
+
for (const pattern of ATTR_PATTERNS) {
|
|
49
|
+
out = out.replace(pattern, (_match, before, body, after) => {
|
|
50
|
+
let translated;
|
|
51
|
+
try {
|
|
52
|
+
translated = translate(body, from, to);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (lenient) return `${before}${body}${after}`;
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
if (validate && to === "en") {
|
|
58
|
+
const errors = validate(translated);
|
|
59
|
+
if (errors.length > 0) {
|
|
60
|
+
dispatch({ stage: "output", code: translated, errors, from, to }, onInvalid);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return `${before}${translated}${after}`;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
function translateHtmlToManyLangs(html, langs, options = {}) {
|
|
69
|
+
const from = options.from ?? "en";
|
|
70
|
+
let perLang = options;
|
|
71
|
+
if (options.validate && options.checkInput !== false && from === "en") {
|
|
72
|
+
for (const report of checkHtmlInput(html, options.validate)) {
|
|
73
|
+
dispatch(report, options.onInvalid ?? "warn");
|
|
74
|
+
}
|
|
75
|
+
perLang = { ...options, checkInput: false };
|
|
76
|
+
}
|
|
77
|
+
const out = /* @__PURE__ */ new Map();
|
|
78
|
+
for (const lang of langs) {
|
|
79
|
+
out.set(lang, translateHtml(html, lang, perLang));
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
extractHyperscriptAttributes,
|
|
86
|
+
checkHtmlInput,
|
|
87
|
+
translateHtml,
|
|
88
|
+
translateHtmlToManyLangs
|
|
89
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
checkHtmlInput,
|
|
3
4
|
translateHtml
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-SYQOFAWY.js";
|
|
6
|
+
import {
|
|
7
|
+
formatParseCheckReport,
|
|
8
|
+
loadValidator
|
|
9
|
+
} from "./chunk-7ZOFDQYR.js";
|
|
5
10
|
|
|
6
11
|
// src/cli.ts
|
|
7
12
|
import { mkdirSync, readFileSync, writeFileSync, existsSync, statSync, readdirSync } from "fs";
|
|
@@ -13,7 +18,8 @@ function parseArgs(argv) {
|
|
|
13
18
|
langs: [],
|
|
14
19
|
from: "en",
|
|
15
20
|
out: ".",
|
|
16
|
-
strict: false
|
|
21
|
+
strict: false,
|
|
22
|
+
check: false
|
|
17
23
|
};
|
|
18
24
|
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") return args;
|
|
19
25
|
args.command = argv[0] === "translate" ? "translate" : "help";
|
|
@@ -28,6 +34,8 @@ function parseArgs(argv) {
|
|
|
28
34
|
args.from = argv[++i] ?? "en";
|
|
29
35
|
} else if (a === "--strict") {
|
|
30
36
|
args.strict = true;
|
|
37
|
+
} else if (a === "--check") {
|
|
38
|
+
args.check = true;
|
|
31
39
|
} else if (a.startsWith("-")) {
|
|
32
40
|
throw new Error(`Unknown flag: ${a}`);
|
|
33
41
|
} else {
|
|
@@ -59,7 +67,7 @@ function printHelp() {
|
|
|
59
67
|
console.log(`hyperscript-i18n \u2014 translate _="..." attributes in HTML
|
|
60
68
|
|
|
61
69
|
Usage:
|
|
62
|
-
hyperscript-i18n translate <input> [<input> ...] --langs <codes> --out <dir> [--from <code>] [--strict]
|
|
70
|
+
hyperscript-i18n translate <input> [<input> ...] --langs <codes> --out <dir> [--from <code>] [--strict] [--check]
|
|
63
71
|
|
|
64
72
|
Options:
|
|
65
73
|
--langs, -l Comma-separated target language codes (e.g. ja,es,ko).
|
|
@@ -67,10 +75,20 @@ Options:
|
|
|
67
75
|
--from, -f Source locale of the input. Defaults to 'en'.
|
|
68
76
|
--strict Fail the run if any single attribute fails to translate.
|
|
69
77
|
Default is lenient: untranslatable snippets are left in place.
|
|
78
|
+
--check Validate the ENGLISH hyperscript on the real hyperscript.org
|
|
79
|
+
parser (input when --from en; output when a target is en) and
|
|
80
|
+
exit 3 if any attribute is invalid. Without --check, invalid
|
|
81
|
+
attributes only print a warning.
|
|
70
82
|
|
|
71
83
|
Output:
|
|
72
84
|
For each input file foo.html and each lang, writes <out>/foo.<lang>.html.
|
|
73
85
|
|
|
86
|
+
Exit codes:
|
|
87
|
+
0 success
|
|
88
|
+
1 no HTML inputs matched, or --check requested but the parser failed to load
|
|
89
|
+
2 usage error (bad flags / missing --langs / no inputs)
|
|
90
|
+
3 --check found invalid hyperscript
|
|
91
|
+
|
|
74
92
|
Example:
|
|
75
93
|
hyperscript-i18n translate src/patterns.html --langs ja,es,ko --out dist/
|
|
76
94
|
`);
|
|
@@ -101,14 +119,43 @@ async function run(argv) {
|
|
|
101
119
|
return 1;
|
|
102
120
|
}
|
|
103
121
|
mkdirSync(args.out, { recursive: true });
|
|
122
|
+
let validate;
|
|
123
|
+
try {
|
|
124
|
+
validate = await loadValidator();
|
|
125
|
+
} catch (err) {
|
|
126
|
+
const msg = err.message;
|
|
127
|
+
if (args.check) {
|
|
128
|
+
console.error(`--check requested but the canonical parser failed to load: ${msg}`);
|
|
129
|
+
return 1;
|
|
130
|
+
}
|
|
131
|
+
console.warn(`parse-check disabled (could not load hyperscript.org): ${msg}`);
|
|
132
|
+
}
|
|
133
|
+
const failures = [];
|
|
134
|
+
const printedCodes = /* @__PURE__ */ new Set();
|
|
135
|
+
const recorderFor = (file) => (report) => {
|
|
136
|
+
failures.push({ file, ...report });
|
|
137
|
+
if (!printedCodes.has(report.code)) {
|
|
138
|
+
printedCodes.add(report.code);
|
|
139
|
+
console.warn(`${file}: ${formatParseCheckReport(report)}`);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
104
142
|
let written = 0;
|
|
105
143
|
for (const file of files) {
|
|
106
144
|
const html = readFileSync(file, "utf8");
|
|
107
145
|
const stem = basename(file, extname(file));
|
|
146
|
+
const onInvalid = recorderFor(file);
|
|
147
|
+
if (validate && args.from === "en") {
|
|
148
|
+
for (const report of checkHtmlInput(html, validate)) onInvalid(report);
|
|
149
|
+
}
|
|
108
150
|
for (const lang of args.langs) {
|
|
109
151
|
const translated = translateHtml(html, lang, {
|
|
110
152
|
from: args.from,
|
|
111
|
-
lenient: !args.strict
|
|
153
|
+
lenient: !args.strict,
|
|
154
|
+
validate,
|
|
155
|
+
// still drives the OUTPUT check when lang === 'en'
|
|
156
|
+
checkInput: false,
|
|
157
|
+
// input already checked once above
|
|
158
|
+
onInvalid
|
|
112
159
|
});
|
|
113
160
|
const outPath = join(args.out, `${stem}.${lang}.html`);
|
|
114
161
|
mkdirSync(dirname(outPath), { recursive: true });
|
|
@@ -119,6 +166,19 @@ async function run(argv) {
|
|
|
119
166
|
console.log(
|
|
120
167
|
`Wrote ${written} files (${files.length} input \xD7 ${args.langs.length} langs) to ${args.out}`
|
|
121
168
|
);
|
|
169
|
+
if (args.check && failures.length > 0) {
|
|
170
|
+
const fileCount = new Set(failures.map((f) => f.file)).size;
|
|
171
|
+
console.error(
|
|
172
|
+
`
|
|
173
|
+
Parse-check failed: ${failures.length} invalid hyperscript attribute(s) in ${fileCount} file(s):`
|
|
174
|
+
);
|
|
175
|
+
for (const f of failures) {
|
|
176
|
+
console.error(
|
|
177
|
+
` ${f.file} [${f.stage}]: ${formatParseCheckReport(f).split("\n").join("\n ")}`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
return 3;
|
|
181
|
+
}
|
|
122
182
|
return 0;
|
|
123
183
|
}
|
|
124
184
|
var isMain = import.meta.url === `file://${process.argv[1]}`;
|
package/dist/eleventy.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LangCode } from './index.js';
|
|
2
2
|
export { TranslateHtmlOptions, translateHtml, translateHtmlToManyLangs } from './index.js';
|
|
3
3
|
import '@lokascript/i18n';
|
|
4
|
+
import './validate.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Eleventy plugin entry: `@hyperscript-tools/i18n/eleventy`
|
|
@@ -22,6 +23,13 @@ import '@lokascript/i18n';
|
|
|
22
23
|
* export default function (eleventyConfig) {
|
|
23
24
|
* eleventyConfig.addPlugin(hyperscriptI18n);
|
|
24
25
|
* }
|
|
26
|
+
*
|
|
27
|
+
* The plugin is ASYNC: when `parseCheck` is on (the default), it awaits the
|
|
28
|
+
* canonical `hyperscript.org` parser before returning. Eleventy 3 awaits plugin
|
|
29
|
+
* callbacks passed to `addPlugin`, so the check is active before any template
|
|
30
|
+
* renders. All three filters are registered SYNCHRONOUSLY before that await, so
|
|
31
|
+
* an un-awaited caller (or Eleventy 2) still gets working filters — the check
|
|
32
|
+
* simply activates once the parser finishes loading.
|
|
25
33
|
*/
|
|
26
34
|
|
|
27
35
|
interface EleventyPluginOptions {
|
|
@@ -29,6 +37,14 @@ interface EleventyPluginOptions {
|
|
|
29
37
|
defaultFrom?: LangCode;
|
|
30
38
|
/** Default lenient flag for HTML translation. Defaults to true. */
|
|
31
39
|
lenient?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Canonical parse-check for the ENGLISH side (input when source is 'en';
|
|
42
|
+
* output when a target is 'en'):
|
|
43
|
+
* 'off' — no check
|
|
44
|
+
* 'warn' — print a deduped warning for invalid English (default)
|
|
45
|
+
* 'error' — throw, failing the build
|
|
46
|
+
*/
|
|
47
|
+
parseCheck?: 'off' | 'warn' | 'error';
|
|
32
48
|
/** Override filter names (in case of collisions in your config). */
|
|
33
49
|
filterNames?: {
|
|
34
50
|
snippet?: string;
|
|
@@ -39,6 +55,6 @@ interface EleventyPluginOptions {
|
|
|
39
55
|
interface EleventyConfig {
|
|
40
56
|
addFilter(name: string, fn: (...args: unknown[]) => unknown): void;
|
|
41
57
|
}
|
|
42
|
-
declare function hyperscriptI18nPlugin(eleventyConfig: EleventyConfig, options?: EleventyPluginOptions): void
|
|
58
|
+
declare function hyperscriptI18nPlugin(eleventyConfig: EleventyConfig, options?: EleventyPluginOptions): Promise<void>;
|
|
43
59
|
|
|
44
60
|
export { type EleventyPluginOptions, LangCode, hyperscriptI18nPlugin as default };
|
package/dist/eleventy.js
CHANGED
|
@@ -1,31 +1,52 @@
|
|
|
1
1
|
import {
|
|
2
2
|
translateHtml,
|
|
3
3
|
translateHtmlToManyLangs
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-SYQOFAWY.js";
|
|
5
|
+
import {
|
|
6
|
+
formatParseCheckReport,
|
|
7
|
+
loadValidator,
|
|
8
|
+
warnInvalidOnce
|
|
9
|
+
} from "./chunk-7ZOFDQYR.js";
|
|
5
10
|
|
|
6
11
|
// src/eleventy.ts
|
|
7
12
|
import { translate } from "@lokascript/i18n";
|
|
8
|
-
function hyperscriptI18nPlugin(eleventyConfig, options = {}) {
|
|
13
|
+
async function hyperscriptI18nPlugin(eleventyConfig, options = {}) {
|
|
9
14
|
const defaultFrom = options.defaultFrom ?? "en";
|
|
10
15
|
const lenient = options.lenient ?? true;
|
|
16
|
+
const parseCheck = options.parseCheck ?? "warn";
|
|
11
17
|
const names = {
|
|
12
18
|
snippet: options.filterNames?.snippet ?? "translateHs",
|
|
13
19
|
snippetMany: options.filterNames?.snippetMany ?? "translateHsAll",
|
|
14
20
|
html: options.filterNames?.html ?? "translateHsHtml"
|
|
15
21
|
};
|
|
22
|
+
let validate;
|
|
23
|
+
const check = (code, stage, from, to) => {
|
|
24
|
+
if (!validate) return;
|
|
25
|
+
const errors = validate(code);
|
|
26
|
+
if (errors.length === 0) return;
|
|
27
|
+
const report = { stage, code, errors, from, to };
|
|
28
|
+
if (parseCheck === "error")
|
|
29
|
+
throw new Error(`[hyperscript-i18n] ${formatParseCheckReport(report)}`);
|
|
30
|
+
warnInvalidOnce(report);
|
|
31
|
+
};
|
|
16
32
|
eleventyConfig.addFilter(names.snippet, (input, to, from) => {
|
|
17
33
|
if (typeof input !== "string" || typeof to !== "string") return input;
|
|
18
34
|
const source = typeof from === "string" ? from : defaultFrom;
|
|
19
35
|
if (source === to) return input;
|
|
36
|
+
if (source === "en") check(input, "input", source, to);
|
|
37
|
+
let out;
|
|
20
38
|
try {
|
|
21
|
-
|
|
39
|
+
out = translate(input, source, to);
|
|
22
40
|
} catch {
|
|
23
41
|
return lenient ? input : "";
|
|
24
42
|
}
|
|
43
|
+
if (to === "en") check(out, "output", source, to);
|
|
44
|
+
return out;
|
|
25
45
|
});
|
|
26
46
|
eleventyConfig.addFilter(names.snippetMany, (input, langs, from) => {
|
|
27
47
|
if (typeof input !== "string" || !Array.isArray(langs)) return {};
|
|
28
48
|
const source = typeof from === "string" ? from : defaultFrom;
|
|
49
|
+
if (source === "en") check(input, "input", source, "*");
|
|
29
50
|
const out = {};
|
|
30
51
|
for (const lang of langs) {
|
|
31
52
|
if (typeof lang !== "string") continue;
|
|
@@ -37,7 +58,9 @@ function hyperscriptI18nPlugin(eleventyConfig, options = {}) {
|
|
|
37
58
|
out[lang] = translate(input, source, lang);
|
|
38
59
|
} catch {
|
|
39
60
|
out[lang] = lenient ? input : "";
|
|
61
|
+
continue;
|
|
40
62
|
}
|
|
63
|
+
if (lang === "en") check(out[lang], "output", source, "en");
|
|
41
64
|
}
|
|
42
65
|
return out;
|
|
43
66
|
});
|
|
@@ -45,10 +68,28 @@ function hyperscriptI18nPlugin(eleventyConfig, options = {}) {
|
|
|
45
68
|
if (typeof html !== "string" || typeof to !== "string") return html;
|
|
46
69
|
const opts = {
|
|
47
70
|
from: typeof from === "string" ? from : defaultFrom,
|
|
48
|
-
lenient
|
|
71
|
+
lenient,
|
|
72
|
+
validate: parseCheck === "off" ? void 0 : validate,
|
|
73
|
+
checkInput: true,
|
|
74
|
+
onInvalid: parseCheck === "error" ? "error" : "warn"
|
|
49
75
|
};
|
|
50
76
|
return translateHtml(html, to, opts);
|
|
51
77
|
});
|
|
78
|
+
if (parseCheck !== "off") {
|
|
79
|
+
try {
|
|
80
|
+
validate = await loadValidator();
|
|
81
|
+
} catch (err) {
|
|
82
|
+
const msg = err.message;
|
|
83
|
+
if (parseCheck === "error") {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`[hyperscript-i18n] parseCheck 'error' but the canonical parser failed to load: ${msg}`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
console.warn(
|
|
89
|
+
`[hyperscript-i18n] parse-check disabled (could not load hyperscript.org): ${msg}`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
52
93
|
}
|
|
53
94
|
export {
|
|
54
95
|
hyperscriptI18nPlugin as default,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { GrammarTransformer, toEnglish, toLocale, translate } from '@lokascript/i18n';
|
|
2
|
+
import { CanonicalValidate, ParseCheckReport } from './validate.js';
|
|
3
|
+
export { formatParseCheckReport, loadValidator, validateHyperscript } from './validate.js';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* HTML attribute scanner + translator.
|
|
@@ -7,8 +9,16 @@ export { GrammarTransformer, toEnglish, toLocale, translate } from '@lokascript/
|
|
|
7
9
|
* attributes, translates each from a source locale to a target locale, and
|
|
8
10
|
* returns the rewritten HTML. The grammar transformer doesn't know about HTML;
|
|
9
11
|
* this module is the glue.
|
|
12
|
+
*
|
|
13
|
+
* Optionally parse-checks the ENGLISH side against the real `hyperscript.org`
|
|
14
|
+
* engine (see ./validate): the input when `from === 'en'` and the output when
|
|
15
|
+
* `to === 'en'`. The check only runs when a `validate` function is supplied, so
|
|
16
|
+
* existing callers see no behaviour change.
|
|
10
17
|
*/
|
|
18
|
+
|
|
11
19
|
type LangCode = string;
|
|
20
|
+
/** What to do with an invalid-English report. */
|
|
21
|
+
type OnInvalid = 'warn' | 'error' | ((report: ParseCheckReport) => void);
|
|
12
22
|
interface TranslateHtmlOptions {
|
|
13
23
|
/** Source locale of the input HTML's `_=` attributes. Defaults to 'en'. */
|
|
14
24
|
from?: LangCode;
|
|
@@ -18,8 +28,30 @@ interface TranslateHtmlOptions {
|
|
|
18
28
|
* whole page build.
|
|
19
29
|
*/
|
|
20
30
|
lenient?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Canonical validator (from `loadValidator()`). When absent, no parse-check
|
|
33
|
+
* runs and behaviour is unchanged.
|
|
34
|
+
*/
|
|
35
|
+
validate?: CanonicalValidate;
|
|
36
|
+
/** What to do with an invalid-English report. Default 'warn' (deduped console.warn). */
|
|
37
|
+
onInvalid?: OnInvalid;
|
|
38
|
+
/**
|
|
39
|
+
* Set false when the caller already validated the English input (as
|
|
40
|
+
* `translateHtmlToManyLangs` and the CLI do). Default true.
|
|
41
|
+
*/
|
|
42
|
+
checkInput?: boolean;
|
|
21
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Every `_=` attribute body in `html` (double/single/backtick quoted), deduped,
|
|
46
|
+
* in document order.
|
|
47
|
+
*/
|
|
48
|
+
declare function extractHyperscriptAttributes(html: string): string[];
|
|
49
|
+
/**
|
|
50
|
+
* Validate every English `_=` attribute body of `html`; returns a report for
|
|
51
|
+
* each invalid one (stage 'input').
|
|
52
|
+
*/
|
|
53
|
+
declare function checkHtmlInput(html: string, validate: CanonicalValidate, to?: LangCode): ParseCheckReport[];
|
|
22
54
|
declare function translateHtml(html: string, to: LangCode, options?: TranslateHtmlOptions): string;
|
|
23
55
|
declare function translateHtmlToManyLangs(html: string, langs: ReadonlyArray<LangCode>, options?: TranslateHtmlOptions): Map<LangCode, string>;
|
|
24
56
|
|
|
25
|
-
export { type LangCode, type TranslateHtmlOptions, translateHtml, translateHtmlToManyLangs };
|
|
57
|
+
export { CanonicalValidate, type LangCode, type OnInvalid, ParseCheckReport, type TranslateHtmlOptions, checkHtmlInput, extractHyperscriptAttributes, translateHtml, translateHtmlToManyLangs };
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
|
+
checkHtmlInput,
|
|
3
|
+
extractHyperscriptAttributes,
|
|
2
4
|
translateHtml,
|
|
3
5
|
translateHtmlToManyLangs
|
|
4
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SYQOFAWY.js";
|
|
7
|
+
import {
|
|
8
|
+
formatParseCheckReport,
|
|
9
|
+
loadValidator,
|
|
10
|
+
validateHyperscript
|
|
11
|
+
} from "./chunk-7ZOFDQYR.js";
|
|
5
12
|
|
|
6
13
|
// src/index.ts
|
|
7
14
|
import { translate, toLocale, toEnglish, GrammarTransformer } from "@lokascript/i18n";
|
|
8
15
|
export {
|
|
9
16
|
GrammarTransformer,
|
|
17
|
+
checkHtmlInput,
|
|
18
|
+
extractHyperscriptAttributes,
|
|
19
|
+
formatParseCheckReport,
|
|
20
|
+
loadValidator,
|
|
10
21
|
toEnglish,
|
|
11
22
|
toLocale,
|
|
12
23
|
translate,
|
|
13
24
|
translateHtml,
|
|
14
|
-
translateHtmlToManyLangs
|
|
25
|
+
translateHtmlToManyLangs,
|
|
26
|
+
validateHyperscript
|
|
15
27
|
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical parse-check.
|
|
3
|
+
*
|
|
4
|
+
* Loads the REAL `hyperscript.org` engine headlessly (parse-only needs no DOM)
|
|
5
|
+
* and exposes a validate function that NEVER throws. The engine has two failure
|
|
6
|
+
* channels: `parse().errors` COLLECTS grammar errors, but the tokenizer THROWS
|
|
7
|
+
* on an unknown character (e.g. a leaked non-ASCII surface → `Unknown token: เ`)
|
|
8
|
+
* and the `js` command's `new Function` throws on invalid JavaScript. Both
|
|
9
|
+
* throws are folded into the returned array so callers have one contract —
|
|
10
|
+
* check `.length === 0`, never wrap in try/catch expecting invalidity to throw.
|
|
11
|
+
*
|
|
12
|
+
* Self-contained on purpose: node builtins + `hyperscript.org` only, no
|
|
13
|
+
* `@lokascript/*` or `@hyperfixi/*` imports, so the package stays extraction-ready.
|
|
14
|
+
*/
|
|
15
|
+
/** Parse `src` on the canonical engine; returns error messages (empty = valid). Never throws. */
|
|
16
|
+
type CanonicalValidate = (src: string) => string[];
|
|
17
|
+
interface ParseCheckReport {
|
|
18
|
+
/** 'input' = English source before translating; 'output' = English produced from a foreign source. */
|
|
19
|
+
stage: 'input' | 'output';
|
|
20
|
+
/** The invalid English hyperscript. */
|
|
21
|
+
code: string;
|
|
22
|
+
/** Canonical parser error messages (first line each). */
|
|
23
|
+
errors: string[];
|
|
24
|
+
/** Source locale of the translate call ('en' for stage 'input'). */
|
|
25
|
+
from: string;
|
|
26
|
+
/** Target locale ('en' for stage 'output'; '*' for an aggregated input check). */
|
|
27
|
+
to: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Load (once, module-cached) the canonical validator. Resolve `hyperscript.org`,
|
|
31
|
+
* then import the sibling prebuilt `_hyperscript.esm.js` by file URL. Rejects if
|
|
32
|
+
* the package cannot be loaded or does not expose `parse()`.
|
|
33
|
+
*/
|
|
34
|
+
declare function loadValidator(): Promise<CanonicalValidate>;
|
|
35
|
+
/** Convenience: load (cached) and validate in one call. */
|
|
36
|
+
declare function validateHyperscript(src: string): Promise<string[]>;
|
|
37
|
+
/** Multi-line human-readable rendering of a report (head line + one ` - <error>` line each). */
|
|
38
|
+
declare function formatParseCheckReport(r: ParseCheckReport): string;
|
|
39
|
+
/** Default warn printer; dedupes by `r.code` per process. Returns true if it printed. */
|
|
40
|
+
declare function warnInvalidOnce(r: ParseCheckReport): boolean;
|
|
41
|
+
/** @internal test hook — clears the per-process warn-dedupe set. */
|
|
42
|
+
declare function __resetParseCheckWarnings(): void;
|
|
43
|
+
|
|
44
|
+
export { type CanonicalValidate, type ParseCheckReport, __resetParseCheckWarnings, formatParseCheckReport, loadValidator, validateHyperscript, warnInvalidOnce };
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__resetParseCheckWarnings,
|
|
3
|
+
formatParseCheckReport,
|
|
4
|
+
loadValidator,
|
|
5
|
+
validateHyperscript,
|
|
6
|
+
warnInvalidOnce
|
|
7
|
+
} from "./chunk-7ZOFDQYR.js";
|
|
8
|
+
export {
|
|
9
|
+
__resetParseCheckWarnings,
|
|
10
|
+
formatParseCheckReport,
|
|
11
|
+
loadValidator,
|
|
12
|
+
validateHyperscript,
|
|
13
|
+
warnInvalidOnce
|
|
14
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperscript-tools/i18n",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "Build-time translation for hyperscript code samples — generate per-language HTML from a canonical English source",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,15 +19,19 @@
|
|
|
19
19
|
},
|
|
20
20
|
"./cli": {
|
|
21
21
|
"default": "./dist/cli.js"
|
|
22
|
+
},
|
|
23
|
+
"./validate": {
|
|
24
|
+
"types": "./dist/validate.d.ts",
|
|
25
|
+
"import": "./dist/validate.js"
|
|
22
26
|
}
|
|
23
27
|
},
|
|
24
28
|
"scripts": {
|
|
25
|
-
"build": "tsup src/index.ts src/cli.ts src/eleventy.ts --format esm --dts --clean",
|
|
26
|
-
"dev": "tsup src/index.ts src/cli.ts src/eleventy.ts --format esm --dts --watch",
|
|
29
|
+
"build": "tsup src/index.ts src/cli.ts src/eleventy.ts src/validate.ts --format esm --dts --clean",
|
|
30
|
+
"dev": "tsup src/index.ts src/cli.ts src/eleventy.ts src/validate.ts --format esm --dts --watch",
|
|
27
31
|
"typecheck": "tsc --noEmit",
|
|
28
32
|
"pretest": "../../scripts/ensure-fresh.sh ../i18n . && npm run build --silent",
|
|
29
33
|
"test": "node --test test/*.test.mjs",
|
|
30
|
-
"test:check": "node --test --test-reporter=spec test/*.test.mjs 2>&1 | tail -10"
|
|
34
|
+
"test:check": "bash -c 'set -o pipefail; node --test --test-reporter=spec test/*.test.mjs 2>&1 | tail -10'"
|
|
31
35
|
},
|
|
32
36
|
"keywords": [
|
|
33
37
|
"hyperscript",
|
|
@@ -44,7 +48,8 @@
|
|
|
44
48
|
"author": "HyperFixi Contributors",
|
|
45
49
|
"license": "MIT",
|
|
46
50
|
"dependencies": {
|
|
47
|
-
"@lokascript/i18n": "^2.
|
|
51
|
+
"@lokascript/i18n": "^2.9.0",
|
|
52
|
+
"hyperscript.org": "^0.9.93"
|
|
48
53
|
},
|
|
49
54
|
"devDependencies": {
|
|
50
55
|
"@types/node": "^20.0.0",
|
|
@@ -54,7 +59,8 @@
|
|
|
54
59
|
"files": [
|
|
55
60
|
"dist",
|
|
56
61
|
"README.md",
|
|
57
|
-
"LICENSE"
|
|
62
|
+
"LICENSE",
|
|
63
|
+
"CHANGELOG.md"
|
|
58
64
|
],
|
|
59
65
|
"publishConfig": {
|
|
60
66
|
"access": "public"
|
package/dist/chunk-XIEA5H7Z.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// src/html.ts
|
|
2
|
-
import { translate } from "@lokascript/i18n";
|
|
3
|
-
var ATTR_PATTERNS = [
|
|
4
|
-
/(_\s*=\s*")([^"]+)(")/g,
|
|
5
|
-
/(_\s*=\s*')([^']+)(')/g,
|
|
6
|
-
/(_\s*=\s*`)([^`]+)(`)/g
|
|
7
|
-
];
|
|
8
|
-
function translateHtml(html, to, options = {}) {
|
|
9
|
-
const from = options.from ?? "en";
|
|
10
|
-
const lenient = options.lenient ?? true;
|
|
11
|
-
if (from === to) return html;
|
|
12
|
-
let out = html;
|
|
13
|
-
for (const pattern of ATTR_PATTERNS) {
|
|
14
|
-
out = out.replace(pattern, (_match, before, body, after) => {
|
|
15
|
-
try {
|
|
16
|
-
const translated = translate(body, from, to);
|
|
17
|
-
return `${before}${translated}${after}`;
|
|
18
|
-
} catch (err) {
|
|
19
|
-
if (lenient) return `${before}${body}${after}`;
|
|
20
|
-
throw err;
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
return out;
|
|
25
|
-
}
|
|
26
|
-
function translateHtmlToManyLangs(html, langs, options = {}) {
|
|
27
|
-
const out = /* @__PURE__ */ new Map();
|
|
28
|
-
for (const lang of langs) {
|
|
29
|
-
out.set(lang, translateHtml(html, lang, options));
|
|
30
|
-
}
|
|
31
|
-
return out;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export {
|
|
35
|
-
translateHtml,
|
|
36
|
-
translateHtmlToManyLangs
|
|
37
|
-
};
|