defussRuntime module · made for defuss-shadcn

Translate the page. Keep its state.

defuss-i18n is the localization module of the defuss runtime. Every translation is plain HTML in a <template> beside your markup, and a switch morphs only what a component owns and has rendered before: focus, typed input and state-related rendering like open dialogs stay exactly as they were.

<section id="welcome">
<section id="welcome" data-i18n-component>
  <h1>Translate the page.</h1>
  <h1 data-i18n-target="title">Translate the page.</h1>
  <template data-i18n-for="title" data-i18n-locale="en">Translate the page.</template>
  <template data-i18n-for="title" data-i18n-locale="de">Seite übersetzen.</template>
  <button aria-label="Close">×</button>
  <button aria-label="Close" data-i18n-aria-label-de="Schließen">×</button>
</section>

The translations live in the markup: one <template> per language, attributes as data-i18n-*. The cursor marks the line that is live right now.

What it is

🧩 A localization module for the defuss runtime

defuss-i18n implements the df$.i18n API with no framework dependencies, no build step and no DOM reconciler.

Plugs into df$

Load it after defuss-shadcn and call df$.i18n. Without shadcn, defuss-query and defuss-morph provide the same runtime.

HTML is the catalog

Each language is a <template> next to the default markup. The page reads without JavaScript, and a translation may change structure, not only words.

State survives a switch

Regions are morphed in place: elements, listeners, focus, text selection, typed input and open dialogs stay.

Native Intl

BCP 47 fallback chains such as de-AT → de → en, CLDR plural rules, and number and date formats from the browser, with no bundled locale data.

Attributes, too

Sixteen content attributes, from alt and aria-label to href and src, through data-i18n-{attribute}-{locale}.

Checked before it writes

validateI18n returns structured diagnostics for missing languages, overlapping regions and broken ID references.

Languages on demand

Ship one language and fetch the others with loadLocale. Only the newest request commits; a superseded one resolves as superseded.

Values stay text

Interpolated values are written as text, and value slots inside script, style or iframe are rejected before any write.

How it works

⚙️ One switch, four steps

A language switch is synchronous: when setLocale returns, every bound component shows the new language.

  1. Author

    Write the default language as plain HTML and mark the parts that change with data-i18n-target. Each translation goes into a <template> beside it.

  2. Bind

    bind(root, locale) captures the templates once and checks ownership: every region belongs to exactly one component.

  3. Select

    setLocale('de') commits a frozen snapshot. Each binding picks one template per region through the fallback chain and plural rules, and fills values and attributes on detached copies.

  4. Morph

    defuss-morph patches each live region through df$. Matched elements stay, lang and dir follow, and defuss-i18n:change fires.

Live demo

🔀 Switch the language, watch what stays

Every sentence on this page comes from a defuss-i18n template. The window below holds what usually breaks: typed input in an open dialog, plural forms, markup rendered from state and translated attributes.

Language
Locale
en
Revision
–
Direction
ltr
Fallback
en

Egg carton

Plural rules

In the carton

1 egg

CLDR picks the plural form, Intl formats the number.

Your settings

Native state

Open the dialog, type a name, then switch the language inside it.

Application state

State renderer

render() builds this markup from state, so the checkbox keeps its value through every switch.

Hello in a speech bubble

Attributes

src · alt

The image’s src and alt switch with the language, through data-i18n-src-de and data-i18n-alt-de.

Event log

One line per render, written by a defuss-i18n:change listener.

6.2 kBgzip, minified browser build, peers excluded
16translatable content attributes
4builds: ESM, CommonJS, browser ESM, classic script
0runtime dependencies; defuss-query and defuss-morph are peers
Install

📦 Add it to a website

defuss-i18n works best with defuss-shadcn. From jsDelivr next to defuss-shadcn, from npm for a bundler, or with the two peers on their own.

By hand

Pick how the page loads its runtime, then copy the snippet.

index.html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/defuss-shadcn@0.9.8/dist/components/core.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/defuss-shadcn@0.9.8/dist/components/all.min.css"><script type="module" src="https://cdn.jsdelivr.net/npm/defuss-shadcn@0.9.8/dist/components/all.min.js"></script><script type="module" src="https://cdn.jsdelivr.net/npm/defuss-i18n@0.1.0/dist/all.min.js"></script><script type="module">  const locale = df$.i18n.createI18n({ locale: 'en', fallback: ['en'] });  for (const root of document.querySelectorAll('[data-i18n-component]')) df$.i18n.bind(root, locale);  locale.setLocale('de'); // every bound component now shows German</script>

Vibe coding

Paste this prompt into your coding agent. It links the repository, so the agent works from the current README and the authoring contract in documentation/component-skill.md.

Integrate defuss-i18n into this app: https://github.com/kyr0/defuss-i18n
Read its README.md and documentation/component-skill.md first. Install it with bun add defuss-i18n defuss-query@^0.1.0 defuss-morph@^0.1.1, or load https://cdn.jsdelivr.net/npm/defuss-i18n@0.1.0/dist/all.min.js after the all.min.js of defuss-shadcn.
Mark each translated component with data-i18n-component and the parts that change with data-i18n-target. Put one <template data-i18n-for data-i18n-locale> per language next to each part, never inside it, and translate attributes with data-i18n-{attribute}-{locale}.
Create one controller with createI18n({ locale, fallback: ['en'] }), bind() every component, resolve every validateI18n diagnostic, pass dynamic values as text through data-i18n-value slots, and dispose() a binding when its component leaves the page.
⏎ send

Give your agent the defuss-vae skills too: it then plans, tests and reviews against a verifier instead of declaring itself done. In Claude Code, the plugin adds the commit and Stop-hook gate. Needs python3 ≥ 3.9, git and make.

npx skills add kyr0/defuss-vae --skill '*'
Portrait of Aron Homberg

Stressed with inconsistent results and tiring reviews?

Build more consistent agents with defuss-vae, Aron Homberg’s Verified Agentic Engineering method. Aron is a freelance AI researcher, O’Reilly author (2011), conference speaker, and longtime mentor to software engineering teams.

Talk to Aron on LinkedIn

…if you’re looking for a remote training or mentoring session for you or your engineering team.

Read the documentation

Guides to state and ownership, lazy loading and the security model, plus the full API and error reference.

MIT licensed · Source on GitHub

Your settings

Type a name, then switch the language: the field keeps what you typed.