Files
meshtastic-web/apps/web/CONTRIBUTING_I18N_DEVELOPER_GUIDE.md
Ben MeadorsandGitHub bbe9a0d5cd feat(protobufs): sync to firmware-current and consume workspace package (#1097)
* feat(protobufs): sync to firmware-current and consume workspace package

Sync the vendored .proto sources to firmware-current (v2.7.25+48), regenerate the v2 TS bindings, and consume the workspace @meshtastic/protobufs (workspace:*) in place of the stale JSR 2.7.20 — finishing the monorepo migration (core was already workspace:*).

Includes the one required breaking-change fix: admin nodedb_reset changed int32 to bool, so resetNodes() now sends value: true.

* build(protobufs): vendor generated bindings for workspace consumers

The package is consumed via workspace:* — its exports point at the TS source, which imports ./dist/meshtastic/*_pb.ts — so the generated output must exist at build time. CI builds web/core with no codegen step and the runners have no buf CLI, so the bindings are vendored here (kept gitignored; lint/format skip them). Regenerate with: pnpm --filter @meshtastic/protobufs gen

* fix(protobufs): clean script removes the actual generated output dir

buf writes bindings to packages/ts/dist, but clean was removing a non-existent root dist — so it never cleaned stale output. Addresses Copilot review feedback.

* refactor: move web app packages/web -> apps/web

Aligns the web app with the apps/web layout (matching the Vercel web-test Root Directory and the SDK-migration direction). Pure directory move plus root config: pnpm-workspace (adds apps/*), vitest projects, root tsconfig reference, and the pr/release-web/nightly workflows. vercel.json moved with the app. Build + 36 validation tests green.

* feat: config fields, module pages, key verification, telemetry capture

Incorporates the firmware-current feature work onto the protobuf foundation: new config fields (Display message bubbles; LoRa fem_lna_mode + serial_hal_only; Telemetry air_quality_screen_enabled); 4 new ModuleConfig pages (TrafficManagement, StatusMessage, TAK, RemoteHardware); the manual Key Verification flow (sendKeyVerification + ClientNotificationDialog stages + Verify Key button); live telemetry capture (nodeDB addDeviceMetrics) and admin hardening (toggleMutedNode, graceful PortNum default); plus the sdk-preview ConfigEditor demo and store/config tests. Build + lint + format + 131 tests green.

* chore: drop #1062 (unsaved-change-detection) to match upstream revert

#1062 was merged to main by accident (per @danditomaso) and is being reverted. Reverse-applied its diff here via 3-way so #1097 stays consistent with where main is headed, while keeping the feature changes layered on the same files (deviceStore/changeRegistry). Build + 131 tests + lint + format green.

* fix(nodes): clean up SNR display in node table and map popup

SNR is a ratio measured in dB, not dBm (which is absolute power); the
node table and map popup both mislabeled it and crammed three values
together: '0dBm/50%/50raw'. The trailing '%/raw' pair was the same
heuristic ((snr+10)*5) shown twice — once clamped, once not.

Render SNR in dB rounded to one decimal, color-coded by a 0-100%
signal-quality heuristic (green/yellow/red), with the quality percentage
as a muted secondary. Drop the redundant raw value. Adds unit.db; this
matches the existing SNRTooltip, which already renders dB.
2026-06-15 13:23:28 -05:00

3.5 KiB

i18n Developer Guide

When developing new components, all user-facing text must be added as an i18n key and rendered using our translation functions. This ensures your UI can be translated into multiple languages.

Adding New i18n Keys

Search Before Creating

Before adding a new key, please perform a quick search to see if one that fits your needs already exists. Many common labels like "Save," "Cancel," "Name," "Description," "Loading...," or "Error" are likely already present, especially in the common.json namespace. Reusing existing keys prevents duplication and ensures consistency across the application. Using your code editor's search function across the /i18n/locales/en/ directory is an effective way to do this.

Key Naming and Structure Rules

To maintain consistency and ease of use, please adhere to the following rules when creating new keys in the JSON files.

  • Keys are camelCase: exampleKey, anotherExampleKey.
  • Avoid Deep Nesting: One or two levels of nesting are acceptable for grouping related keys (e.g., all labels for a specific menu). However, nesting deeper than two levels should be avoided to maintain readability and ease of use.
    • Good (1 level):
      "buttons": {
        "save": "Save",
        "cancel": "Cancel"
      }
      
    • Acceptable (2 levels):
      "userMenu": {
        "items": {
          "profile": "Profile",
          "settings": "Settings"
        }
      }
      
    • Avoid (3+ levels):
      "userMenu": {
        "items": {
          "actions": {
            "viewProfile": "View Profile"
          }
        }
      }
      
  • Organize for Retrieval, Not UI Layout: Keys should be named logically for easy retrieval, not to mirror the layout of your component.

Namespace Rules

We use namespaces to organize keys. All source keys are added to the English (en) files located at /packages/web/public/i18n/locales/en/. Place your new keys in the appropriate file based on these rules:

  • common.json:
    • All button labels (save, cancel, submit, etc.).
    • Any text that is repeated and used throughout the application (e.g., "Loading...", "Error").
  • ui.json:
    • Labels and text specific to a distinct UI element or view that isn't a dialog or a config page.
  • dialog.json:
    • All text specific to modal dialogs (titles, body text, prompts).
  • messages.json:
    • Text specifically related to the messaging interface.
  • deviceConfig.json & moduleConfig.json:
    • Labels and descriptions for the settings on the Device and Module configuration pages.

Using i18n Keys in Components

We use the useTranslation hook from react-i18next to access the translation function, t.

Default Namespaces

Our i18next configuration has fallback namespaces configured which includes common, ui, and dialog. This means you do not need to explicitly specify these namespaces when calling the hook. The system will automatically check these files for your key.

For any keys in common.json, ui.json, or dialog.json, you can instantiate the hook simply:

import { useTranslation } from "react-i18next";

// In your component
const { t } = useTranslation(["messages"]);

// Usage
return <p>{t("someMessageLabel")}</p>;

You can also specify the namespace on a per-call basis using the options object. This is useful if a component primarily uses a default namespace but needs a single key from another.

const { t } = useTranslation();

return <p>{t("someMessageLabel", { ns: "messages" })}</p>;