Repository navigation
Conversation
Adds a build-free translation layer and a Brazilian Portuguese
dictionary, piloted on the navigation chrome and the home page.
- public/i18n.js: tr()/trn() with {placeholders} and CLDR plurals,
English text as the key with English fallback, language from the
saved choice (meshcore-lang), else the browser, else English.
Translates static markup through data-i18n* attributes.
- index.html loads i18n.js in <head> so the dictionary fetch starts
early; app.js waits for I18n.ready (3 s cap) before the first route.
- Customizer > Display: language picker (reloads on change).
- Translated: top nav, bottom nav + More sheet, nav drawer, home page,
timeAgo().
- tests/unit/test-i18n.js: detection, tr/trn, loading/fallback, DOM
application, and dictionary rules (same placeholders, no markup).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator home text (hero, steps, FAQ, footer labels) now goes through tr(). The built-in defaults and the config.example.json defaults are in the pt-BR dictionary, so instances that kept them get them translated; text an operator wrote has no entry and renders exactly as written. Legacy "<emoji> Label" footer labels keep the emoji and translate the words. test-i18n: translations must keep the English entry's links. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Packets page fully routed through tr()/trn(): toolbar and filters, table headers and column picker, row tooltips, detail pane, byte breakdown, observations, BYOP decoder and the standalone packet page. Shared pieces used here are translated too: hop names/conflict popover and path-symbols legend (hop-display), region and area filters, and the filter help, autocomplete, context menu and saved filters (filter-ux; packet-filter.js descriptions are translated at render, the engine is untouched). - PANEL_CLOSE_HTML becomes panelCloseHtml(): it was built at load, before the dictionary arrives. - Modules that unit tests load on their own get a two-line English tr()/trn() fallback; two tests that slice packets.js source are updated for the tr() calls. - scripts/i18n-report.js: per-language coverage (missing keys, keys not found as literals) for translators. - pt-BR: 322 new entries; 100% of the strings in code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Map page routed through tr()/trn(): map controls (node types with active/uncertain/stale counts, byte size, display toggles, status, scope config states and their explanations, region scope filter and hint, neighbours, important links, last heard, quick jump), node and observer popups, path inspector, route trust message, affinity debug and important-link popups, cluster and marker aria labels. Also translated: estimated-position popups (map-estimated.js) and the packet-route renderer's popups, legend and labels (route-render.js). - SCOPE_STATES and ROLE_LABELS stay English data, translated where they are shown (both are built at load, before the dictionary). - map.js, map-estimated.js and route-render.js get the two-line English tr()/trn() fallback for tests that load them alone; test-issue-1356 accepts the translated forms of its source checks. - pt-BR: 180 new entries; 100% of the strings in code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live page routed through tr()/trn(): header stats, display toggles and their screen-reader descriptions, node filter, audio controls, feed (empty state, detail card), legend (packet-type descriptions, node roles, marker styles), panel corner buttons and announcements, VCR bar (buttons, state labels, timeline scopes, missed-packets prompt), fullscreen/settings toggles, node detail panel and its paths list. - Packet-type short names stay English (filter tokens); their long descriptions from payload-labels.js are translated where shown. - The LCD readout (LIVE / PAUSE / PLAY) keeps its device-style English. - live.js gets the English tr()/trn() fallback; two tests that run slices of live.js get a pass-through tr (test-xss-escape-sinks' evalTemplate now provides tr/trn to every extracted template). - pt-BR: 111 new entries; 100% of the strings in code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nodes page routed through tr()/trn(): search, role counts, tabs, status/last-heard/silent/LoRa filters, table headers, side pane and slide-over, full node page (status explanations, relay activity, traffic-share and bridge scores with their tooltips, scopes, recent adverts, direct/relay "Heard by", neighbours, affinity debug, paths, clock skew and its evidence), not-found/error states. Also translated: clock-skew badge, sparkline and the Coverage nav link (roles.js), self-reported repeaters (reported-nodes.js) and LoRa parameters (rf-params.js). - Data labels (tabs, confidence, skew severity, advert groups) stay English and are translated where shown. - Modules get the English tr()/trn() fallback; three source-check tests accept the translated forms (same English text in tr()). - pt-BR: 192 new entries; 100% of the strings in code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Per-node subpages routed through tr()/trn(): Analytics (coverage notice, stat cards, chart titles/descriptions/axes/legends, uptime heatmap day names, battery chart and status, peer table), hop count at this node (node-hop-analytics: filters, summary, empty/ambiguous notes, axes) and Reach (header, scope line, stat cards, link filters, coverage legend, link table, counts, busy/empty/error states) with its map legend and coverage tooltips. - coverageNotice's parameter `tr` (the time range) is renamed `range` so it no longer shadows the translation function. - Data labels (day names, hop filter nouns, link tiers, stat cards) stay English and are translated where shown. - test-coverage-gate's sandbox gets a pass-through tr. - pt-BR: 136 new entries; 100% of the strings in code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Channels page routed through tr()/trn(): sidebar (sections, catalogue, row badges, share/remove/colour controls, unread counts), Add Channel modal (generate PSK, add private key, scan QR, monitor hashtag, propose for everyone), Share modal, message view (headers with counts, loading/empty/error/wrong-key states, message meta, scope chip), sender tooltip and node panel, add/remove/share status messages. Also translated: QR rendering and camera scan (channel-qr.js), colour picker, and channel proposals (validation messages, statuses, list). - English output is unchanged (e.g. the "2-bytes" hash-size label). - The 'Encrypted — click to decrypt' sentinel stays data: map.js compares it and it is never displayed. - PRIVATE_CHANNEL_LABEL, proposal MSG/STATUS_LABELS stay English data, translated where shown. - emoji-allowlist: channel-qr.js comment moved from line 133 to 135. - Three source-check tests accept the tr() forms; two sandboxes get a pass-through tr/trn. - pt-BR: 133 new entries; 100% of the strings in code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tools landing (app.js), Path Inspector (input help, validation messages, results table, per-hop evidence, stats) and Trace Viewer (search, summary stats, path graph and its legend, propagation timeline, empty/error states), plus the unknown-route page. - validatePrefixes() returns translated messages; map.js's inspector pane already passes them through tr(). - test-xss-escape-sinks: the TRACE-1 template gets a pass-through tr and the unknown-route check accepts the tr() form. - pt-BR: 30 new entries; 100% of the strings in code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wrap user-visible strings in tr()/trn() across observers.js, observer-detail.js, mqtt-status-panel.js, observer-airtime.js and compare.js; add 136 pt-BR entries. English output is unchanged. The list error message now escapes e.message. Source-reading tests (XSS sinks, headings, firmware columns, healthStatus) accept the tr() form. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wrap user-visible strings in tr()/trn() across all 17 Analytics tabs in analytics.js and add 516 pt-BR entries. English output is unchanged except "0 hop" now reads "0 hops". Byte-size labels use real plurals in pt-BR. Error messages from subpath loads are now escaped. The repeater dashboard and scatter tests pass tr/trn into their sandboxes and accept the tr() form of the tab labels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wrap user-visible strings in scope-audit.js in tr()/trn(), covering the declared and transport views, the chip and caveat tooltips, the empty state and the search counts. Add 92 pt-BR entries. English output is unchanged; markup inside sentences enters through placeholders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wrap the Performance Dashboard's headings, card labels, table headers, buttons and messages in tr(); add 47 pt-BR entries. Runtime terms (Goroutines, GC, Heap, RSS, WAL, Syscalls, Event Loop, P50/P95) stay in English. English output is unchanged. The error message is now escaped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wrap the Audio Lab's controls, sections, table headers, sound-mapping explanations and messages in tr()/trn(); add 47 pt-BR entries, including the four scale names. Synth terms (MIDI, BPM, Hz, oscillator type names) and the formulas stay as they are. English output is unchanged. The error message is now escaped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wrap the customizer's tabs, field labels, color and hint tables, theme presets, colorblind options, display settings, home editor, GeoFilter tab and modal, export tab and status messages in tr()/trn(). Label tables built at load stay English and are translated where they are shown. Add 233 pt-BR entries. Operator content (home steps, FAQ, footer links) is not translated. English output is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wrap the opt-in coverage page's title, subtitle, layer and gap toggles, legends, cell tooltips, leaderboard headers and messages in tr()/trn(); add 35 pt-BR entries. The two leaderboard column tooltips are Dutch upstream; they stay as they are in English and get pt-BR entries keyed on the Dutch text. The escape test passes tr into its sandbox. English output is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thank you for this, and for doing the pt-BR translation itself, not only the plumbing. We are keeping the UI English-only for now, so I am closing this PR. The reason is maintenance, not the quality of the work. Once a translation layer is in, every new feature, popup and tooltip needs a key and a translation before the language stays complete. Today the frontend changes in most merged PRs, and nobody is set up to keep a second language in step with that. A partially translated UI is harder to use than an English one. When we closed #1550 we said we would reopen if a translator volunteered. You did, so to be clear about what changed: the blocker is the per-feature cost for every contributor, not a missing translator. Things that would change this decision, for later:
Your branch stays available. If those conditions are met, it is the starting point. |
…s columns (#2280) ## Problem The RX coverage leaderboard has two column tooltips in Dutch, since #1728 (`public/rx-coverage.js:340` and `:342`). The rest of the UI is English. ## Change - **score:** now says what `cmd/server/rx_dashboard.go:362-366` computes. Each ~150 m cell (`leaderboardHexRes = 13`, `rx_dashboard.go:264`) adds 1 divided by the number of observers that heard something there, so an unshared cell counts 1 and a cell shared with 3 others counts 0.25. - **cells:** a plain translation. No logic changes, so there is no new test. `sh test-all.sh` passes. Not done: no browser check of the tooltips. Noticed in the review of #2269, which had picked the Dutch text up as an English key. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up to #1550. That issue was closed because there was no i18n layer and no one to own a locale. This PR adds the layer, translates every page to Brazilian Portuguese (pt-BR), and I'm volunteering to maintain the pt-BR locale.
English users see no change: English stays the source text, and any missing translation falls back to it.
How it works
public/i18n.jsdefineswindow.I18n,tr(text, params)andtrn(count, singular, plural, params).tr('…'). Nothing is extracted into IDs, so an English-only contributor's workflow barely changes.public/locales/<code>.json(flat"English": "translation"). Plural forms are{ "one": …, "other": … }objects, resolved withIntl.PluralRules.data-i18n,data-i18n-title,data-i18n-aria-labelanddata-i18n-placeholder, applied when the dictionary loads.app.jswaits for the dictionary (max 3 s) before the first route renders, so there is no flash of English.localStorage["meshcore-lang"];autofollows the browser. Changing it reloads the page.<strong>, codes and node names go in through{placeholders}. Values from nodes or MQTT are escaped by the caller before they are passed in, as before.What is deliberately not translated
Following the triage notes in #1550:
ADVERT,GRP_TXT,TXT_MSG,TRACE,FLOOD,TRANSPORT_FLOOD, …hashRegions,pathTrust.minHashBytesForMapping, …)Safety rails
tests/unit/test-i18n.js(intest-all.sh) checks that every listed language has a dictionary and vice versa. For every entry it checks that the translation keeps the English key's{placeholders}and links and adds no HTML tags. It also covers language detection, the English fallback (missing entry, failed fetch, 404), plural rules, the DOM attributes and the language switch.node scripts/i18n-report.js pt-BRlists strings in the code with no translation and dictionary keys no longer used. pt-BR is at 2005/2005.tr('…')form, not loosened. One visible difference: a counter that showed "0 hop" now reads "0 hops".e.messageintoinnerHTMLare now escaped, since they were touched anyway.Adding a language
public/locales/pt-BR.jsontopublic/locales/<code>.jsonand translate the values. Keys must stay exactly as they are.LANGUAGESinpublic/i18n.js, for example{ code: 'de', name: 'Deutsch' }.node tests/unit/test-i18n.jsandnode scripts/i18n-report.js <code>.The flat JSON format can be loaded directly into Weblate or Crowdin later, as suggested in #1550.
Commits
One commit per page, each with its own dictionary entries and test updates, so it can be reviewed page by page:
test-i18n.js,i18n-report.js)Testing
test-all.shpasses, excepttest-preflight-xss-gate.js, which needs Python; my dev machine doesn't have it, so please let CI confirm that one.tr()is a single dictionary lookup plus placeholder substitution, called while HTML strings are built. It adds no requests and no loops; the dictionary is one ~190 KB JSON (before gzip), fetched once with the usual cache buster. In English nothing is fetched at all.Not in this PR
cmd/server/settings_allowlist.go).config.json.i18n-report.jsis the basis for one; happy to add it as a follow-up if wanted.🤖 Generated with Claude Code