# 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: ```js var LANG={ en:{ nav_dash:"Dashboard", ... }, LANGCODE:{ // add your language here nav_dash:"...", ... } }; ``` ### 2. Add the language to the selector in `html/index.html` ```html ``` ### 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) ```html

Port configuration

``` - `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) ```js 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`).