Dashboard i18n (EN/RU toggle)
Standalone dashboard (dashboard/index.html) is EN-first with a deterministic
EN→RU toggle (no backend, no build step, no new deps).
Source of truth
Section titled “Source of truth”- Dictionary file:
dashboard/i18n_ru.js— EN keys (source), RU overlay. Pretty-printed one key per line, loaded via<script src="i18n_ru.js">beforedashboard/i18n.js(shippedi18n_t/__DYN/__RUlogic, shared by the page anddashboard/i18n.test.jsviavm). Served as static assets byserve.py(itsHandler.do_GETserves any file underSCRIPT_DIRviasuper().do_GET(); no extra route needed). If/i18n_ru.js404s, the__RUguard ((typeof i18nRU !== 'undefined' && i18nRU) || {}) keeps the dashboard working with the EN source itself as fallback. - Key format: EN string is the key, RU string is the value. Example:
"Home": "Главная". Dynamic keys embed${0},${1}placeholders:"Window: ${0} → ${1} (${2})": "Окно: ${0} → ${1} (${2})". - Fallback: missing RU entry → return the EN source verbatim (visible gap, never silent). Do not add a RU-only key — every key must be a valid EN source.
Static vs dynamic
Section titled “Static vs dynamic”- Static keys (
k.indexOf('${') === -1): direct__RU[en]lookup. - Dynamic keys (
${N}): regex fallback. The placeholder is escaped viak.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\$\\\{[^}]*\\\}/g, '(.*)')— note the escaped\$(the unescaped$is a regex end-anchor and breaks the fallback; the bug caused 89/297 dynamic strings to stay untranslated). Captured groups are substituted with a function replacementout.replace('${' + i + '}', () => g)so a literal$&in the provider/model name does not expand to the whole match. - Leaf text only: translate inner text (
Model:,Tokens,Home), not HTML/SVG blobs. The 136 markup-keyed entries (notably the<rect>chart case) were refactored to build structure once and translate leaves;dashboard/i18n.test.jsasserts no key contains<and checks a chart leaf (Model: Other).
Persistence and chrome
Section titled “Persistence and chrome”- Default:
__dashLang = 'en'(document.documentElement.langsynced on load and on toggle).<html lang="en">and<title>hermes-telemetry dashboard</title>are English by default — the EN source is the document language. - Persistence key:
hermes_telemetry_langinlocalStorage(un-namespaced from the genericdashboard_langto avoid collision). Survives reload. - No
location.reload():__swapLang()does an in-place re-render (__syncLangBtn()→__applyStaticI18n()→loadAll({showShell:false})), preserving drilldown/filters/scroll.__applyStaticI18nsnapshots original text/attr/title in__staticI18nOrigso switching EN→RU→EN restores verbatim. - Badge class from raw value:
badge(raw, map, label)resolvesclassfrom the raw status (ok/error/interrupted/running) and renderslabelseparately.statusBadge(raw)mapsok:'OK'(Latin, not CyrillicОК) viastatusLabelsRU; the EN path is identity (as on main) and RU is the only override, so casing stays uniform. Extra statusestimeout/failed/cancelledwere removed — only the four real run statuses remain (subagentchild_statusnever reaches runs).
dashboard/i18n.test.js (node --test dashboard/i18n.test.js, no deps) loads the
shipped dashboard/i18n.js + dashboard/i18n_ru.js via vm (no local copy of
i18n_t — drift fails CI by construction) and asserts: A1 (OK→Успех RU,
OK→OK EN) and A2 (Total: … tokens→Итого), shadowing for every dynamic key
(V0/V1 substitution, most-specific-first), coverage of every static
i18n_t literal in index.html, $& safety, and chart-leaf labels. CI runs it
as Dashboard i18n tests.
Known limitation
Section titled “Known limitation”Only the standalone dashboard (dashboard/index.html) is localized; the plugin
widget (dashboard/dist/index.js) stays English, so running both surfaces gives
a mixed-language UI.
hermes-telemetry is an independent community plugin for Hermes Agent. It is not affiliated with or endorsed by Nous Research.