npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lokascript/htmx-adapter

v4.0.1

Published

Multilingual adapter for upstream htmx v4 — author hx-* attributes in 24 languages

Readme

@lokascript/htmx-adapter

Multilingual adapter for upstream htmx v4 — author hx-* / sse-* / ws-* attributes in 24 languages against the stock htmx library.

<script src="htmx-i18n.global.js"></script>
<!-- this adapter (2.3 KB gz) -->
<script src="vocab/es.js"></script>
<!-- vocab module(s) -->
<script src="htmx.js"></script>
<!-- upstream htmx v4 -->

<section lang="es">
  <button hx-obtener="/api/usuarios" hx-objetivo="#out" hx-disparar="clic">Cargar</button>
</section>

The button issues a real htmx GET /api/usuarios targeting #out on click. The localized attributes stay verbatim in the DOM.

How it relates to the rest of the ecosystem

This package is the htmx analog of @lokascript/hyperscript-adapter (which adapts upstream _hyperscript), built on the hook/vocab pattern from loka-js (which adapts the fixiproject family). It replaces hyperfixi's embedded htmx-compat layer (the hyperfixi-hx*.js bundles of @hyperfixi/core 3.x, retired in 4.0), which reimplemented htmx attributes on hyperfixi's own runtime. This adapter drives the real htmx library.

The vocabulary is the generated modules in vocab/, one self-registering script per language, derived from @lokascript/semantic profiles + @lokascript/i18n dictionaries (so click is clic here as in hyperscript) plus a hand-authored table for the attributes that are not hyperscript keywords. They ship in this package: from a CDN, https://unpkg.com/@lokascript/htmx-adapter/vocab/es.js. (Until 4.0 they shipped in @hyperfixi/core, as vocab/htmx/{lang}.js.)

Mechanism

