// ZPWR-I18N — SHARED PORTABLE I18N RUNTIME

shared front-end component · vanilla JS, zero dependencies · one i18n.js on window · JSON catalogs, 27 locales · hosts: Tauri + JUCE WebViews

Report GitHub
// Color scheme

>_ZPWR-I18N

One runtime, every app, JSON catalogs. The shared internationalization runtime behind the MenkeTechnologies GUI stack — a single portable, host-agnostic i18n.js. The appFmt formatter and applyUiI18n appliers are lifted verbatim from the Audio-Haxor front end (ipc.js + i18n-ui.js); the only change is the loader: catalogs load from plain JSON files (fetch) instead of Audio-Haxor's SQLite-backed Tauri command, so the same code runs in Tauri apps and JUCE WebViews.

One file, many hosts

Tauri (Rust / WebView)Audio-Haxor, traderview, ztranslator — a copy script syncs i18n.js into the served frontend/; the app loads it as a classic script and calls bootI18n() after the DOM exists
JUCE (C++ / WebView)synth, fx, midi-fx, daw — embed i18n.js + the locale JSONs via juce_add_binary_data; the resource provider serves them by basename

No build step, no framework, no module bundler — it attaches its API to window and works wherever a browser fetch and document exist. A missing locale never breaks boot: fetchCatalog returns {} on any failure and appFmt falls back to the key, so an untranslated UI still reads.

API (all on window)

appFmt(key, vars) / toastFmt / tlook up __appStr[key], interpolate {var} tokens from vars, fall back to the key itself when missing or empty
applyUiI18n()apply the loaded catalog to [data-i18n] (textContent), [data-i18n-title] (title), [data-i18n-placeholder] (+ optional -regex) elements
applyI18nPlaceholders()apply only the [data-i18n-placeholder] pass (used when a search box toggles its regex variant)
loadLocale(locale, { base, extraBases })fetch <base><locale>.json, merge any extraBases (e.g. a bundled shared-component fragment) on top, set __appStr, persist, then apply
bootI18n(locale?)load the detected (saved → navigator → en) locale; call once at boot
mergeI18nCatalog(obj)merge a shared-component locale fragment on top of the current catalog (later merges win)
detectLocale() / savedLocale() / normalizeUiLocale(l)best initial locale; the persisted choice; validate a code against the supported set
SUPPORTED_UI_LOCALESfrozen array of the 27 shipped locale codes

Wiring it up

Load i18n.js as a classic script (<script> with a src of the file), then boot once the DOM exists. bootI18n() resolves the locale (saved choice, then navigator language, then en), fetches the catalog, and applies it to every data-i18n* element.

// after i18n.js has loaded:

// optional: override the catalog directory (default 'i18n/')
window.__i18nBase = 'i18n/';
// optional: merge a bundled shared-component fragment on top
window.__i18nExtraBases = ['zpwr-file-browser/i18n/'];

document.addEventListener('DOMContentLoaded', () => {
  bootI18n();                   // detect + load + apply
});

To switch locale at runtime, call loadLocale('de'); it re-fetches, persists the choice via the shared prefs store (or localStorage), and re-applies the DOM.

Format strings

appFmt (aliased toastFmt and t) reads window.__appStr[key] and interpolates {name} tokens. A missing or empty value returns the key, so call sites stay readable even before a catalog loads.

// catalog: { "toast.imported": "Imported {count} samples from {pack}" }
t('toast.imported', { count: 42, pack: 'Vintage Drums' });
// → "Imported 42 samples from Vintage Drums"

t('menu.unknownKey');           // → "menu.unknownKey" (no catalog entry)

Markup contract

Mark translatable DOM with data attributes; applyUiI18n() fills them from the catalog. Four attributes are honored:

data-i18nsets textContent from the keyed string
data-i18n-titlesets the element title (tooltip)
data-i18n-placeholdersets an input placeholder (decodes &#10;/&#13; to newlines)
data-i18n-placeholder-regexalternate placeholder used when the enclosing .search-box has an active .btn-regex
<h1 data-i18n="app.title">Audio Haxor</h1>
<button data-i18n-title="btn.scan.tip">Scan</button>
<input data-i18n-placeholder="search.ph"
       data-i18n-placeholder-regex="search.ph.regex">

Catalogs

Flat { "namespace.key": "string with {vars}" }, one JSON per locale at <base><locale>.json (default base i18n/). Hyphenated codes map to underscores in the filename — es-419es_419.json, pt-BRpt_BR.json (see localeFile). Shared components ship their own per-locale fragment (e.g. zpwr-file-browser/i18n/<locale>.json) which the host merges via extraBases. Locale persistence uses the shared prefs store when present, else localStorage.

Supported locales (27)

SUPPORTED_UI_LOCALES is the frozen set the runtime validates against; normalizeUiLocale rejects anything outside it (returning null), and detectLocale falls back to en.

cs  da  de  el  en  es  es-419  fi  fr  hi  hu  id  it  ja
ko  nb  nl  pl  pt  pt-BR  ro  ru  sv  tr  uk  vi  zh

The MenkeTechnologies stack

zpwr-i18n is a shared front-end component of the MenkeTechnologies GUI applications. Browse the rest via the MenkeTechnologiesMeta umbrella repo:

  • MenkeTechnologiesMeta — umbrella repo orchestrating every shared component and host app
  • Audio-Haxor — Tauri v2 desktop app; the runtime's original home
  • traderview / ztranslator — Tauri apps consuming the runtime over JSON catalogs
  • zpwr-synth / zpwr-fx / zpwr-midi-fx / zpwr-daw — JUCE WebView apps that embed the runtime + locale JSONs
  • zpwr-file-browser / zpwr-embed-terminal — shared components that ship their own per-locale fragments merged via extraBases