Files
mc-webui/docs/translations.md
MarekWoandClaude Opus 5 4d2b3eb6be docs(i18n): state the catalog filename rule and where to install it
A catalog whose filename does not match ^[a-z]{2}(-[A-Z]{2})?$ is ignored
silently by available_languages() — no error, no log line, no entry in the
dropdown. pt-br.json, hu_HU.json and hungarian.json all fail that way, and the
guide gave no way to find out. Verified against the running container: hu.json
appeared as "Magyar" with three keys and the rest falling back to English,
while pt-br.json, hungarian.json and a de.json with a broken body were all
skipped without affecting the app.

Also spells out that app/translations/ lives inside the image and needs a
rebuild, so the config-directory drop-in is the one to reach for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 15:48:38 +02:00

217 lines
7.9 KiB
Markdown

# Translating mc-webui
The interface can be translated into any language. Translations live in a single JSON
file per language, so adding one needs no rebuild, no restart and no compile step —
drop the file on your server and pick it in **Settings → Appearance → Language**.
Built-in: **English** (`en`, the source) and **Polish** (`pl`). Everything else comes
from the community.
> **The backend stays English.** This system translates the interface only. Error and
> status messages produced by the server (roughly one toast in seven, mostly on error
> paths) will still appear in English. That is deliberate, not a gap in your translation.
---
## 1. Read this first: what must NOT be translated
Mesh operators use the English protocol terms whatever language their UI is in. A
translated "flood" matches no firmware documentation, no `meshcore-cli` output and no
forum post — it just makes the app harder to use.
**Leave these in English, always:**
| | |
|---|---|
| **Mode names shown as values** | `Flood`, `Direct`, `FLOOD` — these are states the device reports, not descriptions |
| **Protocol and radio terms** | hop, advert, ACK, RSSI, SNR, LoRa, MQTT, broker, telemetry, pubkey, hash, spreading factor, bandwidth, coding rate |
| **Node roles** | Companion, Repeater, Room Server, Sensor — and the codes `COM`, `REP`, `ROOM`, `SENS` |
Ordinary words **are** translated even when they describe protocol things — "path" becomes
"ścieżka", "label" becomes "etykieta". The rule is whether the word is a *value the device
uses* or a *name the user reads*. `Flood` in a path field is a value; "Configured paths" is
a heading.
| **CLI surfaces** | everything the Console prints, its `help` screen, `Usage:` lines, command names |
| **Log output** | log lines and the level names `DEBUG`, `INFO`, `WARNING`, `ERROR` |
| **Device/firmware fields** | `radio.rxgain`, `advert.interval` and similar |
| **Region and preset names** | `EU/UK (Narrow)`, `USA/Canada (Recommended)` — these are proper nouns |
You **do** translate the text around them. A sentence explaining a technical concept is
translated even though the term inside it is not:
```json
"settings.device.path_hash_desc": "Bytes per hop in routing paths. 1B = shortest path, more collisions."
```
```json
"settings.device.path_hash_desc": "Bajtów na hop w ścieżkach routingu. 1B = najkrótsza ścieżka, więcej kolizji."
```
"hop" survives untranslated inside translated prose. That is the intended shape.
Inflecting a term is fine — *"hopów"*, *"repeatera"* — deleting it is not.
The canonical glossary list lives in `scripts/i18n_check.py` (`GLOSSARY`), which warns
when a translation drops one of these terms.
---
## 2. Adding a language
### Name the file correctly
The filename **is** the language code, and it must be a two-letter code, optionally
followed by an uppercase region: `hu.json`, `de.json`, `pt-BR.json`, `zh-CN.json`.
A file that does not match that shape is **ignored silently** — no error, no entry in the
Language dropdown, nothing in the log. `pt-br.json`, `PT-BR.json`, `hungarian.json` and
`hu_HU.json` all fail this way. If your language does not appear after a refresh and the
⟳ button, check the filename first.
### Get the file to work from
```bash
cp app/translations/en.json app/translations/hu.json # or work outside the repo
```
Then translate the **values**, never the keys:
```json
{
"meta.language_name": "Magyar",
"meta.language_english_name": "Hungarian",
"meta.translator": "Your Name <you@example.com>",
"common.close": "Bezárás",
"chat.compose.input_ph": "Írj üzenetet..."
}
```
`meta.language_name` is what appears in the Settings dropdown, written in the language
itself. The other `meta.*` keys are optional.
### Install it on your server
Drop the file into the `translations` folder inside your config directory — the same
volume that holds the database (`/data` inside the container, `MC_CONFIG_DIR` on the
host). With the stock `docker-compose.yml` that is:
```bash
mkdir -p ./data/translations
cp hu.json ./data/translations/
```
> Putting the file in `app/translations/` instead also works, but that folder is baked
> into the container image — it needs `docker compose up -d --build`. Use it only when
> you are contributing the language back to the project. The config directory is the
> one that needs no rebuild and survives an update.
Refresh the browser. The language appears in **Settings → Appearance → Language**
immediately; no restart is needed. If it does not show up (some network filesystems
report file timestamps too coarsely), click the ⟳ button next to the dropdown.
A file in this folder **overrides** a built-in one of the same name, so you can also use
it to correct the shipped `pl.json` on your own server without touching the image.
### Contribute it back
Open a pull request adding your file to `app/translations/`. Please run the checker
first (below) and mention which mc-webui version you translated against.
---
## 3. The file format
Flat keys, one JSON object, no nesting:
```json
"settings.appearance.theme": "Motyw"
```
Key names describe where the string appears (`settings.appearance.theme`), and a suffix
describes what kind of string it is:
| Suffix | Meaning |
|---|---|
| *(none)* | visible text |
| `_title` | tooltip (`title=`) |
| `_ph` | input placeholder |
| `_aria` | screen-reader label |
| `_desc` | helper text under a control |
| `_btn` | button label |
### Placeholders
`{name}` is substituted at runtime. **Keep every placeholder** — the checker reports a
mismatch as an error. You may reorder them freely:
```json
"toast.contacts.deleted": "Deleted {name}"
```
```json
"toast.contacts.deleted": "Usunięto kontakt {name}"
```
A `{` that is not followed by a word is left alone, so `set {name} <value>` is safe.
### Plurals
A value may be an object of plural forms instead of a string. Use the categories your
language actually needs — the app picks the right one via the browser's own CLDR rules:
```json
"contacts.path.hops": { "one": "({count} hop)", "other": "({count} hops)" }
```
```json
"contacts.path.hops": {
"one": "({count} hop)",
"few": "({count} hopy)",
"many": "({count} hopów)",
"other": "({count} hopa)"
}
```
`{count}` is always available. Valid categories: `zero`, `one`, `two`, `few`, `many`,
`other`. English needs `one` + `other`; Polish needs four.
> Plurals in server-rendered text use a simpler built-in rule table that implements
> English and Polish exactly and falls back to `one`/`other` elsewhere. Client-rendered
> plurals — the large majority — always use the browser's full CLDR rules.
### Markup
A few values contain HTML such as `<b>` or `<a href="...">`. Keep the tags intact and
translate the text between them. Do **not** add markup to a value that had none.
### Missing keys
Anything you leave out falls back to English automatically. A partial translation is
perfectly usable — ship it and fill in the rest over time.
---
## 4. Checking your work
```bash
python scripts/i18n_check.py # everything
python scripts/i18n_check.py --lang hu # one language
python scripts/i18n_check.py --missing hu # your worklist: untranslated keys + English text
```
`--missing` prints tab-separated `key<TAB>English text`, which pastes straight into a
spreadsheet.
The checker reports as **errors**: a placeholder you dropped or invented, a key that no
longer exists, invalid JSON. As **warnings**: a glossary term that disappeared, and keys
present in the catalog but unused in the code.
---
## 5. Security note
An installed catalog is trusted content. Values may contain HTML, and the app renders it
— the same trust level as installing a plugin. Only install catalogs you have read or
that came from a source you trust. Interpolated values (contact names, message text) are
always escaped, so a catalog cannot be used to attack data flowing through it, and
catalog text is never executed as code.
A malformed or unreadable catalog is logged and skipped; it cannot take the app down.