htmx v4 has no hook to override attribute-name resolution — core reads hx-get literally. We maintain a proposed upstream seam plus a working ~30-line reference patch against 4.0.0 and a ready-to-file Discussion draft (docs/UPSTREAM_HOOK_PROPOSAL.md); against a patched build, installResolverMode(htmx) localizes with zero DOM mutation (proven in the e2e suite). Until the seam exists upstream, the adapter canonicalizes: before htmx processes a node, each localized attribute is copied to its canonical name on the same element (hx-obtener="/x" gains a sibling hx-get="/x"). Two coverage paths:

  1. Initial page — a document sweep at DOMContentLoaded. Load the adapter before the htmx <script> so the sweep listener registers first (loka-js's "orchestrator before libraries" rule).
  2. Swapped-in content — a registered htmx v4 extension (lokascript-i18n) canonicalizes each subtree in htmx_before_process (htmx_before_process_node is kept as an inert alias). (A best-effort defineExtension/onEvent fallback covers htmx v1/v2.)

Guarantees, mirroring loka-js where the mechanism allows:

  • Authored attributes are never removed or rewritten — devtools shows what the author wrote. Documented exceptions: an author-written canonical hx-trigger whose value uses localized event names (hx-trigger="clic") is translated in place (idempotently), since there is no separate canonical target; and in opt-in executor mode a canonical-named hx-on:* attr may be removed so htmx never JS-evals its hyperscript body. On htmx v4 with the extension accepted that removal does not happen: the extension cancels the node's htmx:before:on:init instead and the claimed attr stays authored-verbatim (removal survives only as a per-node fallback when the element also carries an hx-on form htmx must still bind — the legacy composite hx-on="…", the config.prefix spelling, a custom metaCharacter). On htmx v2, or when v4 rejected the registration (htmx.config.extensions allowlist), or with no htmx at all, the attr is removed at claim time — the only guard that needs no hook. Adapter-created canonical siblings are not authored and are always cleaned up.
  • An existing canonical attribute always wins — hx-get is never overwritten by hx-obtener.
  • No vocab loaded → no-op. Stock htmx pages pay nothing.

Per-element language

Language resolves per element at canonicalization time (not per page):

  1. data-hyperfixi-lang on the element
  2. data-hyperfixi-lang on any ancestor
  3. lang on any ancestor (HTML standard)
  4. 'en' fallback

So <section lang="es"> and <section lang="ja"> coexist on one page, each resolving its own vocabulary.

Programmatic API

import {
  register, // register(lang, payload) — same shape as core's vocab modules
  registerWith, // registerWith(htmx) → 'v4' | 'v2' | null
  installAutoSweep, // initial-document sweep + re-sweep on late vocab
  canonicalizeTree, // manual canonicalization of a subtree
  langOf, // per-element language resolution
} from '@lokascript/htmx-adapter';

The browser IIFE does all the wiring automatically and installs window.__hyperfixi_i18n.register so the generated vocab modules self-register. If the page already has a registry (core 3.x's hyperfixi-hx.js), registrations fan out to both.

Hyperscript hx-on: bodies (executor mode, opt-in)

By default, hx-on:* bodies keep upstream semantics: they are JavaScript, htmx executes them, and the adapter translates only the attribute name and event suffix. JS is language-neutral — there is nothing to translate.

To author hyperscript bodies instead — including localized ones — opt in by configuring an executor. The easiest way is auto-detection: load _hyperscript (and, for non-English bodies, a @lokascript/hyperscript-adapter language bundle) on the page and the adapter wires itself:

<script src="_hyperscript.js"></script>
<script src="hyperscript-i18n-es.global.js"></script>
<!-- translator: HyperscriptI18n.preprocess -->
<script src="htmx-i18n.global.js"></script>
<script src="vocab/es.js"></script>
<script src="htmx.js"></script>

<section lang="es">
  <button hx-obtener="/api" hx-objetivo="#out" hx-en:clic="alternar .cargando">…</button>
</section>

Or wire it manually:

import { setBodyExecutor, setBodyTranslator } from '@lokascript/htmx-adapter';
setBodyExecutor((code, elt, evt) => _hyperscript.evaluate(code, { me: elt, event: evt }));
setBodyTranslator((body, lang) => HyperscriptI18n.preprocess(body, lang)); // optional

With an executor set, the adapter claims the entire hx-on family (all bodies are treated as hyperscript — mixed JS/hyperscript pages have no reliable detection):

  • It installs a real event listener per hx-on-family attribute and runs the body through the executor. Translation (localized → English hyperscript) is lazy — first fire, memoized — and confidence-gated by the hyperscript-adapter preprocessor, so untranslatable bodies pass through unchanged.
  • Localized-named attrs (hx-en:clic) stay verbatim in the DOM and get no canonical sibling — htmx never recognized them anyway.
  • Canonical-named attrs (hx-on:click) must be kept away from htmx's own binder — left bound, htmx would eval the hyperscript body as JS, giving a console error plus a double-execution attempt on every fire. A claim records the attribute; what keeps htmx off it depends on the runtime:
    • htmx v4, extension accepted (the registerWith result, not the API's shape): no mutation. The extension's htmx:before:on:init hook consults the record per node and cancels htmx's binding; the authored attribute stays in the DOM, and htmx.process(elt, true) after editing it runs the new body. If the node also carries an hx-on form htmx must bind and the adapter never claims (composite hx-on="event -> code", the config.prefix spelling, a custom metaCharacter), per-node cancellation would kill that too, so the hook removes the claimed canonical attrs and lets htmx bind the rest.
    • htmx v2 (2.0.10 binds hx-on before it fires beforeProcessNode, so there is no pre-bind seam), a rejected v4 registration (the adapter warns, naming htmx.config.extensions), or no htmx: the attr is removed at claim time — the documented exception to the never-mutate rule, and the only guard that does not depend on a hook being installed.
    • Wiring createExtension() into htmx.registerExtension yourself? Call setNeutralizeOnClaim(false) after it succeeds to get the v4 behaviour.
  • The hx-on::after-swap shorthand maps to the htmx: event namespace and works unchanged (the listener hears htmx's real CustomEvents).
  • Re-sweeps never stack duplicate listeners (claims are keyed per element by resolved event name), and an executor configured after the initial sweep triggers a healing re-sweep. The initial sweep waits for DOMContentLoaded to have fired (a load listener covers a script injected in between), and the browser entry registers the extension before it re-detects _hyperscript, so a late _hyperscript, a defered adapter, or a module script all see the same behaviour as the recommended order.

A side benefit: executor mode never uses eval, so hyperscript bodies work on CSP-restricted pages where htmx's native hx-on JS eval cannot.

hx-live bodies stay out of scope: upstream v4's re-execution semantics are internal to htmx's reactivity — name translation only.

Scope (v1)

Attribute names (hx-obtener → hx-get, including the hx-on: colon family) and event values in hx-trigger / hx-on: suffixes (clic → click), plus opt-in hyperscript hx-on: bodies via executor mode (above). The hx-/sse-/ws- prefixes are preserved across languages — only the suffix is localized. The _= attribute is @lokascript/hyperscript-adapter's job, not this package's.

Anything read off an attribute name is matched the way HTML matches names: the parser lowercases ASCII letters in them, so Portuguese's camelCase events work as authored (hx-em:teclaBaixo → hx-on:keydown, although the element carries hx-em:teclabaixo). Event names in hx-trigger values keep exact, case-sensitive matching, as DOM event names do.

Tests

npm test --prefix packages/htmx-adapter                  # vitest, jsdom
npm run test:browser --prefix packages/htmx-adapter      # Playwright e2e (build dist first)

The unit suite loads every generated vocab/{lang}.js module against this adapter's registry, and its drift gate (test/vocab-generator.test.ts, also npm run check:vocab) fails when a committed module differs from what the generator emits — so a profile or dictionary change that moves a name fails here rather than in a browser.

The Playwright suite drives real vendored libraries — htmx 4.0.0, htmx 2.0.10, _hyperscript 0.9.93 (test/browser/vendor/) — verifying the end-to-end truths mocks can't: the v4 extension hook name and firing granularity (htmx_before_process, per processed root — validated against the 4.0.0 source), request/swap from a localized button, both script orders, localized attributes inside swapped-in content, executor-mode hx-on bodies running through real _hyperscript with me bound, and the htmx 2.x defineExtension fallback.