>_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 / t | look 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_LOCALES | frozen 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-i18n | sets textContent from the keyed string |
data-i18n-title | sets the element title (tooltip) |
data-i18n-placeholder | sets an input placeholder (decodes / to newlines) |
data-i18n-placeholder-regex | alternate 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-419 → es_419.json, pt-BR → pt_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