Files
RTLPlayground/doc/support-multi-language.md
T
bloqaudio 80552816b9 web: move the language and theme selectors to the System section
The header carried the save button, the language selector and the theme
selector on every section, although the latter two are set once. They
now live in a Display card in the System section; the header keeps the
title, the unsaved-changes badge and Save to flash.
2026-09-14 08:03:36 -05:00

3.3 KiB

Supporting Multiple Languages in the Web UI

The firmware uses a client-side i18n approach: all translations are stored in one JavaScript dictionary embedded in the firmware. No server-side changes are needed.

Architecture

All translation logic lives at the top of html/app.js:

  • A LANG object with one sub-object per language (en, ja, zh)
  • Language auto-detection (browser language, overridden by the rtl_lang key in localStorage, which the language selector in the System section writes)
  • t(key, vars) looks up a translated string; {name} placeholders in the string are replaced from vars, e.g. t("v_del_q", {n: vid})
  • i18nApply() applies the dictionary to the static markup once at load

Translation keys are flat strings. The English entry is the fallback when a key is missing in another language, and the key itself is the fallback when it is missing everywhere.

Changing the language stores the choice and reloads the page, so every dynamically built table is rebuilt in the new language.

login.html is served before authentication and does not load app.js; it carries its own five strings per language in an inline table.

How to Add a New Language

1. Add a dictionary entry in html/app.js

Append a sub-object to LANG. Every key from LANG.en should be present:

var LANG={
en:{
nav_dash:"Dashboard",
...
},
LANGCODE:{            // add your language here
nav_dash:"...",
...
}
};

2. Add the language to the selector in html/index.html

<select class="ctl" id="langSel" data-i18n-t="sy_lang">
  <option value="en">English</option>
  <option value="ja">日本語</option>
  <option value="zh">中文</option>
  <option value="LANGCODE">Native name</option>
</select>

3. Add the login strings in html/login.html

The inline table there holds, per language: page title, subtitle, password label, button label, wrong-password message.

4. Verify auto-detection

The detection normalises navigator.language to its first two characters and selects that language if LANG has it, otherwise English.

Two Translation Mechanisms

(A) data-i18n attributes (declarative, for HTML)

<h2 data-i18n="pt_title">Port configuration</h2>
<button class="ctl" data-i18n="c_apply" data-i18n-t="sy_dhcp_t">Apply</button>
<input class="in" data-i18n-p="l2_filter">
  • data-i18n sets the element's text content
  • data-i18n-t sets the title attribute (tooltip)
  • data-i18n-p sets the placeholder attribute

The English text stays in the markup as the fallback and as the source of truth for what the key means.

(B) t("key") calls (imperative, for JavaScript)

tr.insertCell().textContent=t("c_port")+" "+p;
toast(t("v_loaded",{n:vid}),"ok");

Strings that are identical in every language (10M, 2.5G, MAC, VLAN, CPU, RSTP) are literals, not dictionary entries.

Size Considerations

  • Each language adds roughly 3 KB to app.js before compression; the build gzips the file, so the flash cost of a language is closer to 1 KB
  • The largest embedded file must stay below 64 KB after compression (uint16_t length in the file table); app.js with three languages is about 29 KB compressed

Build

No special flags are needed. html/ is minified and embedded by the normal build (see webui-compression.md).