Compare commits

...
12 Commits
Author SHA1 Message Date
Kaj SchittecatandClaude Opus 4.8 0731b5ff67 touch: beta_22 (test) — trace-ping signal probe + companion contact-refresh (#73)
Signal probe: replace the beta_21 zero-hop advert (which nothing replies to, so the
signal never updated) with a directed TRACE ping to the nearest reachable repeater.
It retransmits — a real reply we measure — without flooding the mesh. New
MyMesh::uiSendSignalProbe() picks the shortest-path repeater; the reply is captured
silently in onTraceRecv via _ui_sig_probe_tag. Falls back to a zero-hop advert when
no repeater is known. Thanks to Tarmo for the insight.

Contacts (#73 part A): a contact discovered while a companion app is connected over
BLE now refreshes the device's own Contacts list + notification. onDiscoveredContact
fired the UI notify only in the standalone branch — move it out so it always fires,
and flag the list dirty for UITask::loop to rebuild on the LVGL thread.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 08:46:56 +02:00
b5e428734c release: credit Samuel Coustet (@samuelcoustet, #71) for the MQTT bridge
The beta_21 MQTT bridge is based on @samuelcoustet's PR #71, reworked for privacy
(consent gate, direct messages off by default, payload encryption). Record the
attribution in the release notes + the version.json "what's new".

Co-authored-by: Samuel Coustet <257299705+samuelcoustet@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:28:31 +02:00
Kaj SchittecatandClaude Opus 4.8 18904c9a5d deploy(nginx): serve the TEST/BETA channel paths on firmware.wadamesh.com
The two-channel rollout added releases/BETA + latest-beta, but the firmware
vhost only had exact-match handling for the old stable paths. Add:
- location = /releases/BETA  -> serves releases/BETA/index.json at the no-slash
  path the device requests when 'Get test builds (beta)' is on (mirrors TOUCH).
- location /latest-beta/      -> the beta web-flasher feed (was hitting the 404
  catch-all).

Stable paths and all bins under /releases/ were already correct; this only wires
up the new beta directory-index + feed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 17:47:46 +02:00
Kaj Schittecat 39a4db8f03 chore: refresh Mesh America catalog for beta_21 2026-06-30 17:36:58 +02:00
Kaj SchittecatandClaude Opus 4.8 03e50b0609 touch: beta_21 — first stable build + test channel, MQTT bridge, contacts & signal-probe fixes
New: stable/test update channels with an opt-in "Get test builds (beta)" toggle on
OTA devices (switches both the update check and what you install); experimental
opt-in MQTT bridge (consent-gated, payload encryption, direct messages off by
default); Portugal (Narrow) region preset (#74); saved-contacts counter in the
Contacts overflow menu (#72).

Fixed: the signal probe is now strictly zero-hop so a repeater can't re-flood it;
the contact list sorts before capping so the most relevant contacts show, with a
"+N more — search to narrow" footer (#73); the blocked-users list opened from
Contacts shows the two-line title bar with a back button; the Contacts overflow
popup fits the screen again.

Also lands the two-channel release tooling (scripts/release.sh stable/beta modes +
gen-flasher-meta channel arg), the docs site, and the MQTT reference decryptor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 17:34:11 +02:00
Kaj SchittecatandClaude Opus 4.8 d75db73fc8 release: credit Yazutsu (#66) for the WAV notification sounds in beta_20 notes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 23:50:46 +02:00
Kaj Schittecat 56cf91ef5b chore: refresh Mesh America catalog for beta_20 2026-06-28 23:36:03 +02:00
Kaj SchittecatandClaude Opus 4.8 b4c941f954 touch: beta_20 - spectrum analyzer, custom sounds, saved Wi-Fi, V4 high-gain RX, flood-scope + channel-send fixes
New: Spectrum analyzer app; custom notification sounds (SD WAV per event);
saved Wi-Fi networks with auto-join; unified chat settings screen; per-channel
mute; Heltec V4.3 high-gain receiver (FEM LNA) toggle; Apps button on the V4
home screen; Blocked list reachable from Contacts + chat settings; Portuguese
(BR) UI language.

Fixed: touch-screen flood adverts now region-scoped so region repeaters relay
them (#68); channel sends matched by name so a message can't go out on the
wrong channel's key; opt-in "scope direct messages to region" so room-server
logins work through a region-only path (#64); Wi-Fi scan no longer cancelled
by the reconnect retry (empty SSID list).

Under the hood: flood-scope rework (channel vs direct floods); Spectrum owns
the SX1262 while open and restores mesh RX on exit; wifiScanIsActive() gate;
FEM KCT8103L LNA wired; TouchPrefsStore migrations for the new prefs. Adds the
previously-untracked device_caps.h.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 23:33:44 +02:00
Kaj Schittecat 1e00ac3876 chore: refresh Mesh America catalog for beta_19 2026-06-27 15:09:40 +02:00
Kaj SchittecatandClaude Opus 4.8 2b45225e0b touch: beta_19 — scroll-popup crash fix, Radio airtime/telemetry, V4 Home fit
- Fix LVGL scroll-momentum use-after-free reboot when a scrollable overlay
  (emoji / quick-reply / add-channel / channel sheets, Wi-Fi scan, pickers,
  app drawer) is freed mid-throw: new closeScrollOverlay() helper
  (lv_indev_wait_release + lv_obj_del_async). Root-caused from field beta_18
  coredumps.
- Radio & Mesh settings: airtime / duty-cycle readout + "Answer telemetry
  requests" toggle, grouped into RADIO / MESH / SIGNAL sections.
- Accent popup dismissed on Enter-to-send (T-Deck physical keyboard).
- Heltec V4: Expansion-Kit Home env line + chart flow below the stats line
  so they fit the 240x320 screen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:08:03 +02:00
Kaj SchittecatandClaude Opus 4.8 dad7d295dc touch: airtime/duty-cycle readout + answer-telemetry toggle in Radio settings
Two firmware-only additions surfacing capability already in the stack:

- Airtime / duty-cycle readout: the radio modal exposed airtime_factor only as a raw
  'AF' number. Add a derived line — '≈ N% max duty cycle · ~M ms / 40-byte packet' —
  computing duty% from MeshCore's af=(100/duty)-1 and the per-packet time-on-air from
  the Semtech closed-form (new loraToaMs helper). Lets EU-868 users see compliance at
  a glance with no measurement.
- Answer telemetry requests toggle: MeshCore already gates telemetry responses on
  _prefs.telemetry_mode_base/env (0=DENY/2=ALLOW_ALL) but it wasn't in the touch UI.
  Add a switch (new UITask::setTelemetryAllow) that sets battery+environment modes;
  location telemetry keeps its own separate setting so a simple 'allow' can't leak
  position.

(RX boosted gain was already a switch in Experimental settings; per-packet RSSI/SNR
is already in the Monitor; region presets already bundle SF/BW/CR — so those research
items were already shipped and not re-added.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 10:21:12 +02:00
Kaj SchittecatandClaude Opus 4.8 2d49fb69ec site: add Tanmatsu support; Telegram -> Discord
- List the Tanmatsu (ESP32-P4) in the hero + meta description.
- Add a Tanmatsu box to the standalone installer: no USB flashing — install from the Tanmatsu app store on-device — plus a link to Nicolai Electronics' shop. Boards grid now auto-fits 3 cards.
- Replace the Telegram floating button with Discord (discord.gg/T8X2UaQ7Sm).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:45:47 +02:00
59 changed files with 5831 additions and 1492 deletions

No files matched your search

+94
View File
@@ -0,0 +1,94 @@
# WadaMesh release channels — Stable + Test
Goal: ship **test** builds to the community fast, let them shake out, then **promote**
a proven test build to **stable**. What ships to stable is exactly what was tested
(promote the binary, don't rebuild).
## Two concepts, kept separate
- **Branches** = where code lives.
- **Channels** = what a device / the flasher pulls (Stable vs Beta).
## Branches
- **`main`** — active line. All work lands here; every *test* release is cut from `main`. (Unchanged from today.)
- **`stable`** — promoted line. Moves only when a test build is blessed. Starts at the **beta_20** commit.
- Promote = `git branch -f stable <blessed beta commit>` + push, then run the stable-promotion publish.
## Versioning / tags
- **Test builds:** `beta_N` (immutable, incrementing), marked **GitHub pre-release**. Also mirrored to a rolling **`beta-latest`** pre-release (overwritten each time) for the Launcher beta sync.
- **Stable:** the blessed `beta_N`, marked **GitHub Latest**. Keep the `beta_N` id for now; friendly `vX.Y` names are an optional later polish (would need a rebuild-with-tag per promotion).
- First stable = **beta_20**.
## Distribution matrix
| Target | Stable | Test / Beta |
|---|---|---|
| GitHub release | blessed `beta_N`, marked **Latest** | every `beta_N`, **pre-release** + rolling `beta-latest` |
| VPS archive | `releases/TOUCH/<beta_N>/` (only blessed lands here) | `releases/BETA/<beta_N>/` |
| VPS channel feed | `stable/` (version.json + bins) | `beta/` (version.json + bins) |
| Flasher (flasher.wadamesh.com) | default | "Beta / testing" toggle |
| On-device update check | stable feed (legacy firmware reads `releases/TOUCH`) | beta feed (Phase 2 setting) |
| LauncherHub (bmorcelli, T-Deck) | existing entry (follows GitHub Latest) | new "WadaMesh (Beta)" entry (Phase 3) |
| Tanmatsu app-store | promote on stable only | (skipped for tests) |
## The linchpin: keeping field devices on stable
Legacy firmware's update check scans `releases/TOUCH/` for the highest `beta_N`. So:
- **Test builds archive under `releases/BETA/`** — invisible to legacy firmware.
- **Promotion copies the blessed build into `releases/TOUCH/`** — legacy devices then see it and show "update available," landing everyone on stable.
Net effect: existing devices quietly settle on **stable (beta_20)** and stop chasing
every beta; beta testers opt in via the flasher Beta toggle (or, later, the on-device
Beta channel).
## Two workflows
### Cut a TEST release (fast, frequent) — from `main`
1. `beta_N` = next number.
2. Build both touch envs with the tag embedded.
3. GitHub: create `beta_N` release **`--prerelease`** with the 7 assets; also refresh the rolling `beta-latest` pre-release.
4. VPS: rsync to `releases/BETA/<beta_N>/` + refresh the `beta/` feed.
5. Flasher Beta toggle now serves it. Skip LauncherHub / Tanmatsu / site. (~minutes)
### Promote to STABLE (deliberate, when proven)
1. `git branch -f stable <beta_N commit>` + push.
2. GitHub: mark that `beta_N` release **Latest** (clear pre-release).
3. VPS: copy `releases/BETA/<beta_N>/` → `releases/TOUCH/<beta_N>/`; refresh `stable/` + `latest/` (flasher stable) feeds.
4. Full fan-out: LauncherHub follows automatically; push the Tanmatsu store; bump the site "what's new."
## Phases
### Phase 1 — channel split, no firmware change (do now)
- [ ] Create + push `stable` branch at beta_20.
- [ ] VPS: stand up `stable/`, `beta/`, `releases/BETA/`; point `latest/` (flasher stable) at beta_20; freeze `releases/TOUCH/` at beta_20.
- [ ] GitHub: ensure beta_20 is **Latest**; future betas → `--prerelease`.
- [ ] Flasher: Stable (default) / Beta toggle + the beta manifest.
- [ ] `release.sh` / `gen-flasher-meta.py`: add `--channel beta|stable` producing the right feed/assets + the rolling `beta-latest`.
- [ ] Rewrite the `wadamesh-release` skill into the two-workflow form.
### Phase 2 — on-device channel selector (ships in a beta, then promoted)
- [ ] Settings → **Update channel: Stable / Beta**, default Stable (persisted).
- [ ] `verchkFetchLatest` reads the `stable/` or `beta/` feed per the setting.
- [ ] Beta devices get the new-beta banner automatically; stable devices never see betas.
### Phase 3 — LauncherHub beta listing (PR to bmorcelli, when wanted)
- [ ] Rolling `beta-latest` already produced in Phase 1.
- [ ] PR `bmorcelli/M5Stack-json-fw`: add a `wadamesh_beta.py` (mirror `bruce_beta.py`, track `beta-latest`) + a `t-deck.json` (Beta) entry.
- [ ] Result: T-Deck Launcher shows **WadaMesh** + **WadaMesh (Beta)**, both auto-synced.
- Until then: T-Deck Launcher beta testers side-load the beta `.bin` manually (it is a T-Deck-only gap; V4 uses the flasher).
## Where current work lands
- The uncommitted post-beta_20 work (auto-advert GUI, message-flash, docs, RAM pass)
plus the hardened **MQTT-privacy** branch → commit to `main` → cut as **beta_21**,
the first TEST release in this model. Community tests it; promote when proven.
- This is exactly the model that handles PR #71: land the reviewed/hardened pieces on
`main`, ship as a beta, promote later.
## Decisions
- **Locked:** `main` + `stable`; keep `beta_N` ids with a Stable/Beta channel label; Phase 1 now, Phase 2 in beta_21, Phase 3 as a fast-follow.
- **Open (reversible):** whether stable later gets friendly `vX.Y` names (needs a rebuild-with-tag per promotion).
+8 -8
View File
@@ -16,19 +16,19 @@
"role": "gui",
"title": "Touch UI",
"version": {
"beta_18": {
"notes": "A topographic map style: turn on Map -> Options -> \"Topographic map\" for terrain-relief tiles (OpenStreetMap stays the default). Thanks to Samuel Coustet for the proxy work (#52).\nNew emoji in the picker: soccer ball, american football, moai, and the transgender flag.\nContacts can now sort ascending or descending, and there's a new \"Nearest first\" sort that orders contacts by distance from you (contacts without a shared location are left out).\nThe Discovered list gets the same sorting — by most-recently-heard, name, or distance — from a new sort button in its header.\nThe Chats header is tidied up: the add / mark-all-read / share-contact buttons are grouped together on the left, and sharing your contact now uses a proper QR icon.\nThe USB/PC companion connection no longer drops or throws \"bad state\" errors mid-session. The same fix also clears up repeater admin mode disconnecting and not being able to log in to member rooms.\nMap tiles along the edges and corners of the view now fill in automatically as you pan, instead of needing a manual \"reload tiles\".\nReclaimed about 41 KB of memory by moving a large internal buffer to PSRAM — this fixes the Heltec V4 running out of RAM and rebooting when it loaded map tiles, and leaves more headroom on every board.\nThe map's \"Show link lines\" toggle is now remembered across reboots. Thanks to Yazutsu (#61).\nSettings category names (Bluetooth, Radio & Mesh, Quick replies, ...) now fit on one line instead of wrapping or splitting mid-word.\nTurning keyboard navigation on or off no longer snaps the settings list back to the top.\nHeltec V4: the node name in the status bar no longer covers the clock.\nT-Deck: the keyboard backlight no longer gets stuck on after flashing when it's set to Off.\nCrash reporting is more reliable (it only prompts after an actual crash, not on every restart), and there are fewer stray reboots while saving settings.\nTanmatsu: changing the LoRa radio settings (frequency and so on) now takes effect immediately, without needing a reboot, and the app's settings now drive the radio.\nEverything from beta_16 (and the Tanmatsu's beta_17) is included.",
"beta_21": {
"notes": "Stable and test update channels. beta_21 is the first stable build. On devices that update over Wi-Fi there is now a \"Get test builds (beta)\" switch on the About screen: leave it off to stay on stable releases, or turn it on to receive test builds earlier — it switches both the \"update available\" check and the firmware you install to the test channel. You can go back to stable at any time.\nMQTT bridge (highly experimental, off by default). An opt-in bridge that forwards the messages your node receives to an MQTT broker of your choice, so you can log your mesh or wire it into other tools. It is gated behind a privacy consent you have to tick, direct messages are excluded unless you deliberately enable them, and the payload can be sealed with an encryption key so only you can read it. Use a broker you control, never a public one. This is experimental and may change.\nPortugal (Narrow) region preset, so Portuguese users can pick the local defaults in one tap. (Issue #74, requested by Salgueiron.)\nSaved-contacts counter. The Contacts overflow menu now shows how many contacts you have saved out of the maximum it can hold (for example 42 / 2000). (Issue #72, asked by Buko84.)\nThe signal probe never travels past the first node now. The \"auto-discover\" signal pulse, and the probe behind the Signal popup, are sent strictly zero-hop, so a repeater can no longer re-flood them across the mesh. The probe only measures the link to whatever hears it first, which is what it was always meant to do.\nYour most relevant contacts now show when you have a lot of them. The on-screen contact list is capped for performance, but it was trimming the list before sorting it, so some contacts could drop out of view. It now sorts first, shows the closest matches, and adds a \"+N more — search to narrow the list\" line at the bottom. (Issue #73, reported by 1nerdherder.)\nThe blocked-users list opened from Contacts now shows the proper two-line title bar with a back button, instead of an unlabelled screen.\nThe Contacts overflow popup fits the screen again after the saved-contacts counter was added.\nEverything from beta_20 is included.",
"files": [
{
"type": "flash-wipe",
"name": "wadamesh-heltec-v4-tft-merged.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_18/wadamesh-heltec-v4-tft-merged.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_21/wadamesh-heltec-v4-tft-merged.bin",
"title": "Full install (bootloader + firmware)"
},
{
"type": "flash-update",
"name": "wadamesh-heltec-v4-tft.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_18/wadamesh-heltec-v4-tft.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_21/wadamesh-heltec-v4-tft.bin",
"title": "Update (app only)"
}
]
@@ -48,19 +48,19 @@
"role": "gui",
"title": "Touch UI",
"version": {
"beta_18": {
"notes": "A topographic map style: turn on Map -> Options -> \"Topographic map\" for terrain-relief tiles (OpenStreetMap stays the default). Thanks to Samuel Coustet for the proxy work (#52).\nNew emoji in the picker: soccer ball, american football, moai, and the transgender flag.\nContacts can now sort ascending or descending, and there's a new \"Nearest first\" sort that orders contacts by distance from you (contacts without a shared location are left out).\nThe Discovered list gets the same sorting — by most-recently-heard, name, or distance — from a new sort button in its header.\nThe Chats header is tidied up: the add / mark-all-read / share-contact buttons are grouped together on the left, and sharing your contact now uses a proper QR icon.\nThe USB/PC companion connection no longer drops or throws \"bad state\" errors mid-session. The same fix also clears up repeater admin mode disconnecting and not being able to log in to member rooms.\nMap tiles along the edges and corners of the view now fill in automatically as you pan, instead of needing a manual \"reload tiles\".\nReclaimed about 41 KB of memory by moving a large internal buffer to PSRAM — this fixes the Heltec V4 running out of RAM and rebooting when it loaded map tiles, and leaves more headroom on every board.\nThe map's \"Show link lines\" toggle is now remembered across reboots. Thanks to Yazutsu (#61).\nSettings category names (Bluetooth, Radio & Mesh, Quick replies, ...) now fit on one line instead of wrapping or splitting mid-word.\nTurning keyboard navigation on or off no longer snaps the settings list back to the top.\nHeltec V4: the node name in the status bar no longer covers the clock.\nT-Deck: the keyboard backlight no longer gets stuck on after flashing when it's set to Off.\nCrash reporting is more reliable (it only prompts after an actual crash, not on every restart), and there are fewer stray reboots while saving settings.\nTanmatsu: changing the LoRa radio settings (frequency and so on) now takes effect immediately, without needing a reboot, and the app's settings now drive the radio.\nEverything from beta_16 (and the Tanmatsu's beta_17) is included.",
"beta_21": {
"notes": "Stable and test update channels. beta_21 is the first stable build. On devices that update over Wi-Fi there is now a \"Get test builds (beta)\" switch on the About screen: leave it off to stay on stable releases, or turn it on to receive test builds earlier — it switches both the \"update available\" check and the firmware you install to the test channel. You can go back to stable at any time.\nMQTT bridge (highly experimental, off by default). An opt-in bridge that forwards the messages your node receives to an MQTT broker of your choice, so you can log your mesh or wire it into other tools. It is gated behind a privacy consent you have to tick, direct messages are excluded unless you deliberately enable them, and the payload can be sealed with an encryption key so only you can read it. Use a broker you control, never a public one. This is experimental and may change.\nPortugal (Narrow) region preset, so Portuguese users can pick the local defaults in one tap. (Issue #74, requested by Salgueiron.)\nSaved-contacts counter. The Contacts overflow menu now shows how many contacts you have saved out of the maximum it can hold (for example 42 / 2000). (Issue #72, asked by Buko84.)\nThe signal probe never travels past the first node now. The \"auto-discover\" signal pulse, and the probe behind the Signal popup, are sent strictly zero-hop, so a repeater can no longer re-flood them across the mesh. The probe only measures the link to whatever hears it first, which is what it was always meant to do.\nYour most relevant contacts now show when you have a lot of them. The on-screen contact list is capped for performance, but it was trimming the list before sorting it, so some contacts could drop out of view. It now sorts first, shows the closest matches, and adds a \"+N more — search to narrow the list\" line at the bottom. (Issue #73, reported by 1nerdherder.)\nThe blocked-users list opened from Contacts now shows the proper two-line title bar with a back button, instead of an unlabelled screen.\nThe Contacts overflow popup fits the screen again after the saved-contacts counter was added.\nEverything from beta_20 is included.",
"files": [
{
"type": "flash-wipe",
"name": "wadamesh-tdeck-merged.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_18/wadamesh-tdeck-merged.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_21/wadamesh-tdeck-merged.bin",
"title": "Full install (bootloader + firmware)"
},
{
"type": "flash-update",
"name": "wadamesh-tdeck.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_18/wadamesh-tdeck.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_21/wadamesh-tdeck.bin",
"title": "Update (app only)"
}
]
+21 -1
View File
@@ -25,12 +25,22 @@ server {
root /srv/wadamesh/firmware;
# The update-check feed — served at the no-slash path the firmware requests.
# STABLE channel (the default on-device update check).
location = /releases/TOUCH {
default_type application/json;
alias /srv/wadamesh/firmware/releases/TOUCH/index.json;
add_header Cache-Control "public, max-age=60";
}
# Same, for the opt-in TEST/BETA channel — what a device requests when the
# "Get test builds (beta)" switch is on. Without this exact-match the no-slash
# path 301-redirects to a dir with no index and the beta check comes back empty.
location = /releases/BETA {
default_type application/json;
alias /srv/wadamesh/firmware/releases/BETA/index.json;
add_header Cache-Control "public, max-age=60";
}
# Bins + everything else under /releases/. CORS so the browser flasher
# (flasher.wadamesh.com) can fetch the images cross-origin.
location /releases/ {
@@ -41,7 +51,7 @@ server {
}
# Rolling "latest" merged images — the web flasher's manifests point here so
# they always install the newest build (release.sh refreshes this dir).
# they always install the newest build (release.sh refreshes this dir). STABLE.
location /latest/ {
autoindex off;
types { application/octet-stream bin; }
@@ -49,5 +59,15 @@ server {
add_header Access-Control-Allow-Origin "*";
}
# Rolling "latest" for the TEST/BETA channel — the beta web-flasher feed
# (version.json + manifests + rolling bins). Without this it falls to the
# catch-all 404 and the flasher's beta toggle can't load metadata.
location /latest-beta/ {
autoindex off;
types { application/octet-stream bin; }
add_header Cache-Control "public, max-age=300";
add_header Access-Control-Allow-Origin "*";
}
location / { return 404; }
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+488
View File
@@ -0,0 +1,488 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WADAMESH — Documentation</title>
<meta name="description" content="User guide for the WADAMESH touch firmware on the LilyGo T-Deck: home, chat, contacts, map, settings and the built-in apps. Real device screenshots.">
<link rel="icon" href="wadamesh-badge.svg">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@600;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#ffffff; --bg2:#f4f6f2; --ink:#15181e; --mut:#5d6770;
--line:#e5e8e2; --teal:#15b6a6; --teal-ink:#0e8d80;
color-scheme:light;
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{font:16px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Inter,sans-serif; background:var(--bg); color:var(--ink); -webkit-font-smoothing:antialiased}
a{color:var(--teal-ink); text-decoration:none}
.top{position:sticky; top:0; z-index:30; display:flex; align-items:center; gap:14px; padding:12px 22px;
background:rgba(255,255,255,.88); backdrop-filter:saturate(1.2) blur(8px); border-bottom:1px solid var(--line)}
.top .word{font-weight:700; letter-spacing:2px; font-size:1.05rem; color:var(--ink)}
.top .word .t{color:var(--teal)}
.top .tag{font-family:'JetBrains Mono',monospace; font-size:.66rem; letter-spacing:.18em; text-transform:uppercase; color:var(--mut); border:1px solid var(--line); border-radius:100px; padding:4px 9px}
.top .sp{flex:1}
.top .home{display:inline-flex; align-items:center; gap:7px; color:var(--mut); font-size:.86rem; border:1px solid var(--line); border-radius:100px; padding:6px 13px; background:#fff; transition:background .15s,color .15s,border-color .15s}
.top .home:hover{color:var(--teal-ink); background:var(--bg2); border-color:#bfe6e0}
.top .home svg{width:16px;height:16px}
.shell{max-width:1200px; margin:0 auto; display:grid; grid-template-columns:220px 1fr; gap:34px; padding:0 22px}
nav.toc{position:sticky; top:64px; align-self:start; height:calc(100vh - 64px); overflow:auto; padding:26px 0 40px}
nav.toc .grp{font-family:'JetBrains Mono',monospace; font-size:.68rem; letter-spacing:.13em; text-transform:uppercase; color:var(--mut); margin:20px 0 7px}
nav.toc .grp:first-child{margin-top:0}
nav.toc a{display:block; padding:6px 12px; border-radius:9px; color:#46525c; font-size:.92rem; border-left:2px solid transparent}
nav.toc a:hover{background:var(--bg2); color:var(--ink)}
nav.toc a.on{color:var(--teal-ink); background:rgba(21,182,166,.09); border-left-color:var(--teal); font-weight:600}
main{padding:30px 0 90px; min-width:0}
.hero h1{font-size:clamp(1.9rem,4.4vw,2.6rem); letter-spacing:-.5px; font-weight:750}
.hero p{color:var(--mut); max-width:64ch; margin-top:10px; font-size:1.05rem}
.hero .meta{display:flex; gap:8px; flex-wrap:wrap; margin-top:16px}
.chip{display:inline-flex; align-items:center; gap:6px; font-size:.8rem; color:#3a444d; border:1px solid var(--line); border-radius:100px; padding:5px 11px; background:#fff}
.chip svg{width:14px;height:14px;color:var(--teal)}
section.doc{padding:36px 0; border-top:1px solid var(--line); scroll-margin-top:74px}
.doc h2{font-size:1.5rem; font-weight:730; letter-spacing:-.3px; display:flex; align-items:center; gap:10px}
.doc h2 .ico{width:30px;height:30px;border-radius:9px;display:grid;place-items:center;flex:none; background:rgba(21,182,166,.12);color:var(--teal)}
.doc h2 .ico svg{width:18px;height:18px}
.row{display:grid; grid-template-columns:minmax(0,1fr) 372px; gap:34px; align-items:start; margin-top:16px}
@media(max-width:980px){.row{grid-template-columns:1fr}}
.prose{color:#3c454d; min-width:0}
.prose p{margin:0 0 12px}
.prose h3{font-size:1.02rem; margin:18px 0 6px; color:var(--ink)}
.prose ul{margin:4px 0 12px; padding-left:18px}
.prose li{margin:4px 0}
.prose b{color:var(--ink)}
.prose code{font-family:'JetBrains Mono',monospace; font-size:.85em; background:var(--bg2); border:1px solid var(--line); border-radius:6px; padding:1px 6px}
.shot{position:sticky; top:74px}
.cap{text-align:center; font-family:'JetBrains Mono',monospace; font-size:.68rem; color:var(--mut); margin-top:10px; letter-spacing:.04em}
/* T-Deck frame: the real screenshot + a generated QWERTY keyboard (added in JS) */
.device{width:344px; margin:0 auto; background:#16191d; border-radius:16px; padding:12px 12px 9px; border:1px solid #e9ece7; box-shadow:0 24px 60px -30px rgba(21,24,30,.5)}
.device img.screen{display:block; width:320px; height:240px; border-radius:8px}
.kbd{margin:9px auto 2px; display:flex; flex-direction:column; gap:4px; width:312px}
.kr{display:flex; gap:4px; justify-content:center}
.key{flex:1; max-width:28px; height:15px; background:#23282e; border-radius:4px; display:grid; place-items:center; font:8px/1 system-ui; color:#7d8893}
.key.wide{max-width:140px; flex:4}
.gallery{display:grid; grid-template-columns:repeat(auto-fill,minmax(232px,1fr)); gap:20px; margin-top:18px}
.mini{background:#16191d; border-radius:12px; padding:7px; border:1px solid #e9ece7}
.mini img{display:block; width:100%; height:auto; border-radius:6px}
.mini .cap{margin-top:6px}
/* Settings-page detail cards: real screenshot + what the page does + its controls */
.sgrp{font-family:'JetBrains Mono',monospace; font-size:.7rem; letter-spacing:.14em; text-transform:uppercase; color:var(--mut); margin:30px 0 0; padding-top:4px}
.scards{display:grid; grid-template-columns:repeat(auto-fill,minmax(290px,1fr)); gap:20px; margin-top:13px}
.scard{border:1px solid var(--line); border-radius:14px; background:#fff; padding:13px; display:flex; flex-direction:column}
.scard .thumb{background:#16191d; border-radius:9px; padding:6px}
.scard .thumb img{display:block; width:100%; height:auto; border-radius:5px}
.scard .thumb.ph{aspect-ratio:4/3; display:grid; place-items:center; border:1px dashed #39424b; padding:0}
.scard .thumb.ph span{font-family:'JetBrains Mono',monospace; font-size:.6rem; letter-spacing:.12em; color:#7d8893; text-transform:uppercase}
.scard h3{font-size:1.04rem; margin:12px 0 2px; color:var(--ink); display:flex; align-items:center; gap:8px; flex-wrap:wrap}
.scard h3 .tag{font-family:'JetBrains Mono',monospace; font-size:.58rem; letter-spacing:.05em; text-transform:uppercase; color:var(--teal-ink); background:rgba(21,182,166,.1); border-radius:5px; padding:2px 6px; font-weight:600}
.scard .lede{color:var(--mut); font-size:.9rem; margin:2px 0 9px}
.scard ul{margin:0; padding-left:17px}
.scard li{margin:4px 0; font-size:.89rem; color:#3c454d; line-height:1.5}
.scard li b{color:var(--ink); font-weight:600}
/* Caution callout (amber) */
.callout{display:flex; gap:11px; margin-top:18px; padding:13px 15px; border-radius:11px; background:#fff7ed; border:1px solid #f6dcb8}
.callout .cico{flex:none; width:20px; height:20px; color:#c2740b; margin-top:1px}
.callout .cico svg{width:20px; height:20px}
.callout b{display:block; color:#7c4a03; margin-bottom:3px; font-size:.94rem}
.callout p{margin:0; color:#7a5320; font-size:.9rem; line-height:1.55}
footer{border-top:1px solid var(--line); padding:26px 22px; text-align:center; color:var(--mut); font-size:.85rem}
@media(max-width:980px){
.shell{grid-template-columns:1fr; gap:0}
nav.toc{position:static; height:auto; border-bottom:1px solid var(--line); padding:14px 0; display:flex; gap:6px; overflow:auto; white-space:nowrap}
nav.toc .grp{display:none} nav.toc a{border-left:0; flex:none}
.shot{position:static; margin:0 auto 6px}
}
</style>
</head>
<body>
<div class="top">
<a class="word" href="index.html">WADA<span class="t">MESH</span></a>
<span class="tag">Docs</span>
<span class="sp"></span>
<a class="home" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></svg> Back to site</a>
</div>
<div class="shell">
<nav class="toc" id="toc">
<div class="grp">Start here</div>
<a href="#overview">Overview</a>
<div class="grp">The five tabs</div>
<a href="#home">Home</a>
<a href="#chat">Chat</a>
<a href="#contacts">Contacts</a>
<a href="#map">Map</a>
<a href="#settings">Settings</a>
<div class="grp">With your phone</div>
<a href="#companion">Companion mode</a>
<div class="grp">Reference</div>
<a href="#settings-pages">Settings pages</a>
<a href="#apps">Apps</a>
</nav>
<main>
<div class="hero" id="overview">
<h1>WADAMESH user guide</h1>
<p>WADAMESH turns a LilyGo T-Deck — its landscape screen, BlackBerry-style keyboard and trackball — into a complete front-end for your MeshCore radio: chat, channels, contacts, a live map and deep radio settings, all on the device. (It also runs on the Heltec V4 and Tanmatsu.) Every screen below is a real screenshot from the device.</p>
<div class="meta">
<span class="chip"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg> No phone required</span>
<span class="chip"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12.55a11 11 0 0 1 14 0"/><path d="M8.5 16.4a5 5 0 0 1 7 0"/><line x1="12" y1="20" x2="12" y2="20"/></svg> LoRa + Wi-Fi + Bluetooth</span>
<span class="chip"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg> Keyboard + touch</span>
</div>
</div>
<section class="doc" id="home">
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/></svg></span> Home</h2>
<div class="row">
<div class="prose">
<p>The home tab is your <b>app launcher</b> — a grid of everything on the device:</p>
<ul>
<li><b>Cmdr</b> — the command centre overview.</li>
<li><b>Chats</b>, <b>Contacts</b>, <b>Map</b>, <b>Mentions</b> — jump straight to those.</li>
<li><b>Advertise</b> — announce yourself to the mesh (manual or on a timer).</li>
<li><b>Signal</b> and <b>Monitor</b> — live RX signal and a repeater-style RF activity view.</li>
<li><b>Spectrum</b> — sweep the LoRa band for interference.</li>
<li><b>Settings</b>, <b>Terminal</b> (MeshCore CLI), <b>Files</b> (microSD).</li>
</ul>
<p>The bar along the bottom is always there — it switches the five main tabs (Chat, Contacts, Home, Map, Settings) from anywhere. The top bar shows unread count, time, signal and battery.</p>
</div>
<div class="shot"><div class="device"><img class="screen" src="docs-img/home.png" alt="Home app launcher"></div><div class="cap">Home — app launcher</div></div>
</div>
</section>
<section class="doc" id="chat">
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></span> Chat</h2>
<div class="row">
<div class="prose">
<p>Every conversation in one list, newest first — both <b>channels</b> (group chats like <code>#wadamesh</code>, <code>Public</code>) and <b>direct messages</b>. An unread badge shows the count.</p>
<h3>On each row</h3>
<ul>
<li><b>Tap</b> a row to open the conversation, then type on the keyboard to reply.</li>
<li>The <b>gear</b> on the right opens that chat's settings — mute, region &amp; scope, share secret, blocked users, remove — the same screen as the cog inside a chat.</li>
</ul>
<p>The top-right buttons add a contact, mark everything read, and show your QR code.</p>
</div>
<div class="shot"><div class="device"><img class="screen" src="docs-img/chat.png" alt="Chat channel list"></div><div class="cap">Chat — channels &amp; DMs</div></div>
</div>
</section>
<section class="doc" id="contacts">
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg></span> Contacts</h2>
<div class="row">
<div class="prose">
<p>Everyone your node knows — name, type and how recently you heard them. Tap a contact to message it or open its details: signal history, a route trace, telemetry, and share/favourite options.</p>
<p>The <b>overflow menu</b> (top right) holds Search, the <b>Discovered</b> list (nodes heard but not yet added), Add contact, Auto-add settings, and the Blocked list. Auto-add can pull new nodes in from their adverts, or you can add them by hand.</p>
</div>
<div class="shot"><div class="device"><img class="screen" src="docs-img/contacts.png" alt="Contacts list"></div><div class="cap">Contacts</div></div>
</div>
</section>
<section class="doc" id="map">
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l-6 3V6l6-3 6 3 6-3v15l-6 3-6-3z"/><path d="M9 3v15"/><path d="M15 6v15"/></svg></span> Map</h2>
<div class="row">
<div class="prose">
<p>An OpenStreetMap / OpenTopoMap view with your position and your contacts plotted as markers, plus link lines from you to each node. Tiles are fetched over Wi-Fi through the wadamesh proxy and cached to flash (or microSD), so areas you've viewed work offline.</p>
<p>It also has a line-of-sight analyzer: pick a contact and it uses SRTM elevation to show whether terrain blocks the path.</p>
</div>
<div class="shot"><div class="device"><img class="screen" src="docs-img/map.png" alt="Map"></div><div class="cap">Map</div></div>
</div>
</section>
<section class="doc" id="settings">
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-2.4.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 8 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-2.4 1.65 1.65 0 0 0-1.51-1H1a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 3 8z"/></svg></span> Settings</h2>
<div class="row">
<div class="prose">
<p>A tile for each category. Tap one to open its page; the title bar carries a back chevron. The full set is in <a href="#settings-pages">Settings pages</a> below.</p>
<ul>
<li><b>Profile</b> — your name and node identity.</li>
<li><b>Radio &amp; Mesh</b> — frequency, spreading factor, region scope, high-gain receiver, signal probe, auto-advert.</li>
<li><b>Auto-add, Wi-Fi, Bluetooth, GPS</b> — discovery and connectivity.</li>
<li><b>Clock, Battery, Sensors, Display, Keyboard, Sound, Quick replies, Lock screen</b> — the device experience.</li>
<li><b>Language</b> (twelve UI languages), <b>Backups</b>, and <b>About</b> (firmware version, update check, diagnostics).</li>
</ul>
</div>
<div class="shot"><div class="device"><img class="screen" src="docs-img/settings.png" alt="Settings categories"></div><div class="cap">Settings categories</div></div>
</div>
</section>
<section class="doc" id="companion">
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2"/><line x1="11" y1="18" x2="13" y2="18"/></svg></span> Companion mode</h2>
<div class="row">
<div class="prose">
<p>WADAMESH runs everything on its own, but it can also be the radio for the <b>official MeshCore app</b> on your phone (iOS and Android). Your phone brings the big screen and full keyboard; the device does the LoRa. Best of all it works <b>alongside</b> the on-device UI — you can keep using the touch screen while your phone is connected, and your contacts, channels and messages stay in sync between them.</p>
<h3>Three ways to connect</h3>
<ul>
<li><b>USB</b> — plug the device into an Android phone or a computer; the app connects over the cable. Nothing to set up on the device.</li>
<li><b>Bluetooth</b> — turn on Bluetooth in <b>Settings &rarr; Bluetooth</b>. The device appears in the app as <code>MeshCore-&lt;your node name&gt;</code>; pair with the 6-digit code (default <code>123456</code>, changeable on the same page).</li>
<li><b>Wi-Fi</b> — put the device on your network in <b>Settings &rarr; Wi-Fi</b>. On the same Wi-Fi, the app connects to the device's IP address on port <code>5000</code> (the Wi-Fi page shows the IP).</li>
</ul>
<div class="callout">
<span class="cico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span>
<div>
<b>Run Bluetooth and Wi-Fi together only when you need to</b>
<p>Bluetooth and Wi-Fi can be on at the same time, but each one reserves a good chunk of RAM. On the Heltec V4 especially — it has the least free memory of the three boards — keeping both on at once leaves little headroom and can lead to instability or odd behaviour. If you only want the phone app, pick one transport (Bluetooth or Wi-Fi) and leave the other off; if you mostly use the device on its own, keep both off.</p>
</div>
</div>
</div>
<div class="shot"><div class="device"><img class="screen" src="docs-img/set_bluetooth.png" alt="Bluetooth pairing page"></div><div class="cap">Bluetooth &mdash; pairing code</div></div>
</div>
</section>
<section class="doc" id="settings-pages">
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/></svg></span> Settings pages</h2>
<div class="prose" style="max-width:68ch"><p>WADAMESH splits its settings into eighteen pages. Here is what each one does and the controls you will find on it. Open any page from the Settings tab; a back chevron in the title bar returns you to the grid.</p></div>
<div class="sgrp">Identity &amp; radio</div>
<div class="scards">
<div class="scard">
<div class="thumb"><img src="docs-img/set_profile.png" alt="Profile settings"></div>
<h3>Profile</h3>
<div class="lede">Who you are on the mesh.</div>
<ul>
<li><b>Node name</b> — your callsign on the network</li>
<li><b>Advert location</b> plus a share-in-advert toggle</li>
<li><b>Identity key</b> and <b>Share QR</b> to hand out your contact</li>
<li><b>Export / Import</b> your whole setup as a JSON backup</li>
</ul>
</div>
<div class="scard">
<div class="thumb"><img src="docs-img/set_radio___mesh.png" alt="Radio and Mesh settings"></div>
<h3>Radio &amp; Mesh</h3>
<div class="lede">Every LoRa and mesh parameter, split into RADIO / MESH / SIGNAL.</div>
<ul>
<li><b>Radio</b> — preset, frequency, bandwidth, spreading factor, coding rate, TX power, with a live duty-cycle and airtime-per-packet readout</li>
<li><b>Mesh</b> — answer-telemetry, path-hash size, region scope tag, scope direct messages</li>
<li><b>Signal</b> — high-gain receiver (Heltec V4.3), signal probe, and a shortcut to the Advertise app</li>
<li><b>Experimental</b> — multi-ACKs, client repeat, RX boost, duty meter</li>
</ul>
</div>
<div class="scard">
<div class="thumb"><img src="docs-img/set_auto_add.png" alt="Auto-add settings"></div>
<h3>Auto-add</h3>
<div class="lede">Which nodes join Contacts automatically from their adverts.</div>
<ul>
<li>Per-type toggles: <b>chat</b>, <b>repeater</b>, <b>room</b>, <b>sensor</b></li>
<li><b>Overwrite oldest</b> when the contact list is full</li>
<li><b>Notify</b> when a new contact is added</li>
<li><b>Max hops</b> filter for how far away a node can be</li>
</ul>
</div>
</div>
<div class="sgrp">Connectivity</div>
<div class="scards">
<div class="scard">
<div class="thumb"><img src="docs-img/set_wi_fi.png" alt="Wi-Fi settings"></div>
<h3>Wi-Fi</h3>
<div class="lede">For map tiles, clock sync and update checks.</div>
<ul>
<li>On / off with a live status line (IP, signal)</li>
<li><b>Scan</b> and tap an SSID, or type one in by hand</li>
<li><b>Save &amp; reconnect</b> applies live — no reboot</li>
<li>Saved network slots for one-tap quick-connect</li>
</ul>
</div>
<div class="scard">
<div class="thumb"><img src="docs-img/set_bluetooth.png" alt="Bluetooth settings"></div>
<h3>Bluetooth</h3>
<div class="lede">Pair with the MeshCore phone app over BLE.</div>
<ul>
<li><b>Enable Bluetooth</b> with a live mode status line</li>
<li>Set a 6-digit <b>pairing code</b> (applies after a reboot)</li>
</ul>
</div>
<div class="scard">
<div class="thumb"><img src="docs-img/set_gps.png" alt="GPS settings"></div>
<h3>GPS</h3>
<div class="lede">The on-board GPS receiver.</div>
<ul>
<li>On / off with a live <b>fix status</b> (acquiring, 2D/3D, age)</li>
<li><b>Serial baud</b> rate (applies after a reboot)</li>
<li>Reads "no GPS module" on devices without one</li>
</ul>
</div>
</div>
<div class="sgrp">Time &amp; power</div>
<div class="scards">
<div class="scard">
<div class="thumb"><img src="docs-img/set_clock___time.png" alt="Clock and time settings"></div>
<h3>Clock &amp; time</h3>
<div class="lede">Keep the clock right.</div>
<ul>
<li><b>Sync clock</b> from the system / NTP</li>
<li><b>Time zone</b> picker, or a custom UTC offset with steppers</li>
<li><b>12-hour clock</b> toggle</li>
</ul>
</div>
<div class="scard">
<div class="thumb"><img src="docs-img/set_battery.png" alt="Battery settings"></div>
<h3>Battery</h3>
<div class="lede">Power readouts and tuning.</div>
<ul>
<li><b>Battery &amp; power history</b> chart</li>
<li><b>Calibrate</b> — tap to set 100% at the current voltage</li>
<li><b>Battery saver</b> throttles the CPU when idle (T-Deck)</li>
</ul>
</div>
<div class="scard">
<div class="thumb"><img src="docs-img/set_sensors.png" alt="Sensors settings"></div>
<h3>Sensors <span class="tag">Heltec V4</span></h3>
<div class="lede">For the V4 Expansion Kit.</div>
<ul>
<li><b>Expansion Kit</b> configuration</li>
<li><b>Show Sensors tab</b> in the bottom bar (applies after restart)</li>
</ul>
</div>
</div>
<div class="sgrp">Experience</div>
<div class="scards">
<div class="scard">
<div class="thumb"><img src="docs-img/set_display.png" alt="Display settings"></div>
<h3>Display</h3>
<div class="lede">Look and feel.</div>
<ul>
<li><b>Screen timeout</b> before it sleeps</li>
<li><b>UI size</b> — Normal / Large / Huge</li>
<li><b>Colourful chat bubbles</b>, distance in miles, hide device name</li>
<li><b>Theme colour</b> picker</li>
</ul>
</div>
<div class="scard">
<div class="thumb"><img src="docs-img/set_keyboard.png" alt="Keyboard settings"></div>
<h3>Keyboard</h3>
<div class="lede">Typing and on-device input.</div>
<ul>
<li><b>Secondary layouts</b> — Cyrillic, Greek, Arabic, French, German and more</li>
<li><b>Accent popups</b> — long-press for accented letters</li>
<li><b>Enter sends</b> the message (T-Deck)</li>
<li><b>Flash</b> the screen and keys on a new message (T-Deck)</li>
</ul>
</div>
<div class="scard">
<div class="thumb"><img src="docs-img/set_sound.png" alt="Sound settings"></div>
<h3>Sound</h3>
<div class="lede">Notification audio.</div>
<ul>
<li>Master sound plus per-type toggles: <b>message</b>, <b>DM</b>, <b>@mention</b></li>
<li>Custom <b>sound files</b> from microSD where supported</li>
<li><b>Volume</b> control (T-Deck / Tanmatsu)</li>
</ul>
</div>
<div class="scard">
<div class="thumb"><img src="docs-img/set_quick_replies.png" alt="Quick replies settings"></div>
<h3>Quick replies</h3>
<div class="lede">One-tap canned messages.</div>
<ul>
<li>Edit <b>six</b> short replies</li>
<li>Pick them from the composer to send instantly</li>
</ul>
</div>
<div class="scard">
<div class="thumb"><img src="docs-img/set_lock_screen.png" alt="Lock screen settings"></div>
<h3>Lock screen <span class="tag">T-Deck</span></h3>
<div class="lede">A simple lock when idle.</div>
<ul>
<li><b>Wallpaper</b> picker and <b>lock text colour</b></li>
<li><b>Lock when screen off</b>, then hold to unlock</li>
</ul>
</div>
</div>
<div class="sgrp">System</div>
<div class="scards">
<div class="scard">
<div class="thumb ph"><span>Screenshot pending</span></div>
<h3>General</h3>
<div class="lede">Device housekeeping.</div>
<ul>
<li><b>Send advert now</b></li>
<li><b>Store data on SD</b> (reboot)</li>
<li><b>Run setup again</b> from scratch</li>
<li><b>Reboot device</b></li>
</ul>
</div>
<div class="scard">
<div class="thumb"><img src="docs-img/set_backups.png" alt="Backups settings"></div>
<h3>Backups</h3>
<div class="lede">Save and restore your whole config.</div>
<ul>
<li><b>Export</b> a new JSON backup</li>
<li>Restore from the <b>saved list</b></li>
<li><b>Factory reset</b> in the danger zone</li>
</ul>
</div>
<div class="scard">
<div class="thumb"><img src="docs-img/set_language.png" alt="Language settings"></div>
<h3>Language</h3>
<div class="lede">Twelve UI languages.</div>
<ul>
<li>Tap a language; the device reboots to apply</li>
</ul>
</div>
<div class="scard">
<div class="thumb"><img src="docs-img/set_about.png" alt="About settings"></div>
<h3>About</h3>
<div class="lede">Version, updates and diagnostics.</div>
<ul>
<li><b>Firmware version</b> plus update check / install</li>
<li>Full <b>system diagnostics</b> — uptime, chip, memory, flash, storage</li>
<li><b>Device ID</b> and crash-report export</li>
</ul>
</div>
</div>
</section>
<section class="doc" id="apps">
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg></span> Apps</h2>
<div class="prose" style="max-width:64ch">
<p>Tools from the home launcher. <b>RF Monitor</b> is a repeater-style view of recent packets and radio stats; <b>Spectrum</b> sweeps the band so you can spot interference or find a clear frequency.</p>
</div>
<div class="gallery">
<div class="mini"><img src="docs-img/app_rfmonitor.png" alt="RF Monitor"><div class="cap">RF Monitor</div></div>
<div class="mini"><img src="docs-img/app_spectrum.png" alt="Spectrum analyzer"><div class="cap">Spectrum analyzer</div></div>
</div>
</section>
</main>
</div>
<footer>WADAMESH — open-source MeshCore touch firmware · <a href="index.html">wadamesh.com</a></footer>
<script>
var KB = ['QWERTYUIOP', 'ASDFGHJKL', 'ZXCVBNM'];
document.querySelectorAll('.device').forEach(function(dev){
var kb = document.createElement('div'); kb.className = 'kbd';
KB.forEach(function(rowStr){
var r = document.createElement('div'); r.className = 'kr';
rowStr.split('').forEach(function(ch){ var k = document.createElement('span'); k.className = 'key'; k.textContent = ch; r.appendChild(k); });
kb.appendChild(r);
});
var sp = document.createElement('div'); sp.className = 'kr';
sp.innerHTML = '<span class="key">sym</span><span class="key wide"></span><span class="key">&#9166;</span>';
kb.appendChild(sp);
dev.appendChild(kb);
});
var links = Array.prototype.slice.call(document.querySelectorAll('nav.toc a'));
var obs = new IntersectionObserver(function(es){
es.forEach(function(e){ if (e.isIntersecting) {
links.forEach(function(l){ l.classList.remove('on'); });
var a = document.querySelector('nav.toc a[href="#' + e.target.id + '"]');
if (a) a.classList.add('on');
}});
}, { rootMargin: '-25% 0px -65% 0px' });
['overview','home','chat','contacts','map','settings','companion','settings-pages','apps'].forEach(function(id){ var el = document.getElementById(id); if (el) obs.observe(el); });
</script>
</body>
</html>
+58 -25
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WADAMESH — a touchscreen for your mesh radio</title>
<meta name="description" content="Open-source MeshCore touch-UI firmware for the LilyGo T-Deck and Heltec V4. Map, chat, channels, contacts — install from your browser.">
<meta name="description" content="Open-source MeshCore touch-UI firmware for the LilyGo T-Deck, Heltec V4 and Tanmatsu. Map, chat, channels, contacts — install from your browser.">
<link rel="icon" href="wadamesh-badge.svg">
<meta name="theme-color" content="#ffffff">
<link rel="preconnect" href="https://fonts.googleapis.com">
@@ -135,7 +135,7 @@
.step p{color:var(--mut); font-size:.92rem}
.step code{background:#fff; border:1px solid var(--line); border-radius:6px; padding:1px 6px; font-size:.85em; font-family:'JetBrains Mono',monospace}
.boards{display:grid; gap:14px; grid-template-columns:1fr 1fr; max-width:760px; margin:18px auto 0}
.boards{display:grid; gap:14px; grid-template-columns:repeat(auto-fit,minmax(210px,1fr)); max-width:760px; margin:18px auto 0}
@media(max-width:560px){.boards{grid-template-columns:1fr}}
.board{border:1px solid var(--line); background:var(--card); border-radius:14px; padding:18px; display:flex;
flex-direction:column; gap:10px; align-items:flex-start; box-shadow:0 8px 26px -22px rgba(21,24,30,.5)}
@@ -214,8 +214,8 @@
<!-- floating quick links -->
<div class="fab-rail left">
<a class="fab" href="https://t.me/+fHuhG67-RFJlNmFk" target="_blank" rel="noopener" aria-label="Join us on Telegram" title="Join us on Telegram">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M9.04 15.47 8.7 19.9c.5 0 .72-.22.98-.47l2.36-2.22 4.89 3.53c.9.5 1.53.24 1.77-.83l3.2-14.5c.3-1.33-.5-1.85-1.36-1.53L1.9 9.46c-1.3.5-1.28 1.22-.22 1.55l5.05 1.57L18.4 5.9c.55-.36 1.05-.16.64.2L9.04 15.47z"/></svg>
<a class="fab" href="https://discord.gg/T8X2UaQ7Sm" target="_blank" rel="noopener" aria-label="Join us on Discord" title="Join us on Discord">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.317 4.369a19.79 19.79 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.249a18.27 18.27 0 0 0-5.487 0 12.6 12.6 0 0 0-.617-1.249.077.077 0 0 0-.079-.037A19.74 19.74 0 0 0 3.677 4.369a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.2 14.2 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.1 13.1 0 0 1-1.872-.892.077.077 0 0 1-.008-.128c.126-.094.252-.192.372-.291a.074.074 0 0 1 .078-.01c3.927 1.793 8.18 1.793 12.061 0a.074.074 0 0 1 .079.009c.12.099.246.198.373.292a.077.077 0 0 1-.006.127 12.3 12.3 0 0 1-1.873.891.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.84 19.84 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.06.06 0 0 0-.031-.029zM8.02 15.331c-1.182 0-2.157-1.086-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.332-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.086-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.332-.946 2.418-2.157 2.418z"/></svg>
</a>
<a class="fab" href="#videos" aria-label="Community videos" title="Community videos">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
@@ -225,9 +225,9 @@
<a class="fab" href="https://github.com/ALLFATHER-BV/wadamesh/issues" target="_blank" rel="noopener" aria-label="Report a bug on GitHub" title="Report a bug">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="8" height="14" x="8" y="6" rx="4"/><path d="m19 7-3 2"/><path d="m5 7 3 2"/><path d="m19 19-3-2"/><path d="m5 19 3-2"/><path d="M20 13h-4"/><path d="M4 13h4"/><path d="m10 4 1 2"/><path d="m14 4-1 2"/></svg>
</a>
<button class="fab" id="docsBtn" type="button" aria-label="Documentation" title="Documentation">
<a class="fab" id="docsBtn" href="docs.html" aria-label="Documentation" title="Documentation">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v5h5"/><path d="M16 13H8"/><path d="M16 17H8"/><path d="M10 9H8"/></svg>
</button>
</a>
<a class="fab" href="https://t413.com/p/projects/MapTileDl/" target="_blank" rel="noopener" aria-label="Map tile downloader" title="Map tile downloader">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18l-6 3V6l6-3 6 3 6-3v15l-6 3-6-3z"/><path d="M9 3v15"/><path d="M15 6v15"/></svg>
</a>
@@ -272,7 +272,7 @@
<span class="byline">by meshcomod</span>
</div>
<p class="lede">A real touchscreen UI for your mesh radio.</p>
<p class="sub">MeshCore MESHCOMOD firmware for the <b>LilyGo&nbsp;T-Deck</b> and <b>Heltec&nbsp;V4</b> — map, chat, channels and contacts on the device itself, <b>and</b> a companion radio for the <b>MeshCore app</b> at the same time.</p>
<p class="sub">MeshCore MESHCOMOD firmware for the <b>LilyGo&nbsp;T-Deck</b>, <b>Heltec&nbsp;V4</b> and <b>Tanmatsu</b> — map, chat, channels and contacts on the device itself, <b>and</b> a companion radio for the <b>MeshCore app</b> at the same time.</p>
<div class="pills">
<span class="pill" tabindex="0"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 20l-6-3V4l6 3 6-3 6 3v13l-6-3-6 3z"/><path d="M9 7v13M15 4v13"/></svg>Offline map
<span class="pop"><b>Offline map</b>A real OpenStreetMap you can pan and zoom on the device. Pre-load tile packs to an SD card for fully offline use, or fetch over Wi-Fi — and see your contacts' GPS positions plotted on it.</span></span>
@@ -317,6 +317,11 @@
<button class="back" data-back>← choose a different path</button>
<div class="guide-head"><h3>Standalone install</h3></div>
<p class="p">Plug the device in over USB and flash it right here. Works in <b>Chrome</b> or <b>Edge</b> on desktop.</p>
<div class="chtoggle" role="group" aria-label="Release channel" style="display:inline-flex;margin:14px 0 0;border:1px solid var(--line);border-radius:10px;overflow:hidden">
<button type="button" data-wm-ch="stable" style="font:inherit;font-size:.85rem;font-weight:600;padding:7px 16px;border:0;background:#fff;color:var(--mut);cursor:pointer">Stable</button>
<button type="button" data-wm-ch="beta" style="font:inherit;font-size:.85rem;font-weight:600;padding:7px 16px;border:0;border-left:1px solid var(--line);background:#fff;color:var(--mut);cursor:pointer">Beta · testing</button>
</div>
<p data-wm-betahint hidden style="margin:8px auto 0;font-size:.84rem;color:#b4631a;max-width:560px">Beta builds are newer and less tested — for trying things early and reporting bugs. On a T-Deck under Launcher you download the .bin and side-load it.</p>
<div data-whatsnew hidden style="margin:18px auto 0;max-width:560px;background:rgba(21,182,166,.07);border:1px solid rgba(21,182,166,.28);border-radius:12px;padding:13px 16px;text-align:left">
<div style="font-weight:600">What's new in <span data-wn-tag style="color:var(--teal)">the latest build</span></div>
<ul data-wn-notes style="margin:7px 0 0;padding-left:18px;font-size:.9rem;color:var(--mut);line-height:1.55"></ul>
@@ -331,6 +336,9 @@
<esp-web-install-button id="inst-heltec" manifest="https://firmware.wadamesh.com/latest/manifest-heltec-v4-tft.json"></esp-web-install-button></div>
<div class="board"><h4>LilyGo T-Deck / Plus</h4><p>ESP32-S3 keyboard handheld.</p>
<esp-web-install-button id="inst-tdeck" manifest="https://firmware.wadamesh.com/latest/manifest-tdeck.json"></esp-web-install-button></div>
<div class="board"><h4>Tanmatsu</h4><p>ESP32-P4 keyboard handheld.</p>
<p style="color:var(--mut);font-size:.84rem;margin:0;line-height:1.5">Already have one? No USB flashing needed — install WADAMESH straight from the <b>Tanmatsu app store</b> on the device.</p>
<a class="dl" href="https://shop.nicolaielectronics.nl/" target="_blank" rel="noopener"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"/><path d="M3 6h18"/><path d="M16 10a4 4 0 0 1-8 0"/></svg>Get a Tanmatsu</a></div>
</div>
<details class="verpick">
<summary>Install a previous version</summary>
@@ -356,7 +364,7 @@
</div>
<div class="boards" style="grid-template-columns:1fr; max-width:380px">
<div class="board"><h4>LilyGo T-Deck / Plus</h4><p>app image (no bootloader)</p>
<a class="dl" href="https://firmware.wadamesh.com/latest/wadamesh-tdeck.bin"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 11l5 5 5-5M5 21h14"/></svg>Download .bin</a></div>
<a class="dl" id="dl-tdeck" href="https://firmware.wadamesh.com/latest/wadamesh-tdeck.bin"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 11l5 5 5-5M5 21h14"/></svg>Download .bin</a></div>
</div>
<p class="hint">New to Launcher? It's the easiest way to try WADAMESH without giving up your other firmware — and you can fully switch to Standalone any time.</p>
</div>
@@ -474,18 +482,44 @@ We use no tracking, analytics, or advertising cookies. Our CDN and security prov
// Live release version + "what's new" — read from the firmware host so the
// flasher always reflects the current build (the release pipeline updates
// version.json; no site redeploy needed per release).
fetch('https://firmware.wadamesh.com/latest/version.json', {cache:'no-store'})
.then(r => r.ok ? r.json() : null)
.then(v => {
if (!v || !v.tag) return;
document.querySelectorAll('[data-wn-tag]').forEach(e => e.textContent = v.tag);
const notes = Array.isArray(v.notes) ? v.notes : [];
document.querySelectorAll('[data-wn-notes]').forEach(ul => {
ul.innerHTML = '';
notes.forEach(n => { const li = document.createElement('li'); li.textContent = n; ul.appendChild(li); });
});
if (notes.length) document.querySelectorAll('[data-whatsnew]').forEach(b => b.hidden = false);
}).catch(()=>{});
// Release channel (Stable default / Beta testing). The toggle swaps the feed dir
// on the firmware host — latest/ (stable) <-> latest-beta/ (beta) — and the install
// manifests, "what's new", the Launcher .bin link and the version picker follow.
const WM = (window.WM = { feed:'latest', arch:'TOUCH' });
const WM_MAN = { heltec:'manifest-heltec-v4-tft.json', tdeck:'manifest-tdeck.json' };
window.wmManifest = board => 'https://firmware.wadamesh.com/' + WM.feed + '/' + WM_MAN[board];
function wmWhatsNew(){
const wn = document.querySelectorAll('[data-whatsnew]');
fetch('https://firmware.wadamesh.com/' + WM.feed + '/version.json', {cache:'no-store'})
.then(r => r.ok ? r.json() : null)
.then(v => {
if (!v || !v.tag) { wn.forEach(b => b.hidden = true); return; }
document.querySelectorAll('[data-wn-tag]').forEach(e => e.textContent = v.tag);
const notes = Array.isArray(v.notes) ? v.notes : [];
document.querySelectorAll('[data-wn-notes]').forEach(ul => {
ul.innerHTML = '';
notes.forEach(n => { const li = document.createElement('li'); li.textContent = n; ul.appendChild(li); });
});
wn.forEach(b => b.hidden = !notes.length);
}).catch(() => { wn.forEach(b => b.hidden = true); });
}
window.wmSetChannel = function(ch){
WM.feed = ch === 'beta' ? 'latest-beta' : 'latest';
WM.arch = ch === 'beta' ? 'BETA' : 'TOUCH';
const bH = document.getElementById('inst-heltec'), bT = document.getElementById('inst-tdeck');
if (bH) bH.setAttribute('manifest', wmManifest('heltec'));
if (bT) bT.setAttribute('manifest', wmManifest('tdeck'));
const dl = document.getElementById('dl-tdeck');
if (dl) dl.href = 'https://firmware.wadamesh.com/' + WM.feed + '/wadamesh-tdeck.bin';
document.querySelectorAll('[data-wm-ch]').forEach(b => { const on = b.getAttribute('data-wm-ch') === ch;
b.style.background = on ? 'var(--teal)' : '#fff'; b.style.color = on ? '#fff' : 'var(--mut)'; });
document.querySelectorAll('[data-wm-betahint]').forEach(e => e.hidden = ch !== 'beta');
const vs = document.getElementById('verSel');
if (vs && vs.options.length) { vs.selectedIndex = 0; vs.dispatchEvent(new Event('change')); }
wmWhatsNew();
};
document.querySelectorAll('[data-wm-ch]').forEach(b => b.addEventListener('click', () => wmSetChannel(b.getAttribute('data-wm-ch'))));
wmSetChannel('stable');
// ---- community videos — add an entry here to add a video ----
const VIDEOS = [
@@ -518,8 +552,7 @@ We use no tracking, analytics, or advertising cookies. Our CDN and security prov
// ---- toast + docs placeholder ----
function toast(msg){ const t = document.getElementById('toast'); if (!t) return; t.textContent = msg; t.classList.add('show'); clearTimeout(t._t); t._t = setTimeout(() => t.classList.remove('show'), 2400); }
const docsBtn = document.getElementById('docsBtn');
if (docsBtn) docsBtn.addEventListener('click', () => toast('Documentation — coming soon!'));
// The documentation button is a plain link to docs.html (no handler needed).
// ---- standalone: install a previous version (latest 4 betas + release notes) ----
(function(){
@@ -527,7 +560,7 @@ We use no tracking, analytics, or advertising cookies. Our CDN and security prov
const wrap = sel.closest('.verpick');
const notesEl = document.getElementById('verNotes'), activeEl = document.getElementById('verActive');
const btnH = document.getElementById('inst-heltec'), btnT = document.getElementById('inst-tdeck');
const latestH = btnH && btnH.getAttribute('manifest'), latestT = btnT && btnT.getAttribute('manifest');
const latestH = () => window.wmManifest('heltec'), latestT = () => window.wmManifest('tdeck');
const num = t => parseInt((String(t).match(/beta_(\d+)/) || [])[1] || '-1', 10);
let notes = {};
const esc = s => s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
@@ -543,10 +576,10 @@ We use no tracking, analytics, or advertising cookies. Our CDN and security prov
function showNotes(tag){ notesEl.innerHTML = notes[tag] ? md(notes[tag])
: '<p>Release notes on <a href="https://github.com/ALLFATHER-BV/wadamesh/releases/tag/'+tag+'" target="_blank" rel="noopener">GitHub</a>.</p>'; }
function blobManifest(tag, board){ const m = { name:'wadamesh '+tag, version:tag, new_install_prompt_erase:true,
builds:[{ chipFamily:'ESP32-S3', parts:[{ path:'https://firmware.wadamesh.com/releases/TOUCH/'+tag+'/wadamesh-'+board+'-merged.bin', offset:0 }] }] };
builds:[{ chipFamily:'ESP32-S3', parts:[{ path:'https://firmware.wadamesh.com/releases/'+((window.WM&&window.WM.arch)||'TOUCH')+'/'+tag+'/wadamesh-'+board+'-merged.bin', offset:0 }] }] };
return URL.createObjectURL(new Blob([JSON.stringify(m)], {type:'application/json'})); }
function apply(){ const tag = sel.value, isLatest = sel.selectedIndex === 0;
if (isLatest){ if(btnH&&latestH) btnH.setAttribute('manifest', latestH); if(btnT&&latestT) btnT.setAttribute('manifest', latestT); activeEl.hidden = true; }
if (isLatest){ if(btnH) btnH.setAttribute('manifest', latestH()); if(btnT) btnT.setAttribute('manifest', latestT()); activeEl.hidden = true; }
else { if(btnH) btnH.setAttribute('manifest', blobManifest(tag,'heltec-v4-tft')); if(btnT) btnT.setAttribute('manifest', blobManifest(tag,'tdeck'));
activeEl.hidden = false; activeEl.innerHTML = 'The <b>Install</b> buttons above now flash <b>'+tag+'</b>.'; }
showNotes(tag); }
+2
View File
@@ -209,6 +209,7 @@ lib_deps =
lvgl/lvgl @ ^8.3.11
bodmer/TFT_eSPI @ ^2.5.43
densaugeo/base64 @ ~1.4.0
knolleary/PubSubClient @ ^2.8.0
; ======================================================================
; LilyGo T-Deck / T-Deck Plus — flattened equivalent of meshcomod's
@@ -377,3 +378,4 @@ lib_deps =
file://arch/esp32/AsyncElegantOTA
lvgl/lvgl @ ^8.3.11
densaugeo/base64 @ ~1.4.0
knolleary/PubSubClient @ ^2.8.0
+14
View File
@@ -0,0 +1,14 @@
# beta_19 (one user-facing note per non-blank, non-# line; # lines are section comments)
# --- New ---
Radio & Mesh settings now show an airtime / duty-cycle readout: roughly how much of the airtime budget your current spreading factor, bandwidth and coding rate use, plus the time-on-air per packet — handy for staying inside regional limits like EU 868.
A new "Answer telemetry requests" toggle in Radio & Mesh settings controls whether other nodes can pull your battery and environment telemetry.
The Radio & Mesh settings page is now split into clear RADIO / MESH / SIGNAL sections so the options are easier to find.
# --- Fixed ---
Fixed a reboot that could happen when you closed a scrollable popup — the emoji picker, quick replies, the Wi-Fi scan list, the app drawer and similar — while it was still coasting from a flick. Thanks to everyone who sent in crash reports.
The accent popup no longer lingers on screen after you send a message on the T-Deck keyboard.
Heltec V4: the Expansion Kit environment line and mini chart on the Home screen no longer overlap the status text or run off the bottom of the display.
# --- Also ---
Everything from beta_18 is included.
+33
View File
@@ -0,0 +1,33 @@
# beta_20 (one user-facing note per non-blank, non-# line; # lines are section comments)
# --- New ---
Spectrum analyzer: a new app in the app drawer that sweeps the LoRa band and draws a live spectrum with a waterfall under it, a colour scale on the side and a peak-frequency readout. Handy for spotting interference or finding a quiet part of the band before you settle on a frequency.
Custom notification sounds: you can set your own notification sound from a 16-bit WAV file on the SD card, per event, instead of only the built-in chime. There is also a separate switch for the direct-message chime. Contributed by Yazutsu (#66).
Saved Wi-Fi networks: the device remembers more than one network now and can auto-join them. Each saved network has its own auto-join switch and can be forgotten on its own.
One chat settings screen: the gear on a chat or channel row, a long-press on that row, and the cog inside an open chat now all open the same screen. Everything is in one place: mark as read, mute messages, mute mentions, region and scope, share secret, blocked users, and remove or delete.
Per-channel mute: silence the notification sound for a single channel while the rest keep chiming.
High-gain receiver for the Heltec V4.3: the V4.3 front-end module has a roughly 17 dB receive amplifier that the firmware never switched on, so every V4.3 shipped with it bypassed. There is now a "High-gain receiver (FEM LNA)" toggle under Radio and Mesh, in the Signal section. Turning it on can noticeably improve weak-signal reception in quiet or remote spots; in a noisy area it can overload the front end, so it is off by default. V4.2 boards do not have this amplifier and do not show the toggle. Thanks to Wireless Rocks for the report and the measurements.
Apps button on the Heltec V4 home screen: the V4 home screen now has an Apps button next to Send advert, so the app drawer (Spectrum, RF Monitor, Snake and the rest) is one tap away. The T-Deck and Tanmatsu already had this.
Blocked list is easier to reach: open it from Contacts (the overflow menu) and from the chat settings screen, not only from where it used to live.
Portuguese (Brazil) is now a UI language, which brings the total to twelve. Community translation.
# --- Fixed ---
Adverts sent from the touch screen are now relayed by region-scoped repeaters. The advert went out without a region tag, so a repeater set to a single region dropped it, while the same advert sent from the phone app got through. The device now tags its flood advert the same way the phone companion does. (Issue #68, reported by EricMW61.)
A message could be transmitted on the wrong channel. After you added or removed channels the cached channel index could drift, so a message typed in one channel was sent on another channel's key. It looked correct on your own screen but arrived in the other channel. The send path now matches the target channel by name at the moment of sending, so this can no longer happen.
Room server logins could time out when the only route to the server was through a region-scoped repeater. A new opt-in toggle, "Scope direct messages to region" under Radio and Mesh, in the Signal section, tags your direct, login and admin messages with your region so a region-only repeater will relay them. It is off by default, so cross-region messaging is unchanged for anyone who does not need it. (Issue #64, reported by randelreiss.)
The Wi-Fi scan could come back empty. If a scan ran while the device was already connected, the automatic reconnect could cancel it mid-sweep and the list returned zero networks. The scan and the reconnect no longer fight over the radio, and the SSID list loads reliably again.
# --- Under the hood ---
Flood scoping was reworked so channel floods and direct floods are handled separately. Channel and group messages stay tagged with your region scope so public channels remain contained, while direct messages, logins and admin traffic stay unscoped by default to keep cross-region messaging working. The new region-scope toggle opts the direct traffic into the region tag (issue #64), and the advert path was moved onto the same scoping the phone companion already used (issue #68).
The Spectrum app and the mesh share one radio. While Spectrum is open it takes ownership of the SX1262 so the mesh loop does not re-tune or re-arm receive underneath the sweep, and leaving the app restores the mesh radio settings and resumes normal receive. The sweep also waits out the chip's RSSI settle time after each retune and takes a short peak reading per step, so the trace shows real energy instead of the reset floor.
A shared flag now tells the main loop when a Wi-Fi scan is running, so the periodic reconnect does not disconnect and reconnect during a sweep, and auto-reconnect is paused for the length of the scan.
The Heltec V4.3 front-end module (KCT8103L) is now driven for receive with its low-noise amplifier behind the new setting. This is the large amplifier and it is separate from the SX1262's own internal RX-boost bit, which is only a fraction of a dB.
The new settings (per-channel mute, custom sounds, saved Wi-Fi networks, the high-gain and region-scope toggles, and the trackball-navigation default) are stored with versioned migrations, so updating from an earlier beta keeps your existing settings.
# --- Changed ---
Trackball and keypad navigation is off by default now and marked experimental, because it still has rough edges. With it off you get the on-screen cursor as before. When it is on it can now reach the top-bar buttons and the back and cog buttons on settings and tool pages.
First-time setup no longer has a Wi-Fi step. Instead it explains that the device can run Wi-Fi and Bluetooth at the same time, as a standalone radio and a phone companion together, and that you set those up later in Settings.
RF Monitor and Spectrum now use the title bar with a back button, the same as the settings pages.
# --- Also ---
Everything from beta_19 is included.
+16
View File
@@ -0,0 +1,16 @@
# beta_21 (one user-facing note per non-blank, non-# line; # lines are section comments)
# --- New ---
Stable and test update channels. beta_21 is the first stable build. On devices that update over Wi-Fi there is now a "Get test builds (beta)" switch on the About screen: leave it off to stay on stable releases, or turn it on to receive test builds earlier — it switches both the "update available" check and the firmware you install to the test channel. You can go back to stable at any time.
MQTT bridge (highly experimental, off by default). An opt-in bridge that forwards the messages your node receives to an MQTT broker of your choice, so you can log your mesh or wire it into other tools. It is gated behind a privacy consent you have to tick, direct messages are excluded unless you deliberately enable them, and the payload can be sealed with an encryption key so only you can read it. Use a broker you control, never a public one. This is experimental and may change. Contributed by Samuel Coustet (@samuelcoustet, PR #71), reworked for privacy.
Portugal (Narrow) region preset, so Portuguese users can pick the local defaults in one tap. (Issue #74, requested by Salgueiron.)
Saved-contacts counter. The Contacts overflow menu now shows how many contacts you have saved out of the maximum it can hold (for example 42 / 2000). (Issue #72, asked by Buko84.)
# --- Fixed ---
The signal probe never travels past the first node now. The "auto-discover" signal pulse, and the probe behind the Signal popup, are sent strictly zero-hop, so a repeater can no longer re-flood them across the mesh. The probe only measures the link to whatever hears it first, which is what it was always meant to do.
Your most relevant contacts now show when you have a lot of them. The on-screen contact list is capped for performance, but it was trimming the list before sorting it, so some contacts could drop out of view. It now sorts first, shows the closest matches, and adds a "+N more — search to narrow the list" line at the bottom. (Issue #73, reported by 1nerdherder.)
The blocked-users list opened from Contacts now shows the proper two-line title bar with a back button, instead of an unlabelled screen.
The Contacts overflow popup fits the screen again after the saved-contacts counter was added.
# --- Also ---
Everything from beta_20 is included.
+8
View File
@@ -0,0 +1,8 @@
# beta_22 (TEST build — one user-facing note per non-blank, non-# line; # lines are section comments)
# --- Fixed ---
The signal probe reads a real signal again. In beta_21 the probe was changed to a zero-hop advert so it could never be re-flooded across the mesh — but nothing answers a zero-hop advert, so the signal reading stopped updating. It now sends a directed trace "ping" to your nearest repeater instead: the repeater replies directly (it still never floods the mesh) and the device reads the link signal from that reply. So you get both — no mesh flooding, and a live signal again. Thanks to Tarmo for spotting this and suggesting the fix.
Contacts discovered while your phone is connected now show on the device too. If the T-Deck was connected to the MeshCore phone app over Bluetooth and heard a new node, the node appeared on the phone but not on the device's own screen, and the Contacts list did not refresh. The device now updates its own Contacts list and notification whether or not a phone app is connected. (Issue #73, reported by 1nerdherder.)
# --- Also ---
This is a test-channel build (opt in with "Get test builds" on the About screen). Everything from beta_21, the first stable, is included.
+45
View File
@@ -38,6 +38,18 @@ SEQ = [
]
# Country/region flags: a regional-indicator PAIR (e.g. LS = U+1F1F1 U+1F1F8) baked
# as ONE image keyed on the LEAD indicator, the trailing indicator mapped to the
# zero-width glyph (same trick as SEQ). Noto's png/128 ships no flags, so pull the
# flat rectangle from the same Noto project's third_party/region-flags. No bare
# regional indicator is ever offered in the UI, so reusing the lead cp as the key
# is safe — and the picker currently has no other flag using these indicators.
FLAGS = [
# (lead_cp, trail_cp, ISO-3166-alpha-2)
(0x1F1F1, 0x1F1F8, "LS"), # Lesotho
]
def fetch(stem):
fn = os.path.join(CACHE, "u_{}.png".format(stem))
if os.path.exists(fn) and os.path.getsize(fn) > 0:
@@ -54,6 +66,22 @@ def fetch(stem):
return None
def fetch_flag(iso):
fn = os.path.join(CACHE, "flag_{}.png".format(iso))
if os.path.exists(fn) and os.path.getsize(fn) > 0:
return fn
url = ("https://raw.githubusercontent.com/googlefonts/noto-emoji/main"
"/third_party/region-flags/png/{}.png".format(iso))
try:
req = urllib.request.Request(url, headers={"User-Agent": "emoji-gen"})
data = urllib.request.urlopen(req, timeout=20).read()
with open(fn, "wb") as f:
f.write(data)
return fn
except Exception:
return None
def to_rgb565a8(img):
# IDENTICAL to gen-emoji-font.py: autocrop transparent margin, bottom-align to
# the text baseline, emit [lo, hi, alpha] per pixel (LV_COLOR_16_SWAP=0).
@@ -94,6 +122,14 @@ for key_cp, stem, zeros in SEQ:
for z in zeros:
new_entries.append((z, "d_zero"))
for lead_cp, trail_cp, iso in FLAGS:
fn = fetch_flag(iso)
if not fn:
sys.exit("FAILED to fetch flag {} for U+{:X}".format(iso, lead_cp))
new_defs.append((lead_cp, "e_{:x}".format(lead_cp), to_rgb565a8(Image.open(fn))))
new_entries.append((lead_cp, "d_{:x}".format(lead_cp)))
new_entries.append((trail_cp, "d_zero"))
# ---- splice into emoji_data.c ----
src = open(OUTC).read()
TYPE_MARK = "\ntypedef struct { uint32_t cp;"
@@ -101,6 +137,15 @@ FUNC_MARK = "const lv_img_dsc_t* emojiGlyphLookup(uint32_t cp) {"
head = src[:src.index(TYPE_MARK)] # header + includes + all glyph defs
func = src[src.index(FUNC_MARK):] # the binary-search getter (verbatim)
# Idempotency: on a RE-RUN, `head` still holds the e_/d_ glyph defs spliced in by a
# PRIOR run (they live before the typedef), so blindly re-appending new_defs would
# redefine them ("redefinition of e_26bd"). Strip any def for a cp we're about to
# re-bake; the lookup table dedups separately (bycp), so this only touches defs.
for _cp, _ename, _data in new_defs:
_hx = "{:x}".format(_cp)
head = re.sub(r"static const uint8_t e_" + _hx + r"\[\] = [^;]*;\n", "", head)
head = re.sub(r"static const lv_img_dsc_t d_" + _hx + r" = [^;]*;\n", "", head)
# Existing lookup entries (includes the FE0F/FE0E/200D -> d_zero rows).
existing = [(int(cp, 16), ref) for cp, ref in
re.findall(r"\{\s*(0x[0-9A-Fa-f]+)u,\s*&(\w+)\s*\}", src)]
+24 -18
View File
@@ -1,21 +1,27 @@
#!/usr/bin/env python3
# Generate the web-flasher metadata that must roll with every release:
# - version.json : {tag, notes[]} — the site shows this ("what's new")
# - version.json : {tag, channel, notes[]} — the site shows this ("what's new")
# - manifest-tdeck.json : esp-web-tools manifest (its "version" drives the
# - manifest-heltec-v4-tft.json install dialog, so it must equal the tag)
#
# These live next to the rolling bins at firmware.wadamesh.com/latest/, so the
# flasher always reflects the current release without a site redeploy. The
# manifests themselves point at the IMMUTABLE per-tag bins (see below) to dodge
# the latest/*.bin 4h edge cache.
# These live next to the rolling bins in a per-channel feed dir on
# firmware.wadamesh.com (latest/ for stable, latest-beta/ for the test channel),
# so the flasher always reflects the current release without a site redeploy. The
# manifests themselves point at the IMMUTABLE per-tag bins (releases/TOUCH for
# stable, releases/BETA for beta) to dodge the latest/*.bin 4h edge cache.
#
# Usage: gen-flasher-meta.py <tag> <outdir> [notes_file]
# notes_file: one note per non-empty line (plain text); optional.
# Usage: gen-flasher-meta.py <tag> <outdir> [notes_file] [channel]
# notes_file: one note per non-empty line (plain text); optional ("" to skip).
# channel: "stable" (default) or "beta" — picks the immutable archive dir
# (releases/TOUCH vs releases/BETA) the manifests point at, and is
# recorded in version.json so the site/flasher can label the channel.
import sys, json, os
tag = sys.argv[1]
outdir = sys.argv[2]
notes_file = sys.argv[3] if len(sys.argv) > 3 else None
notes_file = sys.argv[3] if len(sys.argv) > 3 and sys.argv[3] else None
channel = sys.argv[4] if len(sys.argv) > 4 else "stable"
archive = "BETA" if channel == "beta" else "TOUCH"
notes = []
if notes_file and os.path.exists(notes_file):
@@ -24,7 +30,7 @@ if notes_file and os.path.exists(notes_file):
os.makedirs(outdir, exist_ok=True)
with open(os.path.join(outdir, "version.json"), "w") as f:
json.dump({"tag": tag, "notes": notes}, f, indent=2)
json.dump({"tag": tag, "channel": channel, "notes": notes}, f, indent=2)
BOARDS = {
"manifest-tdeck.json": ("wadamesh — LilyGo T-Deck", "wadamesh-tdeck-merged.bin"),
@@ -32,21 +38,21 @@ BOARDS = {
}
for fn, (name, binf) in BOARDS.items():
manifest = {
"name": name,
"name": name + ("" if channel == "stable" else " (beta)"),
"version": tag,
"new_install_prompt_erase": True,
"builds": [{
"chipFamily": "ESP32-S3",
# Point at the IMMUTABLE per-tag bin, NOT latest/. The latest/*.bin URLs
# are stable filenames cached 4h (max-age=14400) and overwritten in place,
# so for up to 4h after a release the flasher could hand out the PREVIOUS
# beta's bytes while version.json already advertised the new tag. The
# per-tag path is never overwritten, so its (24h) cache is always correct.
# The manifest itself is max-age=300, so this new path propagates in <=5min.
"parts": [{"path": f"https://firmware.wadamesh.com/releases/TOUCH/{tag}/{binf}", "offset": 0}],
# Point at the IMMUTABLE per-tag bin, NOT the rolling feed bin. The feed
# *.bin URLs are stable filenames cached 4h and overwritten in place, so
# for up to 4h after a release the flasher could hand out the PREVIOUS
# build's bytes while version.json already advertised the new tag. The
# per-tag path is never overwritten, so its cache is always correct. The
# manifest itself is max-age=300, so the new path propagates in <=5min.
"parts": [{"path": f"https://firmware.wadamesh.com/releases/{archive}/{tag}/{binf}", "offset": 0}],
}],
}
with open(os.path.join(outdir, fn), "w") as f:
json.dump(manifest, f, indent=2)
print(f"wrote version.json + 2 manifests for {tag} -> {outdir} (notes: {len(notes)})")
print(f"wrote version.json + 2 manifests for {tag} ({channel}) -> {outdir} (notes: {len(notes)})")
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""
Documentation screenshot capture for the DOC_CAPTURE firmware build.
Build the firmware with -DDOC_CAPTURE, flash it to the T-Deck, then run:
python3 scripts/doc/capture.py /dev/cu.usbmodemXXXX [outdir]
The device waits for a 'G' byte, then walks every screen and streams each
framebuffer over USB as a 16-bit BMP wrapped in <<<WMSHOT ...>>> / <<<WMEND>>>
markers. This script sends 'G', reads each frame, and writes a PNG per screen
(via macOS `sips`; falls back to leaving the .bmp if sips is unavailable).
"""
import sys, os, re, time, subprocess
try:
import serial
except ImportError:
sys.exit("pyserial not found. Try: /Library/Developer/CommandLineTools/usr/bin/python3 -m pip install pyserial")
if len(sys.argv) < 2:
sys.exit("usage: capture.py <serial-port> [outdir]")
port = sys.argv[1]
outdir = sys.argv[2] if len(sys.argv) > 2 else "doc-shots"
os.makedirs(outdir, exist_ok=True)
ser = serial.Serial(port, 115200, timeout=2)
print(f"listening on {port} -> {outdir}/")
hdr_re = re.compile(rb"<<<WMSHOT name=(\S+) w=(\d+) h=(\d+) bytes=(\d+)>>>")
def read_exact(n):
data = bytearray()
while len(data) < n:
chunk = ser.read(n - len(data))
if not chunk:
break
data += chunk
return bytes(data)
count = 0
started = False
last_go = 0.0
deadline = time.time() + 90 # give the device up to 90s to start
while time.time() < deadline:
if not started and time.time() - last_go > 0.4:
ser.write(b"G") # nudge the device to begin the tour
ser.flush()
last_go = time.time()
line = ser.readline()
if not line:
continue
if b"WMTOUR START" in line:
started = True
print("tour started")
continue
if b"WMTOUR END" in line:
print("tour ended")
break
m = hdr_re.search(line)
if not m:
continue
name = m.group(1).decode()
nbytes = int(m.group(4))
data = read_exact(nbytes)
if len(data) != nbytes:
print(f" ! {name}: short read {len(data)}/{nbytes}")
continue
bmp = os.path.join(outdir, name + ".bmp")
png = os.path.join(outdir, name + ".png")
with open(bmp, "wb") as f:
f.write(data)
try:
subprocess.run(["sips", "-s", "format", "png", bmp, "--out", png],
check=True, capture_output=True)
os.remove(bmp)
count += 1
print(f" [{count:2d}] {name}.png ({nbytes} bytes)")
except Exception as e:
count += 1
print(f" [{count:2d}] {name}.bmp ({nbytes} bytes; sips convert failed: {e})")
ser.close()
print(f"done: {count} screenshots in {outdir}/")
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""
Reference subscriber + decryptor for the wadamesh MQTT bridge.
The bridge publishes received mesh messages to:
wadamesh/{node_hex}/msg/dm direct / signed messages (only if DM publish is ON)
wadamesh/{node_hex}/msg/ch channel messages
wadamesh/{node_hex}/status "online" / "offline" (retained LWT)
Payload:
- No encryption key set on the device -> payload is plain UTF-8 JSON.
- Encryption key (PSK) set -> payload is
base64( nonce[12] || ciphertext || tag[16] ), AES-256-GCM,
key = SHA-256(psk).
Set PSK below to the same passphrase you typed on the device.
Requires: pip install paho-mqtt cryptography
"""
import base64, hashlib, json
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
BROKER = "homeassistant.local"
PORT = 1883
PSK = "" # same passphrase as Settings -> MQTT -> Encryption key; "" = plaintext
def decode(payload: bytes) -> dict:
"""Return the message dict, decrypting if a PSK is configured."""
if not PSK:
return json.loads(payload)
key = hashlib.sha256(PSK.encode()).digest() # 32-byte AES-256 key
blob = base64.b64decode(payload)
nonce, ct_and_tag = blob[:12], blob[12:] # GCM tag is the trailing 16 bytes
plain = AESGCM(key).decrypt(nonce, ct_and_tag, None)
return json.loads(plain)
def main():
import paho.mqtt.client as mqtt
def on_connect(c, *_):
c.subscribe("wadamesh/+/msg/#")
print("subscribed to wadamesh/+/msg/#")
def on_message(_c, _u, msg):
try:
data = decode(msg.payload)
print(f"{msg.topic}: {data}")
except Exception as e:
print(f"{msg.topic}: <undecodable: {e}>")
c = mqtt.Client()
c.on_connect, c.on_message = on_connect, on_message
c.connect(BROKER, PORT, 60)
c.loop_forever()
if __name__ == "__main__":
main()
+83 -48
View File
@@ -1,85 +1,120 @@
#!/usr/bin/env bash
# wadamesh release: build tagged bins for both touch boards, refresh the
# update-check listing, and publish to the firmware VPS (behind Cloudflare).
# wadamesh release — two channels: TEST (beta, default) and STABLE (promote).
#
# Usage:
# WADAMESH_VPS=user@your-vps WADAMESH_VPS_PATH=/srv/wadamesh/firmware \
# scripts/release.sh beta_2
# Cut a TEST build (builds both boards, publishes to the beta channel — fast/frequent):
# WADAMESH_VPS=user@host scripts/release.sh beta_21
#
# Promote an ALREADY-BUILT beta to STABLE (no rebuild — copies the tested bins):
# WADAMESH_VPS=user@host scripts/release.sh beta_21 --promote
#
# Channels on the VPS firmware root (firmware.wadamesh.com, behind Cloudflare):
# releases/BETA/<tag>/ immutable beta archives <- test channel + new-firmware beta check
# releases/TOUCH/<tag>/ immutable stable archives <- stable feed + legacy on-device check
# latest-beta/ beta feed (version.json + manifests + rolling bins)
# latest/ stable feed (version.json + manifests + rolling bins)
#
# The VPS target comes from the environment — never commit it (Cloudflare fronts
# the origin, so the IP isn't needed in the firmware anyway).
set -euo pipefail
TAG="${1:?usage: release.sh <tag> e.g. beta_2}"
TAG="${1:?usage: release.sh <tag> [--promote] e.g. beta_21 / beta_21 --promote}"
MODE="beta"
[ "${2:-}" = "--promote" ] && MODE="stable"
PIO="${PIO:-$HOME/Library/Python/3.9/bin/pio}"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
OUT="$ROOT/out/firmware" # local mirror of the VPS firmware root
REL="$OUT/releases/TOUCH"; LATEST="$OUT/latest"
OUT="$ROOT/out/firmware" # local mirror of the VPS firmware root
DEST="${WADAMESH_VPS:-}"; DEST_PATH="${WADAMESH_VPS_PATH:-/srv/wadamesh/firmware}"
# env:binname pairs — plain string form (works on macOS's bash 3.2; no associative arrays)
ENVS="heltec_v4_tft_companion_radio_usb_tcp_touch:wadamesh-heltec-v4-tft LilyGo_TDeck_companion_radio_touch:wadamesh-tdeck"
# 1. Pull the current published tree so the listing stays complete across tags.
# Per-channel destination paths.
if [ "$MODE" = "stable" ]; then
ARCH="$OUT/releases/TOUCH"; FEED="$OUT/latest"
else
ARCH="$OUT/releases/BETA"; FEED="$OUT/latest-beta"
fi
# 1. Pull the current published tree so listings stay complete across tags.
if [ -n "$DEST" ]; then
mkdir -p "$OUT"
rsync -a "$DEST:$DEST_PATH/" "$OUT/" 2>/dev/null || echo "note: first publish (nothing to pull yet)"
fi
mkdir -p "$REL/$TAG" "$LATEST"
mkdir -p "$ARCH/$TAG" "$FEED"
# 2. Build both boards (app + merged), tag + version embedded.
export PLATFORMIO_BUILD_FLAGS="-DFIRMWARE_RELEASE_TAG='\"${TAG}\"' -DFIRMWARE_VERSION='\"wadamesh ${TAG}\"'"
for pair in $ENVS; do
env="${pair%%:*}"; name="${pair##*:}"
"$PIO" run -t mergebin -e "$env"
cp ".pio/build/$env/firmware.bin" "$REL/$TAG/$name.bin"
cp ".pio/build/$env/firmware-merged.bin" "$REL/$TAG/$name-merged.bin"
cp ".pio/build/$env/firmware-merged.bin" "$LATEST/$name-merged.bin" # rolling latest -> web flasher (standalone)
cp ".pio/build/$env/firmware.bin" "$LATEST/$name.bin" # rolling latest app image -> Launcher path
done
if [ "$MODE" = "beta" ]; then
# 2b. TEST build: compile both boards (tag + version embedded) into the beta archive + feed.
export PLATFORMIO_BUILD_FLAGS="-DFIRMWARE_RELEASE_TAG='\"${TAG}\"' -DFIRMWARE_VERSION='\"wadamesh ${TAG}\"'"
for pair in $ENVS; do
env="${pair%%:*}"; name="${pair##*:}"
"$PIO" run -t mergebin -e "$env"
cp ".pio/build/$env/firmware.bin" "$ARCH/$TAG/$name.bin"
cp ".pio/build/$env/firmware-merged.bin" "$ARCH/$TAG/$name-merged.bin"
cp ".pio/build/$env/firmware-merged.bin" "$FEED/$name-merged.bin" # rolling -> flasher (standalone)
cp ".pio/build/$env/firmware.bin" "$FEED/$name.bin" # rolling app image -> Launcher path
done
else
# 2s. STABLE promotion: NO rebuild — copy the already-tested beta bins so what
# ships to stable is byte-for-byte what the community tested.
SRC="$OUT/releases/BETA/$TAG"
if [ ! -d "$SRC" ] || ! ls "$SRC/"*.bin >/dev/null 2>&1; then
# beta_20 + any pre-channel build already lives under releases/TOUCH/<tag>/.
if ls "$ARCH/$TAG/"*.bin >/dev/null 2>&1; then
SRC="$ARCH/$TAG"; echo "note: $TAG not in releases/BETA — promoting from existing $ARCH/$TAG"
else
echo "ERROR: no built bins for $TAG (expected $OUT/releases/BETA/$TAG). Cut the beta first."; exit 1
fi
fi
for pair in $ENVS; do
name="${pair##*:}"
cp "$SRC/$name.bin" "$ARCH/$TAG/$name.bin"
cp "$SRC/$name-merged.bin" "$ARCH/$TAG/$name-merged.bin"
cp "$SRC/$name-merged.bin" "$FEED/$name-merged.bin"
cp "$SRC/$name.bin" "$FEED/$name.bin"
done
echo "promoted $TAG bins $SRC -> $ARCH/$TAG (+ $FEED)"
fi
# 3. Regenerate the update-check listing (the firmware scans the body for the
# highest beta_<N>). JSON array of dir names — GitHub-contents shape.
python3 - "$REL" > "$REL/index.json" <<'PY'
# 3. Regenerate this channel's update-check listing (firmware scans the body for
# the highest beta_<N>). JSON array of dir names — GitHub-contents shape.
python3 - "$ARCH" > "$ARCH/index.json" <<'PY'
import os, sys, json
rel = sys.argv[1]
betas = sorted(d for d in os.listdir(rel)
if d.startswith("beta_") and os.path.isdir(os.path.join(rel, d)))
print(json.dumps([{"name": b, "type": "dir"} for b in betas], indent=2))
PY
echo "listing: $(python3 -c 'import json,sys;print(", ".join(x["name"] for x in json.load(open(sys.argv[1]))))' "$REL/index.json")"
echo "listing ($MODE): $(python3 -c 'import json,sys;print(", ".join(x["name"] for x in json.load(open(sys.argv[1]))))' "$ARCH/index.json")"
# 3b. Web-flasher metadata (version.json + esp-web-tools manifests) — must roll
# with the release so the flasher shows the current version + notes, not a
# frozen one. Notes come from release-notes/<tag>.txt (one per line; optional).
python3 "$ROOT/scripts/build/gen-flasher-meta.py" "$TAG" "$LATEST" "$ROOT/release-notes/$TAG.txt"
# 3b. Web-flasher metadata for THIS channel (version.json + manifests -> the channel feed).
python3 "$ROOT/scripts/build/gen-flasher-meta.py" "$TAG" "$FEED" "$ROOT/release-notes/$TAG.txt" "$MODE"
# 3c. Mesh America Configurator provider catalog (apps.meshamerica.com — the web
# flasher Cascadia Mesh uses). It is served LIVE from raw.githubusercontent
# main, so each release must regenerate it for $TAG and push that ONE file to
# main; otherwise the configurator keeps offering the previous beta. It points
# at the immutable /releases/TOUCH/$TAG/ bins published below.
# (Generator: deploy/gen-meshamerica-catalog.py — reads release-notes/$TAG.txt.)
CATALOG="$ROOT/deploy/meshamerica-catalog.json"
python3 "$ROOT/deploy/gen-meshamerica-catalog.py" "$TAG" > "$CATALOG.tmp" && mv "$CATALOG.tmp" "$CATALOG"
mkdir -p "$OUT/meshamerica" && cp "$CATALOG" "$OUT/meshamerica/catalog.json" # also mirror to the VPS (enables the branded URL once its nginx location is deployed)
if git -C "$ROOT" diff --quiet -- "$CATALOG"; then
echo "Mesh America catalog already current for $TAG"
elif [ "$(git -C "$ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null)" = "main" ]; then
if git -C "$ROOT" commit -q -m "chore: refresh Mesh America catalog for $TAG" -- "$CATALOG" \
&& git -C "$ROOT" push origin HEAD:main; then
echo "Mesh America catalog refreshed + pushed to main ($TAG)"
# 3c. Mesh America Configurator provider catalog — STABLE only (it is a public
# flasher catalog served live from raw.githubusercontent main; test builds
# must not bump it). Points at the immutable releases/TOUCH/$TAG bins.
if [ "$MODE" = "stable" ]; then
CATALOG="$ROOT/deploy/meshamerica-catalog.json"
python3 "$ROOT/deploy/gen-meshamerica-catalog.py" "$TAG" > "$CATALOG.tmp" && mv "$CATALOG.tmp" "$CATALOG"
mkdir -p "$OUT/meshamerica" && cp "$CATALOG" "$OUT/meshamerica/catalog.json"
if git -C "$ROOT" diff --quiet -- "$CATALOG"; then
echo "Mesh America catalog already current for $TAG"
elif [ "$(git -C "$ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null)" = "main" ]; then
if git -C "$ROOT" commit -q -m "chore: refresh Mesh America catalog for $TAG" -- "$CATALOG" \
&& git -C "$ROOT" push origin HEAD:main; then
echo "Mesh America catalog refreshed + pushed to main ($TAG)"
else
echo "WARN: Mesh America catalog commit/push failed — push deploy/meshamerica-catalog.json to main by hand"
fi
else
echo "WARN: Mesh America catalog commit/push failed — push deploy/meshamerica-catalog.json to main by hand"
echo "NOTE: regenerated $CATALOG for $TAG but not on 'main' — commit + push it to main."
fi
else
echo "NOTE: regenerated $CATALOG for $TAG but not on 'main' — commit + push it to main so apps.meshamerica.com updates."
fi
# 4. Publish to the VPS (Cloudflare caches at the edge).
if [ -n "$DEST" ]; then
rsync -av "$OUT/" "$DEST:$DEST_PATH/"
echo "published $TAG -> $DEST:$DEST_PATH"
echo "published $TAG ($MODE) -> $DEST:$DEST_PATH"
else
echo "WADAMESH_VPS not set — built + staged in $REL/$TAG only (no publish)."
echo "WADAMESH_VPS not set — built + staged in $ARCH/$TAG + $FEED only (no publish)."
fi
+79 -21
View File
@@ -22,6 +22,7 @@
#endif
#ifdef MULTI_TRANSPORT_COMPANION
#include <helpers/esp32/MultiTransportCompanionInterface.h>
#include "helpers/esp32/MqttBridge.h"
#endif
#endif
@@ -1365,22 +1366,32 @@ static inline bool isMsgFloodType(uint8_t t) {
}
void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
// Live signal for the top-bar icon: SNR of whatever we just heard, with a
// timestamp so the UI can dim the icon when the mesh has gone quiet.
_ui_sig_snr_q4 = (int8_t)(snr * 4.0f);
_ui_sig_rssi = (int8_t)rssi;
_ui_sig_ms = millis();
// Recent-RX ring for the Monitor app (see UiRxRec): parse the minimal header
// (type / route / hops) and push newest-first. Header byte layout is
const int8_t snr_q4 = (int8_t)(snr * 4.0f);
const uint32_t now_ms = millis();
// Parse route + path length (hop count). Header byte layout is
// [version:2][payload_type:4][route_type:2]
// with 4 transport-code bytes following iff route is a TRANSPORT_* one; the
// next byte's low 6 bits are the path length (hop count).
// next byte's low 6 bits are the path length. hops == 0 = heard DIRECTLY.
uint8_t rt = 0, hops = 0;
if (len > 0) {
const uint8_t rt = raw[0] & 0x03;
const bool xp = (rt == ROUTE_TYPE_TRANSPORT_FLOOD || rt == ROUTE_TYPE_TRANSPORT_DIRECT);
const int ps = 1 + (xp ? 4 : 0);
const uint8_t hops = (ps < len) ? (uint8_t)(raw[ps] & 0x3F) : 0;
uiRxLogPush(_ui_sig_ms, (int8_t)rssi, _ui_sig_snr_q4,
rt = raw[0] & 0x03;
const bool xp = (rt == ROUTE_TYPE_TRANSPORT_FLOOD || rt == ROUTE_TYPE_TRANSPORT_DIRECT);
const int ps = 1 + (xp ? 4 : 0);
hops = (ps < len) ? (uint8_t)(raw[ps] & 0x3F) : 0;
}
// Live signal for the top-bar icon: ONLY from packets heard DIRECTLY (0-hop), so
// the reading reflects a real direct-neighbour RF link, not the SNR of a repeater
// relaying multi-hop traffic. The signal probe sends a zero-hop advert (it never
// floods), so this is fed passively by directly-heard neighbours.
if (len > 0 && hops == 0) {
_ui_sig_snr_q4 = snr_q4;
_ui_sig_rssi = (int8_t)rssi;
_ui_sig_ms = now_ms;
}
// Recent-RX ring for the Monitor app (see UiRxRec): log THIS packet's actual
// SNR/RSSI regardless of hop count, newest-first.
if (len > 0) {
uiRxLogPush(now_ms, (int8_t)rssi, snr_q4,
(uint8_t)((raw[0] >> 2) & 0x0F), rt, hops,
(uint8_t)(len > 255 ? 255 : len));
}
@@ -1734,12 +1745,14 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path
memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE);
_serial->writeFrameToAll(out_frame, 1 + PUB_KEY_SIZE);
}
} else {
#ifdef DISPLAY_CLASS
if (_ui) _ui->notify(UIEventType::newContactMessage);
#endif
}
#ifdef DISPLAY_CLASS
// Notify the touch UI whether or not a companion app is connected. This used to
// fire only in the standalone (else) branch, so a contact discovered while the
// phone app was attached over BLE gave the device's OWN screen no indication and
// never refreshed its Contacts list (issue #73). notify() flags the list dirty so
// UITask::loop rebuilds it when the Contacts tab is showing.
if (_ui) _ui->notify(UIEventType::newContactMessage);
// Mirror to the touch UI's Discovered store so the user can browse pending
// adverts and manually add nodes to contacts[] (used when auto-add is off
// or contacts[] is full). `is_new=true` here means the contact is NOT yet
@@ -1981,10 +1994,20 @@ void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, ui
// EXPLICIT per-send override (send_scope, set by the app's CMD_SET_FLOOD_SCOPE)
// is honoured here; otherwise unscoped. Channel/group floods keep default_scope
// — see the GroupChannel overload below — so public-channel containment is intact.
if (send_unscoped || send_scope.isNull()) {
if (send_unscoped) {
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1);
} else if (!send_scope.isNull()) {
sendFloodScoped(send_scope, pkt, delay_millis); // explicit per-send override (app CMD_SET_FLOOD_SCOPE)
} else if (scope_direct_floods) {
// Opt-in "single-region" mode (default OFF): tag these unicast floods with the node's
// default region scope so a region-scoped repeater that is the ONLY path will re-flood
// them (issue #64). sendFloodScoped() falls back to a plain unscoped flood when no region
// is configured. With the flag OFF this whole branch is skipped and behaviour is unchanged.
TransportKey default_scope;
memcpy(&default_scope.key, _prefs.default_scope_key, sizeof(default_scope.key));
sendFloodScoped(default_scope, pkt, delay_millis);
} else {
sendFloodScoped(send_scope, pkt, delay_millis); // explicit override only
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); // default: unscoped (cross-region safe)
}
}
void MyMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) {
@@ -2005,6 +2028,9 @@ void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t
const char *text) {
markConnectionActive(from); // in case this is from a server, and we have a connection
queueMessage(from, TXT_TYPE_PLAIN, pkt, sender_timestamp, NULL, 0, text);
#if defined(ESP32) && defined(MULTI_TRANSPORT_COMPANION)
mqtt_bridge.publishDM(from.name, from.id.pub_key, pkt->getSNR(), pkt->path_len, sender_timestamp, text);
#endif
}
void MyMesh::onCommandDataRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp,
@@ -2019,6 +2045,9 @@ void MyMesh::onSignedMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uin
// from.sync_since change needs to be persisted
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
queueMessage(from, TXT_TYPE_SIGNED_PLAIN, pkt, sender_timestamp, sender_prefix, 4, text);
#if defined(ESP32) && defined(MULTI_TRANSPORT_COMPANION)
mqtt_bridge.publishDM(from.name, from.id.pub_key, pkt->getSNR(), pkt->path_len, sender_timestamp, text);
#endif
}
void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint32_t timestamp,
@@ -2063,6 +2092,16 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle'
_serial->writeFrameToAll(frame, 1);
}
#if defined(ESP32) && defined(MULTI_TRANSPORT_COMPANION)
{
const char* _ch_name = "";
ChannelDetails _cd{};
if (getChannel(channel_idx, _cd)) _ch_name = _cd.name;
mqtt_bridge.publishChannel(channel_idx, _ch_name, pkt->getSNR(),
pkt->isRouteFlood() ? pkt->path_len : 0,
timestamp, text);
}
#endif
#ifdef DISPLAY_CLASS
// Get the channel name from the channel index
const char *channel_name = "Unknown";
@@ -2325,6 +2364,18 @@ void MyMesh::onRawDataRecv(mesh::Packet *packet) {
void MyMesh::onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags,
const uint8_t *path_snrs, const uint8_t *path_hashes, uint8_t path_len) {
uint8_t path_sz = flags & 0x03; // NEW v1.11+
// Silent SIGNAL PROBE result: a directed trace we sent to a neighbouring repeater
// has come back (it retransmitted; we heard it). Capture OUR reception of that
// reply — SNR + RSSI — as the live signal, and swallow it (no popup). This is the
// non-flooding replacement for the old flood-advert probe, and must run before the
// visible-ping path below.
if (tag != 0 && tag == _ui_sig_probe_tag) {
_ui_sig_probe_tag = 0;
_ui_sig_snr_q4 = (int8_t)(packet->getSNR() * 4.0f);
_ui_sig_rssi = (int8_t)_radio->getLastRSSI();
_ui_sig_ms = millis();
return;
}
#if defined(DISPLAY_CLASS)
// If this trace's tag matches the most recent UI-initiated ping, surface
// the bidirectional SNRs directly to the touch UI instead of (or in
@@ -4398,8 +4449,15 @@ bool MyMesh::sendAdvert(bool flood) {
}
if (!pkt) return false;
if (flood) {
// path_hash_size = path_hash_mode + 1 (matches the CMD_SEND_ADVERT path).
sendFlood(pkt, 0, (uint8_t)(_prefs.path_hash_mode + 1));
// Tag the flood advert with the node's default region scope — EXACTLY like the
// companion CMD_SEND_SELF_ADVERT path (see ~line 3007). Without this the UI's flood
// advert went out UNSCOPED, so region-scoped repeaters (denyf *) dropped it: an advert
// sent from the touch screen was never relayed, while the identical advert from the
// phone app (which DOES scope it) was. (Issue #68.) sendFloodScoped() falls back to a
// plain unscoped flood when no default region is configured, so this is a no-op then.
TransportKey default_scope;
memcpy(&default_scope.key, _prefs.default_scope_key, sizeof(default_scope.key));
sendFloodScoped(default_scope, pkt, 0);
} else {
sendZeroHop(pkt);
}
+49
View File
@@ -623,6 +623,48 @@ public:
return tag;
}
/** SIGNAL PROBE (the non-flooding way): trace-ping the best-reachable repeater so
* it retransmits — a real reply we can measure — WITHOUT flooding the mesh. A
* plain zero-hop advert gets no reply (nothing repeats it), so the signal never
* updates; a directed trace to a neighbour repeater does (it appends its RX-SNR
* and retransmits, which we hear). Picks the repeater with the shortest known
* path (0 = direct neighbour), breaking ties by most-recently-heard. The reply
* is captured silently in onTraceRecv via _ui_sig_probe_tag → _ui_sig_*. Returns
* the tag, or 0 if no repeater with a known path is available (the caller can
* fall back to a zero-hop advert). */
uint32_t uiSendSignalProbe() {
ContactInfo best; bool have = false; ContactInfo c;
const int n = getNumContacts();
for (int i = 0; i < n; i++) {
if (!getContactByIdx((uint32_t)i, c)) continue;
if (c.type != ADV_TYPE_REPEATER) continue; // only repeaters retransmit a trace
if (c.out_path_len == OUT_PATH_UNKNOWN) continue; // no known path → would need a flood
if (c.out_path_len > MAX_PATH_SIZE) continue; // guard a corrupt length
if (!have) { best = c; have = true; continue; }
if (c.out_path_len < best.out_path_len ||
(c.out_path_len == best.out_path_len &&
c.last_advert_timestamp > best.last_advert_timestamp)) {
best = c;
}
}
if (!have) return 0;
uint32_t tag = 0;
getRNG()->random((uint8_t*)&tag, sizeof(tag));
if (tag == 0) tag = 1;
uint8_t flags = (uint8_t)(_prefs.path_hash_mode & 0x03);
mesh::Packet* pkt = createTrace(tag, 0, flags);
if (!pkt) return 0;
_ui_sig_probe_tag = tag;
if (best.out_path_len > 0 && best.out_path_len <= MAX_PATH_SIZE) {
sendDirect(pkt, best.out_path, best.out_path_len); // directed along the known path
} else {
uint8_t hash_sz = (uint8_t)(_prefs.path_hash_mode + 1);
if (hash_sz == 0 || hash_sz > 4) hash_sz = 1;
sendDirect(pkt, best.id.pub_key, hash_sz); // direct neighbour → single hop
}
return tag;
}
/** Request CayenneLPP telemetry from a remote contact. Reply is delivered
* via AbstractUITask::onTelemetryReply with the raw LPP payload after the
* 4-byte timestamp header. Falls back to onPingReply if the UI didn't
@@ -651,6 +693,11 @@ public:
// a blank/NULL name clears it (unscoped, the default). Persists immediately.
void setDefaultFloodScope(const char* region_name);
// Opt-in: when true, direct/login/admin UNICAST floods are also tagged with the default
// region scope, so a region-scoped repeater that is your ONLY path will re-flood them.
// Default false keeps them unscoped (cross-region login/DMs work). (Issue #64.)
void setScopeDirectFloods(bool on) { scope_direct_floods = on; }
// Per-channel flood-scope override. pushChannelScope() derives a transient scope
// from "#region" (SHA256) for the NEXT channel send and returns true (the caller
// must popChannelScope() right after that send to restore); a blank/NULL name is
@@ -874,6 +921,7 @@ private:
bool _iter_started;
bool _cli_rescue;
bool send_unscoped; // force un-scoped flood (instead of using send_scope)
bool scope_direct_floods = false; // opt-in (#64): tag direct/login/admin floods with the default region scope
char cli_command[80];
uint8_t app_target_ver;
uint8_t *sign_data;
@@ -949,6 +997,7 @@ private:
// path. Single-slot is enough because the UI gates a new ping until the
// last one resolves or times out.
uint32_t _ui_trace_ping_tag = 0;
uint32_t _ui_sig_probe_tag = 0; // in-flight silent signal probe (updates _ui_sig_* only, no popup)
};
#if defined(ESP32_PLATFORM)
+1 -1
View File
@@ -3,6 +3,6 @@
/* Implementation lives in src/helpers/esp32/WifiRuntimeStore (NVS namespace meshcomod). */
#if defined(ESP32)
#if defined(WIFI_SSID) || defined(MULTI_TRANSPORT_COMPANION)
#include <helpers/esp32/WifiRuntimeStore.h>
#include "helpers/esp32/WifiRuntimeStore.h" // QUOTED: wadamesh's copy (wifiScan*Active etc.), not the lib's stale one
#endif
#endif
+215
View File
@@ -0,0 +1,215 @@
#if defined(ESP32) && defined(MULTI_TRANSPORT_COMPANION)
#include "MqttBridge.h"
#include <Preferences.h>
#include <WiFi.h>
#include <stdio.h>
#include <string.h>
#include "esp_random.h"
#include "mbedtls/gcm.h"
#include "mbedtls/md.h"
#include "mbedtls/base64.h"
MqttBridge mqtt_bridge;
// Read every field from the "mqtt" NVS namespace into the members. isKey() guards
// avoid the [E] NOT_FOUND log spam that getString emits for absent keys on the
// USB-CDC companion stream (see TouchPrefsStore prefsGetStr note).
void MqttBridge::loadConfig() {
_enabled = false; _pubDm = false; _pubChannel = true;
_host[0] = _user[0] = _pwd[0] = _psk[0] = '\0';
_port = 1883;
Preferences p;
if (p.begin("mqtt", true)) {
_enabled = p.getBool("en", false);
_port = (uint16_t)p.getUInt("port", 1883);
_pubDm = p.getBool("dm", false);
_pubChannel = p.getBool("ch", true);
if (p.isKey("host")) p.getString("host", _host, sizeof(_host));
if (p.isKey("user")) p.getString("user", _user, sizeof(_user));
if (p.isKey("pwd")) p.getString("pwd", _pwd, sizeof(_pwd));
if (p.isKey("psk")) p.getString("psk", _psk, sizeof(_psk));
p.end();
}
deriveKey();
if (!_enabled || _host[0] == '\0') _enabled = false;
}
// Derive the AES key from the passphrase. mbedtls_md() (generic digest) is used
// rather than mbedtls_sha256(), whose _ret suffix differs across mbedTLS 2.x/3.x.
void MqttBridge::deriveKey() {
_encOn = (_psk[0] != '\0');
if (!_encOn) { memset(_key, 0, sizeof(_key)); return; }
const mbedtls_md_info_t* info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
if (!info || mbedtls_md(info, (const unsigned char*)_psk, strlen(_psk), _key) != 0) {
memset(_key, 0, sizeof(_key));
_encOn = false; // can't derive → fail closed to plaintext-disabled publishing
}
}
void MqttBridge::begin(const char* nodeHex) {
strncpy(_nodeHex, nodeHex, sizeof(_nodeHex) - 1);
_nodeHex[sizeof(_nodeHex) - 1] = '\0';
loadConfig();
if (!_enabled) return;
_mqtt.setServer(_host, _port);
_mqtt.setKeepAlive(60);
_mqtt.setSocketTimeout(2); // bound connect/read — a dead broker must not stall loop()
_mqtt.setBufferSize(_encOn ? 1024 : 512); // sealed base64 needs the bigger buffer
Serial.printf("[MQTT] configured -> %s:%u dm=%d ch=%d enc=%d\n",
_host, _port, (int)_pubDm, (int)_pubChannel, (int)_encOn);
}
bool MqttBridge::reconnect() {
if (!_enabled || WiFi.status() != WL_CONNECTED) return false;
char clientId[32], lwtTopic[80];
snprintf(clientId, sizeof(clientId), "wadamesh-%s", _nodeHex);
snprintf(lwtTopic, sizeof(lwtTopic), "wadamesh/%s/status", _nodeHex);
bool ok = _user[0]
? _mqtt.connect(clientId, _user, _pwd, lwtTopic, 0, true, "offline")
: _mqtt.connect(clientId, nullptr, nullptr, lwtTopic, 0, true, "offline");
if (ok) {
_mqtt.publish(lwtTopic, "online", true);
Serial.printf("[MQTT] connected as %s\n", clientId);
} else {
Serial.printf("[MQTT] connect failed, rc=%d\n", _mqtt.state());
}
return ok;
}
void MqttBridge::loop() {
if (!_enabled) return;
if (!_mqtt.connected()) {
uint32_t now = millis();
if ((uint32_t)(now - _lastReconnectMs) >= RECONNECT_INTERVAL_MS) {
_lastReconnectMs = now;
reconnect();
}
return;
}
_mqtt.loop();
}
// AES-256-GCM seal: out = base64( nonce[12] || ciphertext || tag[16] ). Single-task
// use (mesh callbacks + loop() run on the same Arduino task), so the stack buffers
// are safe. Returns false on any crypto/size failure (caller then publishes nothing).
bool MqttBridge::sealToB64(const char* plain, char* out, size_t outCap) {
size_t plen = strlen(plain);
if (plen > 480) return false; // matches the json[] producers below
uint8_t blob[12 + 480 + 16];
esp_fill_random(blob, 12); // fresh nonce per message
uint8_t tag[16];
mbedtls_gcm_context gcm;
mbedtls_gcm_init(&gcm);
int rc = mbedtls_gcm_setkey(&gcm, MBEDTLS_CIPHER_ID_AES, _key, 256);
if (rc == 0) {
rc = mbedtls_gcm_crypt_and_tag(&gcm, MBEDTLS_GCM_ENCRYPT, plen,
blob, 12, nullptr, 0,
(const unsigned char*)plain, blob + 12,
16, tag);
}
mbedtls_gcm_free(&gcm);
if (rc != 0) return false;
memcpy(blob + 12 + plen, tag, 16);
size_t blobLen = 12 + plen + 16, olen = 0;
return mbedtls_base64_encode((unsigned char*)out, outCap, &olen, blob, blobLen) == 0;
}
void MqttBridge::pub(const char* subtopic, const char* json) {
if (!_enabled || !_mqtt.connected()) return;
char topic[80];
snprintf(topic, sizeof(topic), "wadamesh/%s/%s", _nodeHex, subtopic);
if (_encOn) {
char b64[720]; // base64 of the ~508-byte sealed blob
if (sealToB64(json, b64, sizeof(b64))) _mqtt.publish(topic, b64);
} else {
_mqtt.publish(topic, json);
}
}
void MqttBridge::escapeJson(const char* src, char* dst, size_t dstLen) {
static const char* hexd = "0123456789abcdef";
size_t j = 0;
for (size_t i = 0; src[i] && j + 7 < dstLen; ++i) { // +7: worst case is \u00XX (6) + NUL
unsigned char c = (unsigned char)src[i];
if (c == '"' || c == '\\') { dst[j++] = '\\'; dst[j++] = (char)c; }
else if (c == '\n') { dst[j++] = '\\'; dst[j++] = 'n'; }
else if (c == '\r') { dst[j++] = '\\'; dst[j++] = 'r'; }
else if (c == '\t') { dst[j++] = '\\'; dst[j++] = 't'; }
else if (c < 0x20) { // other control chars → \u00XX (valid JSON)
dst[j++] = '\\'; dst[j++] = 'u'; dst[j++] = '0'; dst[j++] = '0';
dst[j++] = hexd[(c >> 4) & 0xF]; dst[j++] = hexd[c & 0xF];
} else { dst[j++] = (char)c; }
}
dst[j] = '\0';
}
void MqttBridge::publishDM(const char* senderName, const uint8_t* senderKey32,
float snr, uint8_t hops, uint32_t ts, const char* text) {
if (!_enabled || !_pubDm || !_mqtt.connected()) return; // DMs are opt-in
char keyHex[13] = {};
for (int i = 0; i < 6; ++i) snprintf(keyHex + i * 2, 3, "%02x", senderKey32[i]);
char safeName[48], safeText[300];
escapeJson(senderName, safeName, sizeof(safeName));
escapeJson(text, safeText, sizeof(safeText));
char json[480];
snprintf(json, sizeof(json),
"{\"sender\":\"%s\",\"key\":\"%s\",\"snr\":%.1f,\"hops\":%u,\"ts\":%lu,\"text\":\"%s\"}",
safeName, keyHex, snr, (unsigned)hops, (unsigned long)ts, safeText);
pub("msg/dm", json);
}
void MqttBridge::publishChannel(int channelIdx, const char* channelName,
float snr, uint8_t hops, uint32_t ts, const char* text) {
if (!_enabled || !_pubChannel || !_mqtt.connected()) return; // channel publish toggle
char safeName[48], safeText[300];
escapeJson(channelName, safeName, sizeof(safeName));
escapeJson(text, safeText, sizeof(safeText));
char json[480];
snprintf(json, sizeof(json),
"{\"channel\":\"%s\",\"ch_idx\":%d,\"snr\":%.1f,\"hops\":%u,\"ts\":%lu,\"text\":\"%s\"}",
safeName, channelIdx, snr, (unsigned)hops, (unsigned long)ts, safeText);
pub("msg/ch", json);
}
void MqttBridge::saveConfig(const char* host, uint16_t port,
const char* user, const char* pwd,
bool pubDm, bool pubChannel, const char* psk, bool enable) {
Preferences p;
if (!p.begin("mqtt", false)) return;
p.putBool("en", enable);
p.putString("host", host);
p.putUInt("port", port);
p.putString("user", user);
p.putString("pwd", pwd);
p.putBool("dm", pubDm);
p.putBool("ch", pubChannel);
p.putString("psk", psk);
p.end();
}
void MqttBridge::reloadConfig() {
if (_mqtt.connected()) _mqtt.disconnect();
loadConfig();
if (!_enabled) { Serial.println("[MQTT] disabled"); return; }
_mqtt.setServer(_host, _port);
_mqtt.setKeepAlive(60);
_mqtt.setSocketTimeout(2);
_mqtt.setBufferSize(_encOn ? 1024 : 512);
_lastReconnectMs = 0; // reconnect on next loop() tick
Serial.printf("[MQTT] reloaded -> %s:%u en=%d dm=%d ch=%d enc=%d\n",
_host, _port, (int)_enabled, (int)_pubDm, (int)_pubChannel, (int)_encOn);
}
#endif // ESP32 && MULTI_TRANSPORT_COMPANION
+84
View File
@@ -0,0 +1,84 @@
#pragma once
#if defined(ESP32) && defined(MULTI_TRANSPORT_COMPANION)
#include <Arduino.h>
#include <PubSubClient.h>
#include <WiFiClient.h>
// Publishes received mesh messages to an MQTT broker over the existing WiFi link.
//
// PRIVACY MODEL (mirrors how Meshtastic's MQTT module guards content):
// - Master enable is opt-in (default off).
// - Channel messages publish by default; DIRECT MESSAGES are off by default and
// must be explicitly enabled — they are private 1:1 traffic, and may be from
// someone else who never consented to being bridged.
// - If an encryption key (PSK) is set, every JSON payload is sealed with
// AES-256-GCM (fresh random 12-byte nonce per message) before it leaves the
// device, so the broker only ever sees opaque base64. TLS is deliberately NOT
// used: the mbedTLS handshake (~30 KB heap) does not fit the ESP32-S3 budget
// alongside Wi-Fi + BLE + LVGL (same reason map tiles are HTTP-only). App-layer
// GCM is the lightweight, hardware-accelerated equivalent.
//
// Topics (QoS 0, retained where noted):
// wadamesh/{node_hex}/msg/dm — direct / signed messages (only if DM publish ON)
// wadamesh/{node_hex}/msg/ch — channel messages (only if channel publish ON)
// wadamesh/{node_hex}/status — LWT: "online" on connect, "offline" on drop (retained)
//
// ENCRYPTED PAYLOAD WIRE FORMAT (when a PSK is set):
// base64( nonce[12] || ciphertext[n] || tag[16] ), AES-256-GCM,
// key = SHA-256(psk). A subscriber (Home Assistant / Node-RED) decrypts with the
// same passphrase. With no PSK, the payload is the plain JSON (use a private broker).
//
// Config persisted in Preferences namespace "mqtt" (file-backed via SdNvsPrefs):
// en bool · host str · port u16 · user str · pwd str · dm bool · ch bool · psk str
//
// Call begin() once after the_mesh.begin() and SdNvsPrefs::useFile().
// Call loop() every iteration of the Arduino loop().
class MqttBridge {
public:
void begin(const char* nodeHex);
void loop();
void publishDM(const char* senderName, const uint8_t* senderKey32,
float snr, uint8_t hops, uint32_t ts, const char* text);
void publishChannel(int channelIdx, const char* channelName,
float snr, uint8_t hops, uint32_t ts, const char* text);
bool enabled() const { return _enabled; }
// Persist config (called from Settings UI save).
static void saveConfig(const char* host, uint16_t port,
const char* user, const char* pwd,
bool pubDm, bool pubChannel, const char* psk, bool enable);
// Re-read config from Preferences and reconnect (call after saveConfig).
void reloadConfig();
private:
WiFiClient _wc;
PubSubClient _mqtt{_wc};
char _nodeHex[13] = {}; // 6-byte key → 12 hex chars + '\0'
bool _enabled = false;
bool _pubDm = false; // DMs off by default (private 1:1 traffic)
bool _pubChannel = true; // channel messages on by default
char _host[64] = {};
char _user[32] = {};
char _pwd[32] = {};
char _psk[33] = {}; // passphrase; empty = no payload encryption
uint8_t _key[32] = {}; // SHA-256(psk), valid when _encOn
bool _encOn = false;
uint16_t _port = 1883;
uint32_t _lastReconnectMs = 0;
static const uint32_t RECONNECT_INTERVAL_MS = 15000;
void loadConfig(); // shared by begin() / reloadConfig()
void deriveKey(); // _key = SHA-256(_psk); sets _encOn
bool reconnect();
void pub(const char* subtopic, const char* json); // seals if _encOn
bool sealToB64(const char* plain, char* out, size_t outCap);
static void escapeJson(const char* src, char* dst, size_t dstLen);
};
extern MqttBridge mqtt_bridge;
#endif // ESP32 && MULTI_TRANSPORT_COMPANION
+259 -1
View File
@@ -35,7 +35,7 @@ static bool s_begun = false;
// short read (→ treat as absent → defaults); `ver` lets later builds add fields.
static const char* KEY_CFG = "cfg";
static const uint16_t TOUCH_CFG_MAGIC = 0x5743; // 'WC' (WadaCfg)
static const uint8_t TOUCH_CFG_VER = 21; // v2 sig_probe/poll; v3 tz_zone; v4 hide_node_name; v5 map_night/map_zoom; v6 map text/marker visibility; v7 app_grid_large; v8 ui_scale; v9 tb_keypad; v10 sleep_idle; v11 nav_keys; v12 map_zoom_buttons; v13 nav_dir_keys; v14 home_is_drawer; v15 kbd_nav default ON (one-time migrate); v16 nav_scroll_keys; v17 notify_new_contact; v18 kbd_nav OFF by default (reverses v15; T-Deck/V4 only, Tanmatsu stays on); v19 show_sensors_tab; v20 map_show_links; v21 map_style (0=OSM default, 1=OpenTopoMap)
static const uint8_t TOUCH_CFG_VER = 28; // v2 sig_probe/poll; v3 tz_zone; v4 hide_node_name; v5 map_night/map_zoom; v6 map text/marker visibility; v7 app_grid_large; v8 ui_scale; v9 tb_keypad; v10 sleep_idle; v11 nav_keys; v12 map_zoom_buttons; v13 nav_dir_keys; v14 home_is_drawer; v15 kbd_nav default ON (one-time migrate); v16 nav_scroll_keys; v17 notify_new_contact; v18 kbd_nav OFF by default (reverses v15; T-Deck/V4 only, Tanmatsu stays on); v19 show_sensors_tab; v20 map_show_links; v21 map_style (0=OSM default, 1=OpenTopoMap); v22 tb_nav; v23 scope_direct (opt-in: scope direct/login floods to the region); v24 tb_nav default OFF (experimental); v25 fem_lna (Heltec V4.3 high-gain FEM LNA, opt-in); v26 msg_flash (flash keyboard backlight + wake screen on a new message, opt-in); v27 flood_adv_hrs + local_adv_min (periodic self-advert intervals, the standard MeshCore flood/local advert on a timer); v28 beta_updates (opt-in to test/beta firmware on the OTA update check + install)
// Defaults (kept identical to the historical per-key defaults).
static const uint16_t DEFAULT_SCREEN_TIMEOUT_S = 20;
@@ -92,6 +92,13 @@ struct __attribute__((packed)) TouchCfg {
uint8_t show_sensors_tab; // V4 Expansion Kit: show the Sensors tab + Home env widget (bool, default 1) — v19 (trailing)
uint8_t map_show_links; // show self->contact link lines on the map (bool, default 1) — v20 (trailing)
uint8_t map_style; // map tile style: 0=OpenStreetMap (default), 1=OpenTopoMap — v21 (trailing)
uint8_t tb_nav; // T-Deck trackball: 1=D-pad UI navigation (default), 0=soft cursor — v22 (trailing)
uint8_t scope_direct; // 1=tag direct/login/admin floods with the default region scope (opt-in, default 0) — v23 (trailing)
uint8_t fem_lna; // Heltec V4.3 high-gain FEM LNA (~17 dB): 1=on, 0=bypass (default) — v25 (trailing)
uint8_t msg_flash; // flash keyboard backlight + wake screen on a new message (bool) — v26 (trailing)
uint8_t flood_adv_hrs; // periodic flood self-advert interval in hours (0 = off) — v27 (trailing)
uint16_t local_adv_min; // periodic zero-hop self-advert interval in minutes (0 = off) — v27 (trailing)
uint8_t beta_updates; // opt-in to test/beta firmware on the OTA check + install (bool) — v28 (trailing)
};
static TouchCfg s_cfg;
@@ -154,6 +161,13 @@ static void cfgSetDefaults(TouchCfg& c) {
#else
c.kbd_nav = 0; // T-Deck / V4: keyboard navigation OFF by default (opt-in; persists once toggled on)
#endif
c.tb_nav = 0; // T-Deck trackball: soft-cursor by default. D-pad UI nav is EXPERIMENTAL (opt-in)
c.scope_direct = 0; // OFF: direct/login floods stay unscoped (cross-region safe). Opt-in per issue #64.
c.fem_lna = 0; // OFF: V4.3 FEM LNA bypassed (matches the hardware default). Opt-in high-gain RX.
c.msg_flash = 0; // OFF: opt-in new-message keyboard/screen flash
c.flood_adv_hrs = 0; // OFF: no periodic flood self-advert (advertise manually)
c.local_adv_min = 0; // OFF: no periodic zero-hop self-advert
c.beta_updates = 0; // OFF: stable update channel (opt-in to beta/test firmware)
c.sleep_idle = 0; // default: idle light-sleep OFF
{ const char* d = "ertui"; for (int i = 0; i < 5; i++) c.nav_keys[i] = (uint8_t)d[i]; } // default tab hotkeys E/R/T/U/I
c.map_zoom_buttons = 0; // default: map zoom = slider
@@ -220,6 +234,18 @@ static void cfgLoadOrMigrate() {
#if !defined(HAS_TANMATSU)
if (s_cfg.ver < 18) s_cfg.kbd_nav = 0;
#endif
// v22: new trailing field — default the T-Deck trackball to D-pad UI nav on existing installs.
if (s_cfg.ver < 22) s_cfg.tb_nav = 1;
// v23: new trailing field — scope-direct-floods OFF on existing installs (opt-in).
if (s_cfg.ver < 23) s_cfg.scope_direct = 0;
// v24: trackball D-pad UI nav demoted to EXPERIMENTAL — default OFF (was on at v22). Flip
// existing installs back to the soft cursor; the toggle lets users opt back in.
if (s_cfg.ver < 24) s_cfg.tb_nav = 0;
// v25: new trailing field — V4.3 FEM LNA OFF on existing installs (matches hardware default).
if (s_cfg.ver < 25) s_cfg.fem_lna = 0;
if (s_cfg.ver < 26) s_cfg.msg_flash = 0;
if (s_cfg.ver < 27) { s_cfg.flood_adv_hrs = 0; s_cfg.local_adv_min = 0; }
if (s_cfg.ver < 28) s_cfg.beta_updates = 0;
s_cfg.ver = TOUCH_CFG_VER;
s_cfg.magic = TOUCH_CFG_MAGIC;
cfgFlush(); // rewrite with new fields defaulted-in
@@ -541,6 +567,39 @@ bool touchPrefsSetLockWallpaper(const char* path) {
return ok;
}
static const char* soundFileKey(int slot) {
// Distinct from the on/off keys snd_msg/snd_dm/snd_men (those are uchar) —
// a uchar and a String must not share an NVS key.
switch (slot) {
case TOUCH_SND_DM: return "sndf_dm";
case TOUCH_SND_MEN: return "sndf_men";
default: return "sndf_msg";
}
}
int touchPrefsGetSoundFile(int slot, char* out, int out_cap) {
if (!out || out_cap <= 0) return 0;
out[0] = '\0';
if (!s_begun) touchPrefsBegin();
String v = prefsGetStr(soundFileKey(slot), String(""));
int n = (int)v.length();
if (n > out_cap - 1) n = out_cap - 1;
if (n > TOUCH_SOUND_PATH_MAXLEN - 1) n = TOUCH_SOUND_PATH_MAXLEN - 1;
memcpy(out, v.c_str(), (size_t)n);
out[n] = '\0';
return n;
}
bool touchPrefsSetSoundFile(int slot, const char* path) {
if (!s_begun) touchPrefsBegin();
s_prefs.end();
if (!s_prefs.begin(TOUCH_NS, false)) return false;
bool ok;
if (!path || !path[0]) { s_prefs.remove(soundFileKey(slot)); ok = true; } // empty => built-in chime
else ok = s_prefs.putString(soundFileKey(slot), path) > 0;
s_prefs.end();
s_begun = s_prefs.begin(TOUCH_NS, true);
return ok;
}
int touchPrefsGetTimeOffsetHours() {
if (!s_begun) touchPrefsBegin();
int v = (int)s_cfg.time_offs;
@@ -878,6 +937,77 @@ bool touchPrefsSetKbdNav(bool on) {
s_cfg.kbd_nav = on ? 1 : 0;
return cfgFlush();
}
bool touchPrefsGetTbNav() {
if (!s_begun) touchPrefsBegin();
return s_cfg.tb_nav != 0;
}
bool touchPrefsSetTbNav(bool on) {
if (!s_begun) touchPrefsBegin();
s_cfg.tb_nav = on ? 1 : 0;
return cfgFlush();
}
bool touchPrefsGetScopeDirect() {
if (!s_begun) touchPrefsBegin();
return s_cfg.scope_direct != 0;
}
bool touchPrefsSetScopeDirect(bool on) {
if (!s_begun) touchPrefsBegin();
s_cfg.scope_direct = on ? 1 : 0;
return cfgFlush();
}
bool touchPrefsGetFemLna() {
if (!s_begun) touchPrefsBegin();
return s_cfg.fem_lna != 0;
}
bool touchPrefsSetFemLna(bool on) {
if (!s_begun) touchPrefsBegin();
s_cfg.fem_lna = on ? 1 : 0;
return cfgFlush();
}
bool touchPrefsGetMsgFlash() {
if (!s_begun) touchPrefsBegin();
return s_cfg.msg_flash != 0;
}
bool touchPrefsSetMsgFlash(bool on) {
if (!s_begun) touchPrefsBegin();
s_cfg.msg_flash = on ? 1 : 0;
return cfgFlush();
}
// Periodic self-advert intervals (0 = off). Validation mirrors MeshCore: flood in hours (cap 168);
// local zero-hop in minutes, 0 or 60-240 (MeshCore's MIN_LOCAL_ADVERT_INTERVAL is 60).
uint16_t touchPrefsGetFloodAdvHrs() {
if (!s_begun) touchPrefsBegin();
return s_cfg.flood_adv_hrs;
}
bool touchPrefsSetFloodAdvHrs(uint16_t hrs) {
if (!s_begun) touchPrefsBegin();
if (hrs > 168) hrs = 168;
s_cfg.flood_adv_hrs = (uint8_t)hrs;
return cfgFlush();
}
uint16_t touchPrefsGetLocalAdvMin() {
if (!s_begun) touchPrefsBegin();
return s_cfg.local_adv_min;
}
bool touchPrefsSetLocalAdvMin(uint16_t mins) {
if (!s_begun) touchPrefsBegin();
if (mins != 0) { if (mins < 60) mins = 60; if (mins > 240) mins = 240; }
s_cfg.local_adv_min = mins;
return cfgFlush();
}
bool touchPrefsGetBetaUpdates() {
if (!s_begun) touchPrefsBegin();
return s_cfg.beta_updates != 0;
}
bool touchPrefsSetBetaUpdates(bool on) {
if (!s_begun) touchPrefsBegin();
s_cfg.beta_updates = on ? 1 : 0;
return cfgFlush();
}
uint8_t touchPrefsGetNavKey(int tab) {
if (!s_begun) touchPrefsBegin();
@@ -1046,6 +1176,129 @@ bool touchPrefsSetWifiSlot(int idx, const char* label,
return true;
}
// ---- Saved "known networks" store (reworked iPhone/Android-style Wi-Fi) -----
// Keys per idx: "wn<idx>s" ssid, "wn<idx>p" pwd, "wn<idx>f" flags uchar
// (bit0 used, bit1 auto-join), "wn<idx>r" rank uint32. "wnctr" = global recency.
static void wifiNetKey(int idx, char kind, char out[8]) {
int p = 0;
out[p++] = 'w'; out[p++] = 'n';
out[p++] = (char)('0' + (idx & 0x07));
out[p++] = kind;
out[p] = '\0';
}
bool touchPrefsGetWifiNet(int idx, TouchWifiNet& out) {
memset(&out, 0, sizeof(out));
if (idx < 0 || idx >= TOUCH_WIFI_NET_COUNT) return false;
if (!s_begun) touchPrefsBegin();
char k[8];
wifiNetKey(idx, 'f', k);
const uint8_t flags = s_prefs.isKey(k) ? s_prefs.getUChar(k, 0) : 0;
out.used = (flags & 0x01) != 0;
out.auto_join = (flags & 0x02) != 0;
if (!out.used) return true;
wifiNetKey(idx, 's', k); { String v = prefsGetStr(k, ""); strlcpy(out.ssid, v.c_str(), sizeof(out.ssid)); }
wifiNetKey(idx, 'p', k); { String v = prefsGetStr(k, ""); strlcpy(out.pwd, v.c_str(), sizeof(out.pwd)); }
wifiNetKey(idx, 'r', k); out.rank = s_prefs.isKey(k) ? s_prefs.getUInt(k, 0) : 0;
return true;
}
int touchPrefsFindWifiNet(const char* ssid) {
if (!ssid || !ssid[0]) return -1;
TouchWifiNet n;
for (int i = 0; i < TOUCH_WIFI_NET_COUNT; ++i)
if (touchPrefsGetWifiNet(i, n) && n.used && strcmp(n.ssid, ssid) == 0) return i;
return -1;
}
int touchPrefsSaveWifiNet(const char* ssid, const char* pwd, bool auto_join) {
if (!ssid || !ssid[0]) return -1;
if (!s_begun) touchPrefsBegin();
TouchWifiNet n;
int idx = touchPrefsFindWifiNet(ssid); // update existing by ssid
if (idx < 0) { // else first free slot
for (int i = 0; i < TOUCH_WIFI_NET_COUNT && idx < 0; ++i)
if (touchPrefsGetWifiNet(i, n) && !n.used) idx = i;
}
if (idx < 0) { // else evict the least-recent
uint32_t lo = UINT32_MAX; int loi = 0;
for (int i = 0; i < TOUCH_WIFI_NET_COUNT; ++i) {
touchPrefsGetWifiNet(i, n);
if (n.rank <= lo) { lo = n.rank; loi = i; }
}
idx = loi;
}
// Preserve the existing passphrase if the caller passed an empty one (e.g. a
// metadata-only re-save).
char use_pwd[65];
if (pwd && pwd[0]) strlcpy(use_pwd, pwd, sizeof(use_pwd));
else { TouchWifiNet ex; use_pwd[0] = '\0'; if (touchPrefsGetWifiNet(idx, ex) && ex.used) strlcpy(use_pwd, ex.pwd, sizeof(use_pwd)); }
const uint32_t ctr = (s_prefs.isKey("wnctr") ? s_prefs.getUInt("wnctr", 0) : 0) + 1;
s_prefs.end();
if (!s_prefs.begin(TOUCH_NS, false)) { s_begun = s_prefs.begin(TOUCH_NS, true); return -1; }
char k[8];
wifiNetKey(idx, 's', k); s_prefs.putString(k, ssid);
wifiNetKey(idx, 'p', k); s_prefs.putString(k, use_pwd);
wifiNetKey(idx, 'f', k); s_prefs.putUChar(k, (uint8_t)(0x01 | (auto_join ? 0x02 : 0)));
wifiNetKey(idx, 'r', k); s_prefs.putUInt(k, ctr);
s_prefs.putUInt("wnctr", ctr);
s_prefs.end();
s_begun = s_prefs.begin(TOUCH_NS, true);
return idx;
}
bool touchPrefsForgetWifiNet(int idx) {
if (idx < 0 || idx >= TOUCH_WIFI_NET_COUNT) return false;
TouchWifiNet n;
const bool had = touchPrefsGetWifiNet(idx, n) && n.used;
if (!s_begun) touchPrefsBegin();
s_prefs.end();
if (!s_prefs.begin(TOUCH_NS, false)) { s_begun = s_prefs.begin(TOUCH_NS, true); return false; }
char k[8];
wifiNetKey(idx, 's', k); s_prefs.remove(k);
wifiNetKey(idx, 'p', k); s_prefs.remove(k);
wifiNetKey(idx, 'f', k); s_prefs.remove(k); // clears the used bit
wifiNetKey(idx, 'r', k); s_prefs.remove(k);
s_prefs.end();
s_begun = s_prefs.begin(TOUCH_NS, true);
// If this was the network the device is using/targeting, also drop the ACTIVE
// credentials so main.cpp stops trying to (re)connect to it (the reconnect loop
// is gated on wifiConfigHasRuntime(), which clearing makes false).
if (had && n.ssid[0]) {
char active[WIFI_CONFIG_SSID_MAX] = {0};
wifiConfigGetSsid(active, sizeof active);
if (strcmp(active, n.ssid) == 0) {
wifiConfigClear();
wifiConfigRequestApply();
}
}
return true;
}
bool touchPrefsSetWifiNetAutoJoin(int idx, bool on) {
TouchWifiNet n;
if (!touchPrefsGetWifiNet(idx, n) || !n.used) return false;
if (!s_begun) touchPrefsBegin();
s_prefs.end();
if (!s_prefs.begin(TOUCH_NS, false)) { s_begun = s_prefs.begin(TOUCH_NS, true); return false; }
char k[8];
wifiNetKey(idx, 'f', k); s_prefs.putUChar(k, (uint8_t)(0x01 | (on ? 0x02 : 0)));
s_prefs.end();
s_begun = s_prefs.begin(TOUCH_NS, true);
return true;
}
bool touchPrefsConnectWifiNet(int idx) {
TouchWifiNet n;
if (!touchPrefsGetWifiNet(idx, n) || !n.used || !n.ssid[0]) return false;
if (!wifiConfigSetSsid(n.ssid)) return false;
if (!wifiConfigSetPwd(n.pwd)) return false;
wifiConfigSetRadioEnabled(true);
wifiConfigRequestApply();
touchPrefsSaveWifiNet(n.ssid, n.pwd, n.auto_join); // re-save bumps recency
return true;
}
// Favorites blob (raw bytes: N * 6-byte pub_key prefixes, packed) ----------
static const char* KEY_FAV = "fav";
@@ -1270,6 +1523,11 @@ bool touchPrefsGetSoundMentions() {
return s_prefs.getUChar("snd_men", 1) != 0;
}
void touchPrefsSetSoundMentions(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("snd_men", on ? 1 : 0); }
bool touchPrefsGetSoundDirect() {
if (!s_begun) touchPrefsBegin();
return s_prefs.getUChar("snd_dm", 1) != 0;
}
void touchPrefsSetSoundDirect(bool on) { if (!s_begun) touchPrefsBegin(); prefsPutUChar("snd_dm", on ? 1 : 0); }
bool touchPrefsGetDiscoveredAutoEvict() {
if (!s_begun) touchPrefsBegin();
return s_prefs.getUChar("dsc_evict", 1) != 0;
+74
View File
@@ -127,6 +127,40 @@ bool touchPrefsSetUiScale(uint8_t scale);
bool touchPrefsGetKbdNav();
bool touchPrefsSetKbdNav(bool on);
/* T-Deck trackball mode: 1 = drive the focus-group D-pad nav (up/down/left/right move the
* selection, centre click selects — same logic as the Tanmatsu keypad); 0 = the soft mouse
* cursor. Default 1. Independent of the keyboard ESDFX nav above. Applied live (no reboot). */
bool touchPrefsGetTbNav();
bool touchPrefsSetTbNav(bool on);
/* Opt-in (issue #64): when true, direct/login/admin unicast floods are tagged with the
* node's default region scope so a region-scoped repeater that is your ONLY path will
* re-flood them. Default false (unscoped) — leaving it off keeps cross-region login/DMs. */
bool touchPrefsGetScopeDirect();
bool touchPrefsSetScopeDirect(bool on);
/* Heltec V4.3 high-gain FEM LNA (~17 dB external receive amplifier). Default false (bypassed,
* matching the hardware default). On = better sensitivity in quiet areas; can desensitize in
* noisy/urban areas. Only meaningful on V4.3 (board.femLnaControllable()). */
bool touchPrefsGetFemLna();
bool touchPrefsSetFemLna(bool on);
/* New-message notify flash (T-Deck): briefly light the keyboard backlight + wake the screen when a
* message arrives, like the Tanmatsu envelope LED. Opt-in (default off). */
bool touchPrefsGetMsgFlash();
bool touchPrefsSetMsgFlash(bool on);
/* Periodic self-advert intervals (the standard MeshCore flood/local advert, on a timer). 0 = off.
* Flood in hours; local zero-hop in minutes (0 or 60-240). Scheduled in UITask::loop via sendAdvert. */
uint16_t touchPrefsGetFloodAdvHrs();
bool touchPrefsSetFloodAdvHrs(uint16_t hrs);
uint16_t touchPrefsGetLocalAdvMin();
bool touchPrefsSetLocalAdvMin(uint16_t mins);
/* OTA update channel: opt-in to test/beta firmware. When on, the on-device update
* check AND the Install-update download both point at the beta channel (releases/BETA)
* instead of the stable channel (releases/TOUCH). Default off (stable). */
bool touchPrefsGetBetaUpdates();
bool touchPrefsSetBetaUpdates(bool on);
/* Keyboard-nav tab hotkeys: the ASCII key that jumps to each main tab while
* keyboard navigation is on. `tab` is the tab index 0..4 = chat / contacts / home
* / map / settings. Defaults E/R/T/U/I. Programmable in Settings → Keyboard. */
@@ -206,6 +240,36 @@ bool touchPrefsSetWifiSlot(int idx, const char* label,
* Returns false if idx out of range or the slot is empty. */
bool touchPrefsActivateWifiSlot(int idx);
/** Saved "known networks" (iPhone/Android-style) — the reworked Wi-Fi store.
* Up to 8 networks, each {ssid, passphrase, auto-join flag, recency rank}.
* Rank is a monotonic counter bumped on connect: highest = most recently used
* (drives the saved-list order + which entry is evicted when full). The "active"
* credentials still live in the meshcomod NVS namespace; connecting copies a
* saved network into it + requests an apply. ssid/pwd sizes mirror
* WIFI_CONFIG_SSID_MAX (32) / WIFI_CONFIG_PWD_MAX (64). */
constexpr int TOUCH_WIFI_NET_COUNT = 8;
struct TouchWifiNet {
bool used;
bool auto_join;
uint32_t rank;
char ssid[33];
char pwd[65];
};
/** Read saved network idx (0..7). out.used == false when the slot is empty. */
bool touchPrefsGetWifiNet(int idx, TouchWifiNet& out);
/** Index of the saved network with this exact ssid, or -1. */
int touchPrefsFindWifiNet(const char* ssid);
/** Add or update a network by ssid (preserves the old passphrase when pwd is
* empty), bumping its recency. Evicts the least-recent when full. -> idx / -1. */
int touchPrefsSaveWifiNet(const char* ssid, const char* pwd, bool auto_join);
/** Delete saved network idx. */
bool touchPrefsForgetWifiNet(int idx);
/** Set the auto-join flag on saved network idx. */
bool touchPrefsSetWifiNetAutoJoin(int idx, bool on);
/** Load saved network idx into the active credentials + request a reconnect,
* and bump its recency so a reboot reconnects to it. Returns false if empty. */
bool touchPrefsConnectWifiNet(int idx);
/** Favorite contacts. Identified by the first 6 bytes of their pubkey — same
* prefix the firmware uses for short-key contact lookups — so 16 favorites
* fit in a 96-byte NVS blob. Pure UI metadata; the firmware contact table
@@ -268,9 +332,19 @@ uint8_t touchPrefsGetDiscoveredMaxHops(); // auto-delete discovered nodes h
void touchPrefsSetDiscoveredMaxHops(uint8_t hops);
bool touchPrefsGetSoundMentions(); // default true
void touchPrefsSetSoundMentions(bool on);
bool touchPrefsGetSoundDirect(); // direct/DM chime on/off, default true
void touchPrefsSetSoundDirect(bool on);
uint8_t touchPrefsGetSoundVolume(); // 0..100, default 70
void touchPrefsSetSoundVolume(uint8_t vol);
/** Per-event notification sound FILE (empty = built-in chime). Slot:
* 0 = message, 1 = direct/DM, 2 = @-mention. Stored as a path pref like the
* lock wallpaper ("" | "/sounds/x.wav" | "sd:/x.wav"). T-Deck only. */
enum { TOUCH_SND_MSG = 0, TOUCH_SND_DM = 1, TOUCH_SND_MEN = 2 };
constexpr int TOUCH_SOUND_PATH_MAXLEN = 128;
int touchPrefsGetSoundFile(int slot, char* out, int out_cap);
bool touchPrefsSetSoundFile(int slot, const char* path);
/** Tanmatsu keyboard backlight brightness, 0–100 %. Default 100. Separate from
* screen brightness (touchPrefsGet/SetBrightness) and volume (…SoundVolume). */
uint8_t touchPrefsGetKbdBacklight();
+5 -1
View File
@@ -176,6 +176,10 @@ bool wifiConfigConsumeApplyRequest() {
return true;
}
static volatile bool s_wifi_scan_active = false;
void wifiScanSetActive(bool active) { s_wifi_scan_active = active; }
bool wifiScanIsActive() { return s_wifi_scan_active; }
void wifiConfigApply() {
#if defined(HAS_TANMATSU)
printf("[WIFI] apply radio_en=%d hasRuntime=%d mode=%d status=%d\n",
@@ -188,7 +192,7 @@ void wifiConfigApply() {
WiFi.mode(WIFI_OFF);
return;
}
if (!wifiConfigHasRuntime()) return;
if (!wifiConfigHasRuntime()) return; // no cred -> leave the STA alone (eraseap wedges this radio)
char ssid[WIFI_CONFIG_SSID_MAX];
char pwd[WIFI_CONFIG_PWD_MAX];
wifiConfigGetSsid(ssid, sizeof(ssid));
+7
View File
@@ -46,4 +46,11 @@ bool wifiConfigWantsWifi();
void wifiConfigRequestApply();
bool wifiConfigConsumeApplyRequest();
/* Set while a Wi-Fi scan runs on the worker task. The main loop's reconnect-retry
* must NOT call WiFi.disconnect()/begin() during a scan — that aborts the in-flight
* sweep (esp_wifi_scan_start gets cancelled), which is why a scan-while-connected
* returned 0 APs. The worker also disables setAutoReconnect for the same reason. */
void wifiScanSetActive(bool active);
bool wifiScanIsActive();
#endif
+23 -1
View File
@@ -66,6 +66,7 @@ static uint32_t _atoi(const char* sp) {
#ifdef ESP32
#ifdef MULTI_TRANSPORT_COMPANION
#include <helpers/esp32/MultiTransportCompanionInterface.h>
#include "helpers/esp32/MqttBridge.h"
MultiTransportCompanionInterface serial_interface;
#ifndef TCP_PORT
#define TCP_PORT 5000
@@ -410,6 +411,14 @@ void setup() {
);
Serial.println("[BOOT] mesh ok");
#if defined(ESP32) && defined(MULTI_TRANSPORT_COMPANION)
{
char nodeHex[13] = {};
for (int i = 0; i < 6; ++i) snprintf(nodeHex + i * 2, 3, "%02x", the_mesh.self_id.pub_key[i]);
mqtt_bridge.begin(nodeHex);
}
#endif
#if defined(WIFI_SSID) || defined(MULTI_TRANSPORT_COMPANION)
wifiConfigBegin();
Serial.println("[BOOT] wifiConfig ok");
@@ -608,7 +617,9 @@ void loop() {
}
}
// Automatic WiFi recovery for TCP mode: retry connection periodically if link drops.
if (wifiConfigHasRuntime() && WiFi.status() != WL_CONNECTED) {
// Suppressed while a scan runs on the worker: WiFi.disconnect()+begin() here would
// abort the in-flight sweep (the scan-while-connected "0 networks" bug).
if (wifiConfigHasRuntime() && WiFi.status() != WL_CONNECTED && !wifiScanIsActive()) {
uint32_t now = millis();
if ((uint32_t)(now - last_wifi_retry_ms) >= WIFI_RETRY_INTERVAL_MS) {
last_wifi_retry_ms = now;
@@ -676,8 +687,19 @@ void loop() {
serial_interface.startTcpServer(WiFi.status() == WL_CONNECTED);
serial_interface.tickWebSocketHandshake();
}
#endif
#if defined(HAS_TOUCH_UI)
// The touch-UI "Spectrum" RF analyzer borrows the radio while open: it sweeps
// the modem across the band, so the mesh must NOT touch the radio (re-tune /
// re-arm RX on the home channel) meanwhile. spectrumOwnsRadio() is true only
// while that app is up; the moment it closes it restores the mesh radio params
// and clears the flag, so the next the_mesh.loop() re-arms RX correctly.
if (!spectrumOwnsRadio())
#endif
the_mesh.loop();
#if defined(ESP32) && defined(MULTI_TRANSPORT_COMPANION)
mqtt_bridge.loop();
#endif
#if defined(GPS_BUF_DEBUG)
// Bench diagnostic (build with -DGPS_BUF_DEBUG only; absent in releases): peak GPS UART
// backlog accumulated between sensors.loop() drains. A peak above the old 256-byte default
+3440 -947
View File
File diff suppressed because it is too large. Load diff
+9
View File
@@ -386,6 +386,7 @@ public:
void setAdvertLocationPolicy(uint8_t policy);
void setPathHashMode(uint8_t mode);
void setExperimentalFlags(uint8_t multi_acks, uint8_t client_repeat, uint8_t rx_boosted);
void setTelemetryAllow(bool on); // answer mesh telemetry requests (battery+env; location stays separate)
/** Meshcomod CLI on device: `wifi on` / `wifi off`. */
bool setWifiRadio(bool on);
bool isTcpEnabled() const { return _serial && _serial->isTcpEnabled(); }
@@ -422,6 +423,7 @@ public:
bool sendAdvertNow(); // legacy: zero-hop
bool sendAdvertFlood(); // multi-hop flood
bool sendAdvertZeroHop(); // explicit zero-hop (same as sendAdvertNow)
bool sendSignalProbe(); // trace-ping the nearest repeater (non-flooding); falls back to zero-hop advert
void rebootDevice();
// Synchronously persist chat history to flash. Call before any path that
// restarts the device (Wi-Fi/BLE mode switch, etc.) so recent chat isn't
@@ -460,3 +462,10 @@ public:
void shutdown(bool restart = false);
};
// True while the touch-UI "Spectrum" RF-analyzer app owns the radio. main.cpp's
// loop() checks this and SKIPS the_mesh.loop() while it's true, so the mesh never
// re-tunes / re-arms RX on the home channel while the analyzer sweeps the band.
// Defined in UITask.cpp (returns a static flag set on open / cleared after the
// radio is restored to the mesh config on close).
bool spectrumOwnsRadio();
+116
View File
@@ -0,0 +1,116 @@
#pragma once
// =============================================================================
// device_caps.h — capability flags for the touch UI
//
// Gate UI features on what a board CAN DO, not on its model name. This replaces
// the scattered #if defined(HAS_TDECK_GT911) / HAS_TANMATSU / HELTEC_LORA_V4_TFT
// checks throughout UITask.cpp, so adding a new device becomes "fill in one
// capability block here" instead of hunting hundreds of device-name #ifs.
//
// Rules:
// * Every CAP_* is ALWAYS defined to 1 or 0 — gate with `#if CAP_X`, never
// `#if defined(CAP_X)` (it is always defined).
// * Caps newly factored out of the device-name macros are set per-board below.
// * Caps that already had a clean, device-neutral macro (HAS_EXPANSION_KIT,
// HAS_CC_*, HAS_UI_SOUND, MULTI_TRANSPORT_COMPANION, …) are ALIASED to it so
// new code can be written all-CAP; existing sites can migrate opportunistically.
// * Genuine one-off chip quirks (AppFS getSketchSize, FatFs f_mkfs, exact panel
// rotation constants) stay on the raw device macro — they are not reusable
// capabilities. Keep that allow-list short + commented at each site.
//
// The board -D macros (HAS_TDECK_GT911, HAS_TANMATSU, HELTEC_LORA_V4_TFT, …) come
// from platformio.ini / the Tanmatsu CMakeLists and are available everywhere, so
// this header has no include dependencies. Only ui-touch/UITask.cpp includes it,
// so the #else branch is always "the Heltec V4 TFT touch board".
// =============================================================================
// ---- Per-board structural capabilities (factored out of the device names) ----
#if defined(HAS_TDECK_GT911) // ===== LilyGo T-Deck (ESP32-S3) =====
#define CAP_TOUCH 1 // capacitive touchscreen (pointer input)
#define CAP_ROTATABLE 0 // panel is fixed landscape
#define CAP_LARGE_SCREEN 0 // 320x240
#define CAP_SD 1 // microSD on the shared SPI bus
#define CAP_FILESYSTEM 1 // browsable filesystem (the SD card)
#define CAP_GPS 1
#define CAP_OTA 1 // native dual-OTA slot
#define CAP_LOCK_SCREEN 1
#elif defined(HAS_TANMATSU) // ===== Tanmatsu (ESP32-P4) =====
#define CAP_TOUCH 0 // no touchscreen — keypad nav only
#define CAP_ROTATABLE 0 // fixed (software ROT_270 portrait->landscape)
#define CAP_LARGE_SCREEN 1 // 800x480 -> UI upscaling
#define CAP_SD 0
#define CAP_FILESYSTEM 1 // internal FFat partition
#define CAP_GPS 0 // no GPS module
#define CAP_OTA 0 // AppFS app — updated via launcher, no spare slot
#define CAP_LOCK_SCREEN 1
#else // ===== Heltec V4 TFT (default) =====
#define CAP_TOUCH 1 // capacitive touch panel
#define CAP_ROTATABLE 1 // user can flip portrait/landscape
#define CAP_LARGE_SCREEN 0 // 240x320
#define CAP_SD 0 // SPIFFS only
#define CAP_FILESYSTEM 0 // no browsable filesystem
#define CAP_GPS 1
#define CAP_OTA 1
#define CAP_LOCK_SCREEN 0
#endif
// ---- Derived input capabilities ---------------------------------------------
// Physical keyboard: T-Deck matrix OR Tanmatsu keypad.
#if defined(HAS_TDECK_KEYBOARD) || defined(HAS_TANMATSU)
#define CAP_KEYBOARD 1
#else
#define CAP_KEYBOARD 0
#endif
// Focus-group D-pad navigation (no pointer): Tanmatsu keypad OR T-Deck trackball.
#if defined(HAS_TANMATSU) || defined(HAS_TDECK_TRACKBALL)
#define CAP_KEYPAD_NAV 1
#else
#define CAP_KEYPAD_NAV 0
#endif
// ---- Capabilities aliased to existing device-neutral macros -----------------
// (behaviour-identical; lets new code use CAP_* while old sites migrate lazily)
#if defined(HAS_TDECK_TRACKBALL)
#define CAP_TRACKBALL 1
#else
#define CAP_TRACKBALL 0
#endif
#if defined(HAS_EXPANSION_KIT)
#define CAP_SENSORS 1
#else
#define CAP_SENSORS 0
#endif
#if defined(HAS_CC_BRIGHTNESS)
#define CAP_BACKLIGHT 1
#else
#define CAP_BACKLIGHT 0
#endif
#if defined(HAS_CC_VOLUME)
#define CAP_VOLUME 1
#else
#define CAP_VOLUME 0
#endif
#if defined(HAS_CC_KBD_BACKLIGHT)
#define CAP_KBD_BACKLIGHT 1
#else
#define CAP_KBD_BACKLIGHT 0
#endif
#if defined(HAS_UI_SOUND)
#define CAP_SOUND 1
#else
#define CAP_SOUND 0
#endif
#if defined(MULTI_TRANSPORT_COMPANION)
#define CAP_COMPANION 1
#else
#define CAP_COMPANION 0
#endif
+5 -1
View File
@@ -518,6 +518,8 @@ static const uint8_t e_1f5ff[] = {0,0,0,0,0,0,239,123,2,0,0,0,56,206,50,154,222,
static const lv_img_dsc_t d_1f5ff = { { LV_IMG_CF_TRUE_COLOR_ALPHA, 0, 0, 16, 16 }, sizeof(e_1f5ff), e_1f5ff };
static const uint8_t e_1f3f3[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,154,93,65,219,93,164,29,94,219,29,94,234,252,93,221,252,93,184,187,93,125,122,93,60,145,83,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,220,85,232,255,86,255,191,86,255,191,86,255,191,86,255,223,86,255,191,86,255,62,94,255,28,94,219,219,93,162,154,93,107,89,85,67,57,93,52,57,85,61,154,93,110,187,93,195,219,101,230,60,158,252,186,189,253,185,197,254,185,189,254,251,165,253,61,134,252,127,102,255,223,86,255,255,86,255,223,94,255,127,94,255,94,94,255,126,94,255,223,94,255,94,86,255,245,220,230,54,253,255,21,253,254,21,253,255,21,253,255,21,253,255,54,253,255,119,237,255,185,197,253,251,157,251,93,126,251,126,102,253,127,102,254,126,102,253,93,126,251,120,165,252,213,228,230,249,253,255,90,246,254,155,254,255,122,246,255,25,246,255,151,245,255,54,253,255,21,253,255,22,253,255,54,253,255,87,237,255,119,229,255,119,237,255,54,253,255,212,236,255,219,230,230,255,255,255,255,255,254,255,255,255,255,255,255,255,255,255,223,255,255,93,255,255,155,254,255,249,245,255,119,245,255,54,245,255,54,253,255,54,245,255,151,245,255,216,229,255,125,239,230,223,255,255,93,255,254,61,255,255,93,255,255,191,255,255,255,255,255,255,255,255,255,255,255,255,255,255,191,255,255,125,255,255,61,255,255,93,255,255,223,255,255,158,239,255,216,229,230,184,253,255,54,245,254,54,253,255,54,245,255,119,245,255,216,245,255,123,246,255,61,255,255,223,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,252,238,255,180,236,230,86,253,255,87,237,254,119,229,255,87,237,255,54,245,255,22,253,255,21,253,255,54,253,255,119,245,255,249,245,255,122,246,255,187,254,255,123,246,255,249,245,255,21,229,255,88,173,228,93,142,251,126,102,251,127,102,253,126,110,252,93,126,251,251,157,252,185,197,253,119,229,255,54,253,255,21,253,255,21,253,255,21,253,255,21,253,255,22,253,255,245,228,255,28,86,239,31,95,255,127,94,255,126,94,255,159,94,255,223,94,255,255,86,255,191,86,255,127,102,255,61,134,252,251,165,253,185,197,254,185,205,255,185,197,255,251,165,253,219,109,253,186,93,192,219,93,131,121,93,72,89,85,58,89,93,72,154,93,110,219,93,168,28,94,223,94,94,255,191,86,255,191,86,255,191,86,255,159,86,255,159,86,255,223,86,255,61,86,255,239,59,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,83,76,13,121,93,66,187,93,136,220,93,200,252,93,237,252,93,253,28,94,242,252,93,196,154,93,105};
static const lv_img_dsc_t d_1f3f3 = { { LV_IMG_CF_TRUE_COLOR_ALPHA, 0, 0, 16, 16 }, sizeof(e_1f3f3), e_1f3f3 };
static const uint8_t e_1f1f1[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19,1,255,19,1,255,19,1,255,19,1,255,19,1,255,19,1,255,19,1,255,19,1,255,19,1,255,19,1,255,19,1,255,19,1,255,19,1,255,19,1,255,19,1,255,19,1,255,20,1,255,20,1,255,20,1,255,20,1,255,20,1,255,20,1,255,20,1,255,19,1,255,19,1,255,20,1,255,20,1,255,20,1,255,20,1,255,20,1,255,20,1,255,20,1,255,179,0,255,179,0,255,179,0,255,179,0,255,179,0,255,179,0,255,179,0,255,212,0,255,212,0,255,179,0,255,179,0,255,179,0,255,179,0,255,179,0,255,179,0,255,179,0,255,220,181,255,220,181,255,220,181,255,220,181,255,220,181,255,220,181,255,220,181,255,118,132,255,118,132,255,220,181,255,220,181,255,220,181,255,220,181,255,220,181,255,220,181,255,220,181,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,19,165,255,19,165,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,223,255,255,223,255,255,223,255,255,223,255,255,223,255,255,223,247,255,255,255,255,138,82,255,105,74,255,255,255,255,223,247,255,223,255,255,223,255,255,223,255,255,223,255,255,223,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,118,181,255,65,16,255,65,16,255,86,181,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,248,182,255,248,182,255,248,182,255,248,182,255,248,182,255,248,182,255,114,133,255,13,84,255,45,84,255,114,133,255,248,182,255,248,182,255,248,182,255,248,182,255,248,182,255,248,182,255,103,4,255,103,4,255,103,4,255,103,4,255,103,4,255,103,4,255,200,4,255,232,4,255,232,4,255,200,4,255,103,4,255,103,4,255,103,4,255,103,4,255,103,4,255,103,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,136,4,255,136,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255,168,4,255};
static const lv_img_dsc_t d_1f1f1 = { { LV_IMG_CF_TRUE_COLOR_ALPHA, 0, 0, 16, 16 }, sizeof(e_1f1f1), e_1f1f1 };
typedef struct { uint32_t cp; const lv_img_dsc_t* dsc; } EmojiGlyph;
static const EmojiGlyph kGlyphs[] = {
@@ -551,6 +553,8 @@ static const EmojiGlyph kGlyphs[] = {
{ 0x2B55u, &d_2b55 },
{ 0xFE0Eu, &d_zero },
{ 0xFE0Fu, &d_zero },
{ 0x1F1F1u, &d_1f1f1 },
{ 0x1F1F8u, &d_zero },
{ 0x1F308u, &d_1f308 },
{ 0x1F30Au, &d_1f30a },
{ 0x1F319u, &d_1f319 },
@@ -782,7 +786,7 @@ static const EmojiGlyph kGlyphs[] = {
{ 0x1F9D0u, &d_1f9d0 },
{ 0x1F9E1u, &d_1f9e1 },
};
const uint16_t kEmojiGlyphCount = 256;
const uint16_t kEmojiGlyphCount = 257;
const lv_img_dsc_t* emojiGlyphLookup(uint32_t cp) {
int lo = 0, hi = (int)(sizeof(kGlyphs)/sizeof(kGlyphs[0])) - 1;
+1 -1
View File
@@ -1,5 +1,5 @@
// AUTO-GENERATED by scripts/build/gen-emoji-font.py — do not edit.
// 256 Noto colour emoji baked to RGB565+alpha at 16px.
// 257 Noto colour emoji baked to RGB565+alpha at 16px.
#pragma once
#include "lvgl.h"
#ifdef __cplusplus
+418 -417
View File
@@ -5,6 +5,7 @@
const char* const kUiLangNames[LANG_COUNT] = {
"English", "Nederlands", "Deutsch", "Français", "Español", "Italiano",
"Русский", "Українська", "Български", "Српски", "Ελληνικά",
"Português (BR)",
};
static uint8_t s_ui_lang = LANG_EN;
@@ -24,17 +25,17 @@ struct I18nEntry { const char* en; const char* tr[LANG_COUNT]; };
// NL DE FR ES IT RU UK BG SR EL
static const I18nEntry kI18n[] = {
{ "Profile", { 0, "Profiel", "Profil", "Profil", "Perfil", "Profilo", "Профиль", "Профіль", "Профил", "Профил", "Προφίλ" } },
{ "Radio & Mesh", { 0, "Radio & Mesh", "Funk & Mesh", "Radio et mesh", "Radio y mesh", "Radio e mesh", "Радио и меш", "Радіо та меш", "Радио и меш", "Радио и меш", "Ράδιο & Mesh" } },
{ "Display", { 0, "Scherm", "Anzeige", "Affichage", "Pantalla", "Schermo", "Экран", "Екран", "Дисплей", "Приказ", "Οθόνη" } },
{ "Keyboard", { 0, "Toetsenbord", "Tastatur", "Clavier", "Teclado", "Tastiera", "Клавиатура", "Клавіатура", "Клавиатура", "Тастатура", "Πληκτρολόγιο" } },
{ "Quick replies", { 0, "Snelle antwoorden","Schnellantworten", "Réponses rapides", "Respuestas rápidas", "Risposte rapide", "Быстрые ответы", "Швидкі відповіді", "Бързи отговори", "Брзи одговори", "Γρήγορες απαντήσεις" } },
{ "Sound", { 0, "Geluid", "Ton", "Son", "Sonido", "Suono", "Звук", "Звук", "Звук", "Звук", "Ήχος" } },
{ "Lock screen", { 0, "Vergrendelscherm", "Sperrbildschirm", "Écran de verrouillage", "Pantalla de bloqueo", "Schermata di blocco", "Экран блокировки", "Екран блокування", "Заключен екран", "Закључани екран", "Οθόνη κλειδώματος" } },
{ "System", { 0, "Systeem", "System", "Système", "Sistema", "Sistema", "Система", "Система", "Система", "Систем", "Σύστημα" } },
{ "About", { 0, "Over", "Über", "À propos", "Acerca de", "Informazioni", "О программе", "Про програму", "Относно", "О програму", "Σχετικά" } },
{ "Language", { 0, "Taal", "Sprache", "Langue", "Idioma", "Lingua", "Язык", "Мова", "Език", "Језик", "Γλώσσα" } },
{ "Settings", { 0, "Instellingen", "Einstellungen", "Paramètres", "Ajustes", "Impostazioni", "Настройки", "Налаштування", "Настройки", "Подешавања", "Ρυθμίσεις" } },
{ "Profile", { 0, "Profiel", "Profil", "Profil", "Perfil", "Profilo", "Профиль", "Профіль", "Профил", "Профил", "Προφίλ" ,"Perfil"} },
{ "Radio & Mesh", { 0, "Radio & Mesh", "Funk & Mesh", "Radio et mesh", "Radio y mesh", "Radio e mesh", "Радио и меш", "Радіо та меш", "Радио и меш", "Радио и меш", "Ράδιο & Mesh" ,"Rádio & Mesh"} },
{ "Display", { 0, "Scherm", "Anzeige", "Affichage", "Pantalla", "Schermo", "Экран", "Екран", "Дисплей", "Приказ", "Οθόνη" ,"Tela"} },
{ "Keyboard", { 0, "Toetsenbord", "Tastatur", "Clavier", "Teclado", "Tastiera", "Клавиатура", "Клавіатура", "Клавиатура", "Тастатура", "Πληκτρολόγιο" ,"Teclado"} },
{ "Quick replies", { 0, "Snelle antwoorden","Schnellantworten", "Réponses rapides", "Respuestas rápidas", "Risposte rapide", "Быстрые ответы", "Швидкі відповіді", "Бързи отговори", "Брзи одговори", "Γρήγορες απαντήσεις" ,"Respostas rápidas"} },
{ "Sound", { 0, "Geluid", "Ton", "Son", "Sonido", "Suono", "Звук", "Звук", "Звук", "Звук", "Ήχος" ,"Som"} },
{ "Lock screen", { 0, "Vergrendelscherm", "Sperrbildschirm", "Écran de verrouillage", "Pantalla de bloqueo", "Schermata di blocco", "Экран блокировки", "Екран блокування", "Заключен екран", "Закључани екран", "Οθόνη κλειδώματος" ,"Tela de bloqueio"} },
{ "System", { 0, "Systeem", "System", "Système", "Sistema", "Sistema", "Система", "Система", "Система", "Систем", "Σύστημα" ,"Sistema"} },
{ "About", { 0, "Over", "Über", "À propos", "Acerca de", "Informazioni", "О программе", "Про програму", "Относно", "О програму", "Σχετικά" ,"Sobre"} },
{ "Language", { 0, "Taal", "Sprache", "Langue", "Idioma", "Lingua", "Язык", "Мова", "Език", "Језик", "Γλώσσα" ,"Idioma"} },
{ "Settings", { 0, "Instellingen", "Einstellungen", "Paramètres", "Ajustes", "Impostazioni", "Настройки", "Налаштування", "Настройки", "Подешавања", "Ρυθμίσεις" ,"Ajustes"} },
{ "Tap a language; the device reboots to apply.",
{ 0, "Tik op een taal; het apparaat herstart.",
"Sprache antippen; Gerät startet neu.",
@@ -45,433 +46,433 @@ static const I18nEntry kI18n[] = {
"Виберіть мову; пристрій перезавантажиться.",
"Изберете език; устройството ще се рестартира.",
"Изаберите језик; уређај ће се рестартовати.",
"Πατήστε μια γλώσσα· η συσκευή επανεκκινεί." } },
"Πατήστε μια γλώσσα· η συσκευή επανεκκινεί." ,"Toque num idioma; o aparelho reinicia para aplicar."} },
// ---- Settings detail pages (sweep pass 1: Display / Keyboard / Sound / System) ----
{ "Save", { 0, "Opslaan","Speichern","Enregistrer","Guardar","Salva","Сохранить","Зберегти","Запази","Сачувај","Αποθήκευση" } },
{ "Toggle GPS", { 0, "GPS aan/uit","GPS umschalten","GPS activer/désactiver","Alternar GPS","Attiva/disattiva GPS","Переключить GPS","Перемкнути GPS","Превключи GPS","Укључи/искључи GPS","Εναλλαγή GPS" } },
{ "GPS serial baud", { 0, "GPS seriële baud","GPS-Baudrate","Débit série GPS","Baudios serie GPS","Baud seriale GPS","Скорость GPS (бод)","Швидкість GPS (бод)","GPS сериен baud","GPS серијски baud","GPS σειριακό baud" } },
{ "Sync clock from system", { 0, "Klok synchroniseren","Uhr synchronisieren","Synchroniser l'horloge","Sincronizar reloj","Sincronizza orologio","Синхронизировать часы","Синхронізувати годинник","Синхронизирай часовника","Синхронизуј сат","Συγχρονισμός ρολογιού" } },
{ "Send advert now", { 0, "Nu advert verzenden","Advert jetzt senden","Envoyer l'annonce","Enviar anuncio ahora","Invia advert ora","Отправить advert","Надіслати advert","Изпрати advert","Пошаљи advert","Αποστολή advert" } },
{ "Reboot device", { 0, "Apparaat herstarten","Gerät neu starten","Redémarrer l'appareil","Reiniciar dispositivo","Riavvia dispositivo","Перезагрузить устройство","Перезавантажити пристрій","Рестартирай устройството","Рестартуј уређај","Επανεκκίνηση συσκευής" } },
{ "Run setup again", { 0, "Setup opnieuw uitvoeren","Setup erneut ausführen","Relancer la configuration","Ejecutar configuración de nuevo","Esegui di nuovo la configurazione","Запустить настройку снова","Запустити налаштування знову","Стартирай настройката отново","Поново покрени подешавање","Εκτέλεση ρύθμισης ξανά" } },
{ "Distance in miles", { 0, "Afstand in mijlen","Entfernung in Meilen","Distance en miles","Distancia en millas","Distanza in miglia","Расстояние в милях","Відстань у милях","Разстояние в мили","Растојање у миљама","Απόσταση σε μίλια" } },
{ "Colourful chat bubbles", { 0, "Kleurrijke chatbubbels","Bunte Chat-Blasen","Bulles de chat colorées","Burbujas de chat coloridas","Fumetti chat colorati","Цветные пузыри чата","Кольорові бульбашки чату","Цветни чат балончета","Шарени облачићи ћаскања","Χρωματιστές φούσκες" } },
{ "Theme colour", { 0, "Themakleur","Themenfarbe","Couleur du thème","Color del tema","Colore tema","Цвет темы","Колір теми","Цвят на темата","Боја теме","Χρώμα θέματος" } },
{ "Pick colour", { 0, "Kies kleur","Farbe wählen","Choisir la couleur","Elegir color","Scegli colore","Выбрать цвет","Вибрати колір","Избери цвят","Изабери боју","Επιλογή χρώματος" } },
{ "Secondary keyboards", { 0, "Secundaire toetsenborden","Sekundäre Tastaturen","Claviers secondaires","Teclados secundarios","Tastiere secondarie","Дополнительные клавиатуры","Додаткові клавіатури","Допълнителни клавиатури","Секундарне тастатуре","Δευτερεύοντα πληκτρολόγια" } },
{ "Accent popups", { 0, "Accent-popups","Akzent-Popups","Fenêtres d'accents","Ventanas de acentos","Popup accenti","Подсказки акцентов","Підказки акцентів","Изскачащи акценти","Искачући акценти","Αναδυόμενα τόνων" } },
{ "Screen timeout (s, 0 = never)", { 0, "Schermtime-out (s, 0 = nooit)","Bildschirm-Timeout (s, 0 = nie)","Délai d'écran (s, 0 = jamais)","Tiempo de pantalla (s, 0 = nunca)","Timeout schermo (s, 0 = mai)","Тайм-аут экрана (с, 0 = никогда)","Тайм-аут екрана (с, 0 = ніколи)","Изключване на екрана (с, 0 = никога)","Истек екрана (с, 0 = никад)","Λήξη οθόνης (s, 0 = ποτέ)" } },
{ "Orientation (tap to rotate, reboots)", { 0, "Oriëntatie (tik om te draaien, herstart)","Ausrichtung (tippen zum Drehen, Neustart)","Orientation (toucher pour pivoter, redémarre)","Orientación (toca para girar, reinicia)","Orientamento (tocca per ruotare, riavvia)","Ориентация (нажмите для поворота, перезагрузка)","Орієнтація (торкніться, щоб обернути)","Ориентация (докоснете за завъртане, рестарт)","Оријентација (додирните за ротацију)","Προσανατολισμός (πατήστε για περιστροφή)" } },
{ "Lock screen wallpaper", { 0, "Vergrendelscherm-achtergrond","Sperrbildschirm-Hintergrund","Fond d'écran de verrouillage","Fondo de pantalla de bloqueo","Sfondo schermata di blocco","Обои экрана блокировки","Шпалери екрана блокування","Тапет на заключен екран","Позадина закључаног екрана","Ταπετσαρία οθόνης κλειδώματος" } },
{ "Lock text colour", { 0, "Tekstkleur vergrendeling","Sperrtext-Farbe","Couleur du texte de verrouillage","Color del texto de bloqueo","Colore testo blocco","Цвет текста блокировки","Колір тексту блокування","Цвят на текста при заключване","Боја текста закључавања","Χρώμα κειμένου κλειδώματος" } },
{ "Time offset (vs automatic)", { 0, "Tijdverschuiving (t.o.v. automatisch)","Zeitversatz (vs. automatisch)","Décalage horaire (vs auto)","Desfase horario (vs automático)","Sfasamento orario (vs automatico)","Смещение времени (от авто)","Зсув часу (від авто)","Изместване на времето (спрямо авто)","Помак времена (у односу на ауто)","Μετατόπιση ώρας (έναντι αυτόματης)" } },
{ "Store data on SD (reboot)", { 0, "Gegevens op SD opslaan (herstart)","Daten auf SD speichern (Neustart)","Stocker les données sur SD (redémarrage)","Guardar datos en SD (reinicio)","Salva dati su SD (riavvio)","Хранить данные на SD (перезагрузка)","Зберігати дані на SD (перезавантаження)","Съхранявай данните на SD (рестарт)","Чувај податке на SD (рестарт)","Αποθήκευση δεδομένων σε SD (επανεκκίνηση)" } },
{ "Calibrate battery (full = 100%)", { 0, "Batterij kalibreren (vol = 100%)","Batterie kalibrieren (voll = 100%)","Étalonner la batterie (plein = 100%)","Calibrar batería (lleno = 100%)","Calibra batteria (pieno = 100%)","Калибровать батарею (полная = 100%)","Калібрувати батарею (повна = 100%)","Калибрирай батерията (пълна = 100%)","Калибриши батерију (пуна = 100%)","Βαθμονόμηση μπαταρίας (πλήρης = 100%)" } },
{ "Sound: %s", { 0, "Geluid: %s","Ton: %s","Son : %s","Sonido: %s","Suono: %s","Звук: %s","Звук: %s","Звук: %s","Звук: %s","Ήχος: %s" } },
{ "on", { 0, "aan","an","activé","activado","attivo","вкл","увімк","включен","укључен","ενεργός" } },
{ "off", { 0, "uit","aus","désactivé","desactivado","disattivo","выкл","вимк","изключен","искључен","ανενεργός" } },
{ "Model:", { 0, "Model:","Modell:","Modèle :","Modelo:","Modello:","Модель:","Модель:","Модел:","Модел:","Μοντέλο:" } },
{ "Public key:", { 0, "Publieke sleutel:","Öffentl. Schlüssel:","Clé publique :","Clave pública:","Chiave pubblica:","Открытый ключ:","Відкритий ключ:","Публичен ключ:","Јавни кључ:","Δημόσιο κλειδί:" } },
{ "Contacts:", { 0, "Contacten:","Kontakte:","Contacts :","Contactos:","Contatti:","Контакты:","Контакти:","Контакти:","Контакти:","Επαφές:" } },
{ "Channels:", { 0, "Kanalen:","Kanäle:","Canaux :","Canales:","Canali:","Каналы:","Канали:","Канали:","Канали:","Κανάλια:" } },
{ "Battery:", { 0, "Batterij:","Batterie:","Batterie :","Batería:","Batteria:","Батарея:","Батарея:","Батерия:","Батерија:","Μπαταρία:" } },
{ "Device time:", { 0, "Apparaattijd:","Gerätezeit:","Heure de l'appareil :","Hora del dispositivo:","Ora dispositivo:","Время устройства:","Час пристрою:","Време на устройството:","Време уређаја:","Ώρα συσκευής:" } },
{ "Save", { 0, "Opslaan","Speichern","Enregistrer","Guardar","Salva","Сохранить","Зберегти","Запази","Сачувај","Αποθήκευση" ,"Salvar"} },
{ "Toggle GPS", { 0, "GPS aan/uit","GPS umschalten","GPS activer/désactiver","Alternar GPS","Attiva/disattiva GPS","Переключить GPS","Перемкнути GPS","Превключи GPS","Укључи/искључи GPS","Εναλλαγή GPS" ,"Alternar GPS"} },
{ "GPS serial baud", { 0, "GPS seriële baud","GPS-Baudrate","Débit série GPS","Baudios serie GPS","Baud seriale GPS","Скорость GPS (бод)","Швидкість GPS (бод)","GPS сериен baud","GPS серијски baud","GPS σειριακό baud" ,"Baud serial do GPS"} },
{ "Sync clock from system", { 0, "Klok synchroniseren","Uhr synchronisieren","Synchroniser l'horloge","Sincronizar reloj","Sincronizza orologio","Синхронизировать часы","Синхронізувати годинник","Синхронизирай часовника","Синхронизуј сат","Συγχρονισμός ρολογιού" ,"Sincronizar relógio do sistema"} },
{ "Send advert now", { 0, "Nu advert verzenden","Advert jetzt senden","Envoyer l'annonce","Enviar anuncio ahora","Invia advert ora","Отправить advert","Надіслати advert","Изпрати advert","Пошаљи advert","Αποστολή advert" ,"Enviar advert agora"} },
{ "Reboot device", { 0, "Apparaat herstarten","Gerät neu starten","Redémarrer l'appareil","Reiniciar dispositivo","Riavvia dispositivo","Перезагрузить устройство","Перезавантажити пристрій","Рестартирай устройството","Рестартуј уређај","Επανεκκίνηση συσκευής" ,"Reiniciar aparelho"} },
{ "Run setup again", { 0, "Setup opnieuw uitvoeren","Setup erneut ausführen","Relancer la configuration","Ejecutar configuración de nuevo","Esegui di nuovo la configurazione","Запустить настройку снова","Запустити налаштування знову","Стартирай настройката отново","Поново покрени подешавање","Εκτέλεση ρύθμισης ξανά" ,"Executar configuração de novo"} },
{ "Distance in miles", { 0, "Afstand in mijlen","Entfernung in Meilen","Distance en miles","Distancia en millas","Distanza in miglia","Расстояние в милях","Відстань у милях","Разстояние в мили","Растојање у миљама","Απόσταση σε μίλια" ,"Distância em milhas"} },
{ "Colourful chat bubbles", { 0, "Kleurrijke chatbubbels","Bunte Chat-Blasen","Bulles de chat colorées","Burbujas de chat coloridas","Fumetti chat colorati","Цветные пузыри чата","Кольорові бульбашки чату","Цветни чат балончета","Шарени облачићи ћаскања","Χρωματιστές φούσκες" ,"Balões de chat coloridos"} },
{ "Theme colour", { 0, "Themakleur","Themenfarbe","Couleur du thème","Color del tema","Colore tema","Цвет темы","Колір теми","Цвят на темата","Боја теме","Χρώμα θέματος" ,"Cor do tema"} },
{ "Pick colour", { 0, "Kies kleur","Farbe wählen","Choisir la couleur","Elegir color","Scegli colore","Выбрать цвет","Вибрати колір","Избери цвят","Изабери боју","Επιλογή χρώματος" ,"Escolher cor"} },
{ "Secondary keyboards", { 0, "Secundaire toetsenborden","Sekundäre Tastaturen","Claviers secondaires","Teclados secundarios","Tastiere secondarie","Дополнительные клавиатуры","Додаткові клавіатури","Допълнителни клавиатури","Секундарне тастатуре","Δευτερεύοντα πληκτρολόγια" ,"Teclados secundários"} },
{ "Accent popups", { 0, "Accent-popups","Akzent-Popups","Fenêtres d'accents","Ventanas de acentos","Popup accenti","Подсказки акцентов","Підказки акцентів","Изскачащи акценти","Искачући акценти","Αναδυόμενα τόνων" ,"Pop-ups de acento"} },
{ "Screen timeout (s, 0 = never)", { 0, "Schermtime-out (s, 0 = nooit)","Bildschirm-Timeout (s, 0 = nie)","Délai d'écran (s, 0 = jamais)","Tiempo de pantalla (s, 0 = nunca)","Timeout schermo (s, 0 = mai)","Тайм-аут экрана (с, 0 = никогда)","Тайм-аут екрана (с, 0 = ніколи)","Изключване на екрана (с, 0 = никога)","Истек екрана (с, 0 = никад)","Λήξη οθόνης (s, 0 = ποτέ)" ,"Tempo de tela (s, 0 = nunca)"} },
{ "Orientation (tap to rotate, reboots)", { 0, "Oriëntatie (tik om te draaien, herstart)","Ausrichtung (tippen zum Drehen, Neustart)","Orientation (toucher pour pivoter, redémarre)","Orientación (toca para girar, reinicia)","Orientamento (tocca per ruotare, riavvia)","Ориентация (нажмите для поворота, перезагрузка)","Орієнтація (торкніться, щоб обернути)","Ориентация (докоснете за завъртане, рестарт)","Оријентација (додирните за ротацију)","Προσανατολισμός (πατήστε για περιστροφή)" ,"Orientação (toque para girar, reinicia)"} },
{ "Lock screen wallpaper", { 0, "Vergrendelscherm-achtergrond","Sperrbildschirm-Hintergrund","Fond d'écran de verrouillage","Fondo de pantalla de bloqueo","Sfondo schermata di blocco","Обои экрана блокировки","Шпалери екрана блокування","Тапет на заключен екран","Позадина закључаног екрана","Ταπετσαρία οθόνης κλειδώματος" ,"Papel de parede do bloqueio"} },
{ "Lock text colour", { 0, "Tekstkleur vergrendeling","Sperrtext-Farbe","Couleur du texte de verrouillage","Color del texto de bloqueo","Colore testo blocco","Цвет текста блокировки","Колір тексту блокування","Цвят на текста при заключване","Боја текста закључавања","Χρώμα κειμένου κλειδώματος" ,"Cor do texto de bloqueio"} },
{ "Time offset (vs automatic)", { 0, "Tijdverschuiving (t.o.v. automatisch)","Zeitversatz (vs. automatisch)","Décalage horaire (vs auto)","Desfase horario (vs automático)","Sfasamento orario (vs automatico)","Смещение времени (от авто)","Зсув часу (від авто)","Изместване на времето (спрямо авто)","Помак времена (у односу на ауто)","Μετατόπιση ώρας (έναντι αυτόματης)" ,"Fuso horário (vs automático)"} },
{ "Store data on SD (reboot)", { 0, "Gegevens op SD opslaan (herstart)","Daten auf SD speichern (Neustart)","Stocker les données sur SD (redémarrage)","Guardar datos en SD (reinicio)","Salva dati su SD (riavvio)","Хранить данные на SD (перезагрузка)","Зберігати дані на SD (перезавантаження)","Съхранявай данните на SD (рестарт)","Чувај податке на SD (рестарт)","Αποθήκευση δεδομένων σε SD (επανεκκίνηση)" ,"Salvar dados no SD (reinicia)"} },
{ "Calibrate battery (full = 100%)", { 0, "Batterij kalibreren (vol = 100%)","Batterie kalibrieren (voll = 100%)","Étalonner la batterie (plein = 100%)","Calibrar batería (lleno = 100%)","Calibra batteria (pieno = 100%)","Калибровать батарею (полная = 100%)","Калібрувати батарею (повна = 100%)","Калибрирай батерията (пълна = 100%)","Калибриши батерију (пуна = 100%)","Βαθμονόμηση μπαταρίας (πλήρης = 100%)" ,"Calibrar bateria (cheia = 100%)"} },
{ "Sound: %s", { 0, "Geluid: %s","Ton: %s","Son : %s","Sonido: %s","Suono: %s","Звук: %s","Звук: %s","Звук: %s","Звук: %s","Ήχος: %s" ,"Som: %s"} },
{ "on", { 0, "aan","an","activé","activado","attivo","вкл","увімк","включен","укључен","ενεργός" ,"ligado"} },
{ "off", { 0, "uit","aus","désactivé","desactivado","disattivo","выкл","вимк","изключен","искључен","ανενεργός" ,"desligado"} },
{ "Model:", { 0, "Model:","Modell:","Modèle :","Modelo:","Modello:","Модель:","Модель:","Модел:","Модел:","Μοντέλο:" ,"Modelo:"} },
{ "Public key:", { 0, "Publieke sleutel:","Öffentl. Schlüssel:","Clé publique :","Clave pública:","Chiave pubblica:","Открытый ключ:","Відкритий ключ:","Публичен ключ:","Јавни кључ:","Δημόσιο κλειδί:" ,"Chave pública:"} },
{ "Contacts:", { 0, "Contacten:","Kontakte:","Contacts :","Contactos:","Contatti:","Контакты:","Контакти:","Контакти:","Контакти:","Επαφές:" ,"Contatos:"} },
{ "Channels:", { 0, "Kanalen:","Kanäle:","Canaux :","Canales:","Canali:","Каналы:","Канали:","Канали:","Канали:","Κανάλια:" ,"Canais:"} },
{ "Battery:", { 0, "Batterij:","Batterie:","Batterie :","Batería:","Batteria:","Батарея:","Батарея:","Батерия:","Батерија:","Μπαταρία:" ,"Bateria:"} },
{ "Device time:", { 0, "Apparaattijd:","Gerätezeit:","Heure de l'appareil :","Hora del dispositivo:","Ora dispositivo:","Время устройства:","Час пристрою:","Време на устройството:","Време уређаја:","Ώρα συσκευής:" ,"Hora do aparelho:"} },
// ---- Common buttons / actions (whole-OS) ----
{ "Close", { 0, "Sluiten","Schließen","Fermer","Cerrar","Chiudi","Закрыть","Закрити","Затвори","Затвори","Κλείσιμο" } },
{ "Cancel", { 0, "Annuleren","Abbrechen","Annuler","Cancelar","Annulla","Отмена","Скасувати","Отказ","Откажи","Άκυρο" } },
{ "Add", { 0, "Toevoegen","Hinzufügen","Ajouter","Añadir","Aggiungi","Добавить","Додати","Добави","Додај","Προσθήκη" } },
{ "Apply", { 0, "Toepassen","Anwenden","Appliquer","Aplicar","Applica","Применить","Застосувати","Приложи","Примени","Εφαρμογή" } },
{ "Clear", { 0, "Wissen","Löschen","Effacer","Borrar","Cancella","Очистить","Очистити","Изчисти","Обриши","Εκκαθάριση" } },
{ "Reset", { 0, "Herstellen","Zurücksetzen","Réinitialiser","Restablecer","Ripristina","Сброс","Скинути","Нулиране","Ресетуј","Επαναφορά" } },
{ "Create", { 0, "Aanmaken","Erstellen","Créer","Crear","Crea","Создать","Створити","Създай","Направи","Δημιουργία" } },
{ "Join", { 0, "Deelnemen","Beitreten","Rejoindre","Unirse","Unisciti","Присоединиться","Приєднатися","Присъедини се","Придружи се","Συμμετοχή" } },
{ "Find", { 0, "Zoeken","Suchen","Rechercher","Buscar","Trova","Найти","Знайти","Намери","Пронађи","Εύρεση" } },
{ "Unblock", { 0, "Deblokkeren","Entsperren","Débloquer","Desbloquear","Sblocca","Разблокировать","Розблокувати","Отблокирай","Одблокирај","Ξεμπλοκάρισμα" } },
{ "Add contact", { 0, "Contact toevoegen","Kontakt hinzufügen","Ajouter un contact","Añadir contacto","Aggiungi contatto","Добавить контакт","Додати контакт","Добави контакт","Додај контакт","Προσθήκη επαφής" } },
{ "Add channel", { 0, "Kanaal toevoegen","Kanal hinzufügen","Ajouter un canal","Añadir canal","Aggiungi canale","Добавить канал","Додати канал","Добави канал","Додај канал","Προσθήκη καναλιού" } },
{ "Apply radio params", { 0, "Radioparameters toepassen","Funkparameter anwenden","Appliquer les paramètres radio","Aplicar parámetros de radio","Applica parametri radio","Применить параметры радио","Застосувати параметри радіо","Приложи радио параметри","Примени радио параметре","Εφαρμογή παραμέτρων ράδιο" } },
{ "Export settings", { 0, "Instellingen exporteren","Einstellungen exportieren","Exporter les réglages","Exportar ajustes","Esporta impostazioni","Экспорт настроек","Експорт налаштувань","Експорт на настройки","Извези подешавања","Εξαγωγή ρυθμίσεων" } },
{ "Import settings", { 0, "Instellingen importeren","Einstellungen importieren","Importer les réglages","Importar ajustes","Importa impostazioni","Импорт настроек","Імпорт налаштувань","Импорт на настройки","Увези подешавања","Εισαγωγή ρυθμίσεων" } },
{ "Memory detail", { 0, "Geheugendetail","Speicherdetails","Détail mémoire","Detalle de memoria","Dettaglio memoria","Сведения о памяти","Деталі пам'яті","Подробности за паметта","Детаљи меморије","Λεπτομέρειες μνήμης" } },
{ "Map options", { 0, "Kaartopties","Kartenoptionen","Options de carte","Opciones de mapa","Opzioni mappa","Параметры карты","Параметри карти","Опции на картата","Опције мапе","Επιλογές χάρτη" } },
{ "Message info", { 0, "Berichtinfo","Nachrichteninfo","Infos du message","Info del mensaje","Info messaggio","Информация о сообщении","Інформація про повідомлення","Информация за съобщението","Подаци о поруци","Πληροφορίες μηνύματος" } },
{ "Channel settings", { 0, "Kanaalinstellingen","Kanaleinstellungen","Réglages du canal","Ajustes del canal","Impostazioni canale","Настройки канала","Налаштування каналу","Настройки на канала","Подешавања канала","Ρυθμίσεις καναλιού" } },
{ "Blocked users", { 0, "Geblokkeerde gebruikers","Blockierte Benutzer","Utilisateurs bloqués","Usuarios bloqueados","Utenti bloccati","Заблокированные","Заблоковані","Блокирани потребители","Блокирани корисници","Αποκλεισμένοι χρήστες" } },
{ "Save name", { 0, "Naam opslaan","Namen speichern","Enregistrer le nom","Guardar nombre","Salva nome","Сохранить имя","Зберегти ім'я","Запази името","Сачувај име","Αποθήκευση ονόματος" } },
{ "Save position", { 0, "Positie opslaan","Position speichern","Enregistrer la position","Guardar posición","Salva posizione","Сохранить позицию","Зберегти позицію","Запази позицията","Сачувај позицију","Αποθήκευση θέσης" } },
{ "Save policy", { 0, "Beleid opslaan","Richtlinie speichern","Enregistrer la règle","Guardar política","Salva criterio","Сохранить политику","Зберегти політику","Запази правилото","Сачувај правило","Αποθήκευση πολιτικής" } },
{ "Copy secret", { 0, "Geheim kopiëren","Geheimnis kopieren","Copier le secret","Copiar secreto","Copia segreto","Копировать секрет","Копіювати секрет","Копирай тайната","Копирај тајну","Αντιγραφή μυστικού" } },
{ "Select network", { 0, "Netwerk kiezen","Netzwerk wählen","Choisir le réseau","Seleccionar red","Seleziona rete","Выбрать сеть","Вибрати мережу","Избери мрежа","Изабери мрежу","Επιλογή δικτύου" } },
{ "Close", { 0, "Sluiten","Schließen","Fermer","Cerrar","Chiudi","Закрыть","Закрити","Затвори","Затвори","Κλείσιμο" ,"Fechar"} },
{ "Cancel", { 0, "Annuleren","Abbrechen","Annuler","Cancelar","Annulla","Отмена","Скасувати","Отказ","Откажи","Άκυρο" ,"Cancelar"} },
{ "Add", { 0, "Toevoegen","Hinzufügen","Ajouter","Añadir","Aggiungi","Добавить","Додати","Добави","Додај","Προσθήκη" ,"Adicionar"} },
{ "Apply", { 0, "Toepassen","Anwenden","Appliquer","Aplicar","Applica","Применить","Застосувати","Приложи","Примени","Εφαρμογή" ,"Aplicar"} },
{ "Clear", { 0, "Wissen","Löschen","Effacer","Borrar","Cancella","Очистить","Очистити","Изчисти","Обриши","Εκκαθάριση" ,"Limpar"} },
{ "Reset", { 0, "Herstellen","Zurücksetzen","Réinitialiser","Restablecer","Ripristina","Сброс","Скинути","Нулиране","Ресетуј","Επαναφορά" ,"Redefinir"} },
{ "Create", { 0, "Aanmaken","Erstellen","Créer","Crear","Crea","Создать","Створити","Създай","Направи","Δημιουργία" ,"Criar"} },
{ "Join", { 0, "Deelnemen","Beitreten","Rejoindre","Unirse","Unisciti","Присоединиться","Приєднатися","Присъедини се","Придружи се","Συμμετοχή" ,"Entrar"} },
{ "Find", { 0, "Zoeken","Suchen","Rechercher","Buscar","Trova","Найти","Знайти","Намери","Пронађи","Εύρεση" ,"Buscar"} },
{ "Unblock", { 0, "Deblokkeren","Entsperren","Débloquer","Desbloquear","Sblocca","Разблокировать","Розблокувати","Отблокирай","Одблокирај","Ξεμπλοκάρισμα" ,"Desbloquear"} },
{ "Add contact", { 0, "Contact toevoegen","Kontakt hinzufügen","Ajouter un contact","Añadir contacto","Aggiungi contatto","Добавить контакт","Додати контакт","Добави контакт","Додај контакт","Προσθήκη επαφής" ,"Adicionar contato"} },
{ "Add channel", { 0, "Kanaal toevoegen","Kanal hinzufügen","Ajouter un canal","Añadir canal","Aggiungi canale","Добавить канал","Додати канал","Добави канал","Додај канал","Προσθήκη καναλιού" ,"Adicionar canal"} },
{ "Apply radio params", { 0, "Radioparameters toepassen","Funkparameter anwenden","Appliquer les paramètres radio","Aplicar parámetros de radio","Applica parametri radio","Применить параметры радио","Застосувати параметри радіо","Приложи радио параметри","Примени радио параметре","Εφαρμογή παραμέτρων ράδιο" ,"Aplicar parâmetros de rádio"} },
{ "Export settings", { 0, "Instellingen exporteren","Einstellungen exportieren","Exporter les réglages","Exportar ajustes","Esporta impostazioni","Экспорт настроек","Експорт налаштувань","Експорт на настройки","Извези подешавања","Εξαγωγή ρυθμίσεων" ,"Exportar ajustes"} },
{ "Import settings", { 0, "Instellingen importeren","Einstellungen importieren","Importer les réglages","Importar ajustes","Importa impostazioni","Импорт настроек","Імпорт налаштувань","Импорт на настройки","Увези подешавања","Εισαγωγή ρυθμίσεων" ,"Importar ajustes"} },
{ "Memory detail", { 0, "Geheugendetail","Speicherdetails","Détail mémoire","Detalle de memoria","Dettaglio memoria","Сведения о памяти","Деталі пам'яті","Подробности за паметта","Детаљи меморије","Λεπτομέρειες μνήμης" ,"Detalhe de memória"} },
{ "Map options", { 0, "Kaartopties","Kartenoptionen","Options de carte","Opciones de mapa","Opzioni mappa","Параметры карты","Параметри карти","Опции на картата","Опције мапе","Επιλογές χάρτη" ,"Opções do mapa"} },
{ "Message info", { 0, "Berichtinfo","Nachrichteninfo","Infos du message","Info del mensaje","Info messaggio","Информация о сообщении","Інформація про повідомлення","Информация за съобщението","Подаци о поруци","Πληροφορίες μηνύματος" ,"Info da mensagem"} },
{ "Channel settings", { 0, "Kanaalinstellingen","Kanaleinstellungen","Réglages du canal","Ajustes del canal","Impostazioni canale","Настройки канала","Налаштування каналу","Настройки на канала","Подешавања канала","Ρυθμίσεις καναλιού" ,"Ajustes do canal"} },
{ "Blocked users", { 0, "Geblokkeerde gebruikers","Blockierte Benutzer","Utilisateurs bloqués","Usuarios bloqueados","Utenti bloccati","Заблокированные","Заблоковані","Блокирани потребители","Блокирани корисници","Αποκλεισμένοι χρήστες" ,"Usuários bloqueados"} },
{ "Save name", { 0, "Naam opslaan","Namen speichern","Enregistrer le nom","Guardar nombre","Salva nome","Сохранить имя","Зберегти ім'я","Запази името","Сачувај име","Αποθήκευση ονόματος" ,"Salvar nome"} },
{ "Save position", { 0, "Positie opslaan","Position speichern","Enregistrer la position","Guardar posición","Salva posizione","Сохранить позицию","Зберегти позицію","Запази позицията","Сачувај позицију","Αποθήκευση θέσης" ,"Salvar posição"} },
{ "Save policy", { 0, "Beleid opslaan","Richtlinie speichern","Enregistrer la règle","Guardar política","Salva criterio","Сохранить политику","Зберегти політику","Запази правилото","Сачувај правило","Αποθήκευση πολιτικής" ,"Salvar política"} },
{ "Copy secret", { 0, "Geheim kopiëren","Geheimnis kopieren","Copier le secret","Copiar secreto","Copia segreto","Копировать секрет","Копіювати секрет","Копирай тайната","Копирај тајну","Αντιγραφή μυστικού" ,"Copiar segredo"} },
{ "Select network", { 0, "Netwerk kiezen","Netzwerk wählen","Choisir le réseau","Seleccionar red","Seleziona rete","Выбрать сеть","Вибрати мережу","Избери мрежа","Изабери мрежу","Επιλογή δικτύου" ,"Selecionar rede"} },
// ---- Common status / toast messages (whole-OS) ----
{ "Added to contacts", { 0, "Toegevoegd aan contacten","Zu Kontakten hinzugefügt","Ajouté aux contacts","Añadido a contactos","Aggiunto ai contatti","Добавлено в контакты","Додано до контактів","Добавено в контактите","Додато у контакте","Προστέθηκε στις επαφές" } },
{ "Contact added", { 0, "Contact toegevoegd","Kontakt hinzugefügt","Contact ajouté","Contacto añadido","Contatto aggiunto","Контакт добавлен","Контакт додано","Контактът е добавен","Контакт додат","Η επαφή προστέθηκε" } },
{ "Contact deleted", { 0, "Contact verwijderd","Kontakt gelöscht","Contact supprimé","Contacto eliminado","Contatto eliminato","Контакт удалён","Контакт видалено","Контактът е изтрит","Контакт обрисан","Η επαφή διαγράφηκε" } },
{ "Channel created", { 0, "Kanaal aangemaakt","Kanal erstellt","Canal créé","Canal creado","Canale creato","Канал создан","Канал створено","Каналът е създаден","Канал направљен","Το κανάλι δημιουργήθηκε" } },
{ "Channel joined", { 0, "Deelgenomen aan kanaal","Kanal beigetreten","Canal rejoint","Unido al canal","Canale unito","Вы присоединились к каналу","Приєднано до каналу","Присъединено към канала","Придружено каналу","Συμμετοχή στο κανάλι" } },
{ "Clock synced", { 0, "Klok gesynchroniseerd","Uhr synchronisiert","Horloge synchronisée","Reloj sincronizado","Orologio sincronizzato","Часы синхронизированы","Годинник синхронізовано","Часовникът е синхронизиран","Сат синхронизован","Το ρολόι συγχρονίστηκε" } },
{ "Deleted", { 0, "Verwijderd","Gelöscht","Supprimé","Eliminado","Eliminato","Удалено","Видалено","Изтрито","Обрисано","Διαγράφηκε" } },
{ "Sent", { 0, "Verzonden","Gesendet","Envoyé","Enviado","Inviato","Отправлено","Надіслано","Изпратено","Послато","Στάλθηκε" } },
{ "Send failed", { 0, "Verzenden mislukt","Senden fehlgeschlagen","Échec de l'envoi","Error al enviar","Invio non riuscito","Не удалось отправить","Не вдалося надіслати","Изпращането неуспешно","Слање није успело","Αποτυχία αποστολής" } },
{ "Login failed", { 0, "Inloggen mislukt","Anmeldung fehlgeschlagen","Échec de connexion","Error de inicio de sesión","Accesso non riuscito","Ошибка входа","Помилка входу","Входът неуспешен","Пријава није успела","Αποτυχία σύνδεσης" } },
{ "Name saved", { 0, "Naam opgeslagen","Name gespeichert","Nom enregistré","Nombre guardado","Nome salvato","Имя сохранено","Ім'я збережено","Името е запазено","Име сачувано","Το όνομα αποθηκεύτηκε" } },
{ "Position saved", { 0, "Positie opgeslagen","Position gespeichert","Position enregistrée","Posición guardada","Posizione salvata","Позиция сохранена","Позицію збережено","Позицията е запазена","Позиција сачувана","Η θέση αποθηκεύτηκε" } },
{ "Setup complete", { 0, "Installatie voltooid","Einrichtung abgeschlossen","Configuration terminée","Configuración completa","Configurazione completata","Настройка завершена","Налаштування завершено","Настройката е готова","Подешавање завршено","Η ρύθμιση ολοκληρώθηκε" } },
{ "Slot empty", { 0, "Slot leeg","Slot leer","Emplacement vide","Ranura vacía","Slot vuoto","Слот пуст","Слот порожній","Слотът е празен","Слот празан","Κενή θέση" } },
{ "Screen locked", { 0, "Scherm vergrendeld","Bildschirm gesperrt","Écran verrouillé","Pantalla bloqueada","Schermo bloccato","Экран заблокирован","Екран заблоковано","Екранът е заключен","Екран закључан","Η οθόνη κλειδώθηκε" } },
{ "Out of memory", { 0, "Onvoldoende geheugen","Kein Speicher mehr","Mémoire insuffisante","Sin memoria","Memoria esaurita","Недостаточно памяти","Недостатньо пам'яті","Недостатъчна памет","Нема меморије","Ανεπαρκής μνήμη" } },
{ "Already exists", { 0, "Bestaat al","Existiert bereits","Existe déjà","Ya existe","Esiste già","Уже существует","Вже існує","Вече съществува","Већ постоји","Υπάρχει ήδη" } },
{ "Marked read", { 0, "Gemarkeerd als gelezen","Als gelesen markiert","Marqué comme lu","Marcado como leído","Segnato come letto","Отмечено прочитанным","Позначено прочитаним","Отбелязано като прочетено","Означено прочитаним","Σημειώθηκε ως αναγνωσμένο" } },
{ "All marked read", { 0, "Alles gelezen","Alle als gelesen","Tout marqué comme lu","Todo marcado como leído","Tutto segnato come letto","Всё прочитано","Усе прочитано","Всичко прочетено","Све означено","Όλα ως αναγνωσμένα" } },
{ "Enter a name", { 0, "Voer een naam in","Namen eingeben","Saisissez un nom","Introduce un nombre","Inserisci un nome","Введите имя","Введіть ім'я","Въведете име","Унесите име","Εισαγάγετε όνομα" } },
{ "Invalid lat/lon", { 0, "Ongeldige lat/lon","Ungültige Lat/Lon","Lat/lon invalide","Lat/lon no válida","Lat/lon non valida","Неверные коорд.","Невірні коорд.","Невалидни коорд.","Неисправне коорд.","Μη έγκυρες συντ." } },
{ "SD card inserted", { 0, "SD-kaart geplaatst","SD-Karte eingelegt","Carte SD insérée","Tarjeta SD insertada","Scheda SD inserita","SD-карта вставлена","SD-картку вставлено","SD картата е поставена","SD картица убачена","Η κάρτα SD εισήχθη" } },
{ "SD card removed", { 0, "SD-kaart verwijderd","SD-Karte entfernt","Carte SD retirée","Tarjeta SD retirada","Scheda SD rimossa","SD-карта извлечена","SD-картку вилучено","SD картата е извадена","SD картица уклоњена","Η κάρτα SD αφαιρέθηκε" } },
{ "Added to contacts", { 0, "Toegevoegd aan contacten","Zu Kontakten hinzugefügt","Ajouté aux contacts","Añadido a contactos","Aggiunto ai contatti","Добавлено в контакты","Додано до контактів","Добавено в контактите","Додато у контакте","Προστέθηκε στις επαφές" ,"Adicionado aos contatos"} },
{ "Contact added", { 0, "Contact toegevoegd","Kontakt hinzugefügt","Contact ajouté","Contacto añadido","Contatto aggiunto","Контакт добавлен","Контакт додано","Контактът е добавен","Контакт додат","Η επαφή προστέθηκε" ,"Contato adicionado"} },
{ "Contact deleted", { 0, "Contact verwijderd","Kontakt gelöscht","Contact supprimé","Contacto eliminado","Contatto eliminato","Контакт удалён","Контакт видалено","Контактът е изтрит","Контакт обрисан","Η επαφή διαγράφηκε" ,"Contato excluído"} },
{ "Channel created", { 0, "Kanaal aangemaakt","Kanal erstellt","Canal créé","Canal creado","Canale creato","Канал создан","Канал створено","Каналът е създаден","Канал направљен","Το κανάλι δημιουργήθηκε" ,"Canal criado"} },
{ "Channel joined", { 0, "Deelgenomen aan kanaal","Kanal beigetreten","Canal rejoint","Unido al canal","Canale unito","Вы присоединились к каналу","Приєднано до каналу","Присъединено към канала","Придружено каналу","Συμμετοχή στο κανάλι" ,"Entrou no canal"} },
{ "Clock synced", { 0, "Klok gesynchroniseerd","Uhr synchronisiert","Horloge synchronisée","Reloj sincronizado","Orologio sincronizzato","Часы синхронизированы","Годинник синхронізовано","Часовникът е синхронизиран","Сат синхронизован","Το ρολόι συγχρονίστηκε" ,"Relógio sincronizado"} },
{ "Deleted", { 0, "Verwijderd","Gelöscht","Supprimé","Eliminado","Eliminato","Удалено","Видалено","Изтрито","Обрисано","Διαγράφηκε" ,"Excluído"} },
{ "Sent", { 0, "Verzonden","Gesendet","Envoyé","Enviado","Inviato","Отправлено","Надіслано","Изпратено","Послато","Στάλθηκε" ,"Enviado"} },
{ "Send failed", { 0, "Verzenden mislukt","Senden fehlgeschlagen","Échec de l'envoi","Error al enviar","Invio non riuscito","Не удалось отправить","Не вдалося надіслати","Изпращането неуспешно","Слање није успело","Αποτυχία αποστολής" ,"Falha ao enviar"} },
{ "Login failed", { 0, "Inloggen mislukt","Anmeldung fehlgeschlagen","Échec de connexion","Error de inicio de sesión","Accesso non riuscito","Ошибка входа","Помилка входу","Входът неуспешен","Пријава није успела","Αποτυχία σύνδεσης" ,"Falha no login"} },
{ "Name saved", { 0, "Naam opgeslagen","Name gespeichert","Nom enregistré","Nombre guardado","Nome salvato","Имя сохранено","Ім'я збережено","Името е запазено","Име сачувано","Το όνομα αποθηκεύτηκε" ,"Nome salvo"} },
{ "Position saved", { 0, "Positie opgeslagen","Position gespeichert","Position enregistrée","Posición guardada","Posizione salvata","Позиция сохранена","Позицію збережено","Позицията е запазена","Позиција сачувана","Η θέση αποθηκεύτηκε" ,"Posição salva"} },
{ "Setup complete", { 0, "Installatie voltooid","Einrichtung abgeschlossen","Configuration terminée","Configuración completa","Configurazione completata","Настройка завершена","Налаштування завершено","Настройката е готова","Подешавање завршено","Η ρύθμιση ολοκληρώθηκε" ,"Configuração concluída"} },
{ "Slot empty", { 0, "Slot leeg","Slot leer","Emplacement vide","Ranura vacía","Slot vuoto","Слот пуст","Слот порожній","Слотът е празен","Слот празан","Κενή θέση" ,"Slot vazio"} },
{ "Screen locked", { 0, "Scherm vergrendeld","Bildschirm gesperrt","Écran verrouillé","Pantalla bloqueada","Schermo bloccato","Экран заблокирован","Екран заблоковано","Екранът е заключен","Екран закључан","Η οθόνη κλειδώθηκε" ,"Tela bloqueada"} },
{ "Out of memory", { 0, "Onvoldoende geheugen","Kein Speicher mehr","Mémoire insuffisante","Sin memoria","Memoria esaurita","Недостаточно памяти","Недостатньо пам'яті","Недостатъчна памет","Нема меморије","Ανεπαρκής μνήμη" ,"Sem memória"} },
{ "Already exists", { 0, "Bestaat al","Existiert bereits","Existe déjà","Ya existe","Esiste già","Уже существует","Вже існує","Вече съществува","Већ постоји","Υπάρχει ήδη" ,"Já existe"} },
{ "Marked read", { 0, "Gemarkeerd als gelezen","Als gelesen markiert","Marqué comme lu","Marcado como leído","Segnato come letto","Отмечено прочитанным","Позначено прочитаним","Отбелязано като прочетено","Означено прочитаним","Σημειώθηκε ως αναγνωσμένο" ,"Marcado como lido"} },
{ "All marked read", { 0, "Alles gelezen","Alle als gelesen","Tout marqué comme lu","Todo marcado como leído","Tutto segnato come letto","Всё прочитано","Усе прочитано","Всичко прочетено","Све означено","Όλα ως αναγνωσμένα" ,"Tudo marcado como lido"} },
{ "Enter a name", { 0, "Voer een naam in","Namen eingeben","Saisissez un nom","Introduce un nombre","Inserisci un nome","Введите имя","Введіть ім'я","Въведете име","Унесите име","Εισαγάγετε όνομα" ,"Digite um nome"} },
{ "Invalid lat/lon", { 0, "Ongeldige lat/lon","Ungültige Lat/Lon","Lat/lon invalide","Lat/lon no válida","Lat/lon non valida","Неверные коорд.","Невірні коорд.","Невалидни коорд.","Неисправне коорд.","Μη έγκυρες συντ." ,"Lat/lon inválida"} },
{ "SD card inserted", { 0, "SD-kaart geplaatst","SD-Karte eingelegt","Carte SD insérée","Tarjeta SD insertada","Scheda SD inserita","SD-карта вставлена","SD-картку вставлено","SD картата е поставена","SD картица убачена","Η κάρτα SD εισήχθη" ,"Cartão SD inserido"} },
{ "SD card removed", { 0, "SD-kaart verwijderd","SD-Karte entfernt","Carte SD retirée","Tarjeta SD retirada","Scheda SD rimossa","SD-карта извлечена","SD-картку вилучено","SD картата е извадена","SD картица уклоњена","Η κάρτα SD αφαιρέθηκε" ,"Cartão SD removido"} },
// ---- Common field labels / status words ----
{ "Enable Bluetooth", { 0, "Bluetooth inschakelen","Bluetooth aktivieren","Activer le Bluetooth","Activar Bluetooth","Attiva Bluetooth","Включить Bluetooth","Увімкнути Bluetooth","Включи Bluetooth","Укључи Bluetooth","Ενεργοποίηση Bluetooth" } },
{ "Network name (SSID)", { 0, "Netwerknaam (SSID)","Netzwerkname (SSID)","Nom du réseau (SSID)","Nombre de red (SSID)","Nome rete (SSID)","Имя сети (SSID)","Назва мережі (SSID)","Име на мрежата (SSID)","Назив мреже (SSID)","Όνομα δικτύου (SSID)" } },
{ "Password (empty = open)", { 0, "Wachtwoord (leeg = open)","Passwort (leer = offen)","Mot de passe (vide = ouvert)","Contraseña (vacío = abierta)","Password (vuoto = aperta)","Пароль (пусто = открытая)","Пароль (порожньо = відкрита)","Парола (празно = отворена)","Лозинка (празно = отворена)","Κωδικός (κενό = ανοιχτό)" } },
{ "Saved profiles", { 0, "Opgeslagen profielen","Gespeicherte Profile","Profils enregistrés","Perfiles guardados","Profili salvati","Сохранённые профили","Збережені профілі","Запазени профили","Сачувани профили","Αποθηκευμένα προφίλ" } },
{ "Channel name", { 0, "Kanaalnaam","Kanalname","Nom du canal","Nombre del canal","Nome canale","Имя канала","Назва каналу","Име на канала","Назив канала","Όνομα καναλιού" } },
{ "Max hops (0..64)", { 0, "Max hops (0..64)","Max. Hops (0..64)","Sauts max (0..64)","Saltos máx (0..64)","Hop max (0..64)","Макс. хопов (0..64)","Макс. хопів (0..64)","Макс. скокове (0..64)","Макс. скокова (0..64)","Μέγ. άλματα (0..64)" } },
{ "Offline", { 0, "Offline","Offline","Hors ligne","Sin conexión","Offline","Не в сети","Офлайн","Офлайн","Ван мреже","Εκτός σύνδεσης" } },
{ "Name", { 0, "Naam","Name","Nom","Nombre","Nome","Имя","Ім'я","Име","Име","Όνομα" } },
{ "Status", { 0, "Status","Status","État","Estado","Stato","Статус","Статус","Състояние","Статус","Κατάσταση" } },
{ "Storage", { 0, "Opslag","Speicher","Stockage","Almacenamiento","Archiviazione","Хранилище","Сховище","Хранилище","Складиште","Αποθηκευτικός χώρος" } },
{ "Diagnostics", { 0, "Diagnostiek","Diagnose","Diagnostics","Diagnóstico","Diagnostica","Диагностика","Діагностика","Диагностика","Дијагностика","Διαγνωστικά" } },
{ "Active", { 0, "Actief","Aktiv","Actif","Activo","Attivo","Активно","Активно","Активно","Активно","Ενεργό" } },
{ "Inactive", { 0, "Inactief","Inaktiv","Inactif","Inactivo","Inattivo","Неактивно","Неактивно","Неактивно","Неактивно","Ανενεργό" } },
{ "New", { 0, "Nieuw","Neu","Nouveau","Nuevo","Nuovo","Новое","Нове","Ново","Ново","Νέο" } },
{ "About the map", { 0, "Over de kaart","Über die Karte","À propos de la carte","Acerca del mapa","Informazioni sulla mappa","О карте","Про карту","Относно картата","О мапи","Σχετικά με τον χάρτη" } },
{ "Show link lines", { 0, "Verbindingslijnen tonen","Verbindungslinien zeigen","Afficher les liens","Mostrar líneas de enlace","Mostra linee di collegamento","Показывать линии связи","Показувати лінії зв'язку","Показвай линиите на връзката","Прикажи линије веза","Εμφάνιση γραμμών σύνδεσης" } },
{ "Tiles from SD card", { 0, "Tegels van SD-kaart","Kacheln von SD-Karte","Tuiles depuis la carte SD","Teselas desde tarjeta SD","Riquadri da scheda SD","Тайлы с SD-карты","Плитки з SD-картки","Плочки от SD картата","Плочице са SD картице","Πλακίδια από κάρτα SD" } },
{ "Quick reply", { 0, "Snel antwoord","Schnellantwort","Réponse rapide","Respuesta rápida","Risposta rapida","Быстрый ответ","Швидка відповідь","Бърз отговор","Брз одговор","Γρήγορη απάντηση" } },
{ "Enable Bluetooth", { 0, "Bluetooth inschakelen","Bluetooth aktivieren","Activer le Bluetooth","Activar Bluetooth","Attiva Bluetooth","Включить Bluetooth","Увімкнути Bluetooth","Включи Bluetooth","Укључи Bluetooth","Ενεργοποίηση Bluetooth" ,"Ativar Bluetooth"} },
{ "Network name (SSID)", { 0, "Netwerknaam (SSID)","Netzwerkname (SSID)","Nom du réseau (SSID)","Nombre de red (SSID)","Nome rete (SSID)","Имя сети (SSID)","Назва мережі (SSID)","Име на мрежата (SSID)","Назив мреже (SSID)","Όνομα δικτύου (SSID)" ,"Nome da rede (SSID)"} },
{ "Password (empty = open)", { 0, "Wachtwoord (leeg = open)","Passwort (leer = offen)","Mot de passe (vide = ouvert)","Contraseña (vacío = abierta)","Password (vuoto = aperta)","Пароль (пусто = открытая)","Пароль (порожньо = відкрита)","Парола (празно = отворена)","Лозинка (празно = отворена)","Κωδικός (κενό = ανοιχτό)" ,"Senha (vazio = aberta)"} },
{ "Saved profiles", { 0, "Opgeslagen profielen","Gespeicherte Profile","Profils enregistrés","Perfiles guardados","Profili salvati","Сохранённые профили","Збережені профілі","Запазени профили","Сачувани профили","Αποθηκευμένα προφίλ" ,"Perfis salvos"} },
{ "Channel name", { 0, "Kanaalnaam","Kanalname","Nom du canal","Nombre del canal","Nome canale","Имя канала","Назва каналу","Име на канала","Назив канала","Όνομα καναλιού" ,"Nome do canal"} },
{ "Max hops (0..64)", { 0, "Max hops (0..64)","Max. Hops (0..64)","Sauts max (0..64)","Saltos máx (0..64)","Hop max (0..64)","Макс. хопов (0..64)","Макс. хопів (0..64)","Макс. скокове (0..64)","Макс. скокова (0..64)","Μέγ. άλματα (0..64)" ,"Máx. saltos (0..64)"} },
{ "Offline", { 0, "Offline","Offline","Hors ligne","Sin conexión","Offline","Не в сети","Офлайн","Офлайн","Ван мреже","Εκτός σύνδεσης" ,"Off-line"} },
{ "Name", { 0, "Naam","Name","Nom","Nombre","Nome","Имя","Ім'я","Име","Име","Όνομα" ,"Nome"} },
{ "Status", { 0, "Status","Status","État","Estado","Stato","Статус","Статус","Състояние","Статус","Κατάσταση" ,"Status"} },
{ "Storage", { 0, "Opslag","Speicher","Stockage","Almacenamiento","Archiviazione","Хранилище","Сховище","Хранилище","Складиште","Αποθηκευτικός χώρος" ,"Armazenamento"} },
{ "Diagnostics", { 0, "Diagnostiek","Diagnose","Diagnostics","Diagnóstico","Diagnostica","Диагностика","Діагностика","Диагностика","Дијагностика","Διαγνωστικά" ,"Diagnóstico"} },
{ "Active", { 0, "Actief","Aktiv","Actif","Activo","Attivo","Активно","Активно","Активно","Активно","Ενεργό" ,"Ativo"} },
{ "Inactive", { 0, "Inactief","Inaktiv","Inactif","Inactivo","Inattivo","Неактивно","Неактивно","Неактивно","Неактивно","Ανενεργό" ,"Inativo"} },
{ "New", { 0, "Nieuw","Neu","Nouveau","Nuevo","Nuovo","Новое","Нове","Ново","Ново","Νέο" ,"Novo"} },
{ "About the map", { 0, "Over de kaart","Über die Karte","À propos de la carte","Acerca del mapa","Informazioni sulla mappa","О карте","Про карту","Относно картата","О мапи","Σχετικά με τον χάρτη" ,"Sobre o mapa"} },
{ "Show link lines", { 0, "Verbindingslijnen tonen","Verbindungslinien zeigen","Afficher les liens","Mostrar líneas de enlace","Mostra linee di collegamento","Показывать линии связи","Показувати лінії зв'язку","Показвай линиите на връзката","Прикажи линије веза","Εμφάνιση γραμμών σύνδεσης" ,"Mostrar linhas de enlace"} },
{ "Tiles from SD card", { 0, "Tegels van SD-kaart","Kacheln von SD-Karte","Tuiles depuis la carte SD","Teselas desde tarjeta SD","Riquadri da scheda SD","Тайлы с SD-карты","Плитки з SD-картки","Плочки от SD картата","Плочице са SD картице","Πλακίδια από κάρτα SD" ,"Tiles do cartão SD"} },
{ "Quick reply", { 0, "Snel antwoord","Schnellantwort","Réponse rapide","Respuesta rápida","Risposta rapida","Быстрый ответ","Швидка відповідь","Бърз отговор","Брз одговор","Γρήγορη απάντηση" ,"Resposta rápida"} },
// ---- Sweep pass 3: chats / contacts / wifi / map / lock — alerts + labels ----
{ "Contacts", { 0, "Contacten","Kontakte","Contacts","Contactos","Contatti","Контакты","Контакти","Контакти","Контакти","Επαφές" } },
{ "Commands", { 0, "Opdrachten","Befehle","Commandes","Comandos","Comandi","Команды","Команди","Команди","Команде","Εντολές" } },
{ "Editing", { 0, "Bewerken","Bearbeiten","Modification","Editando","Modifica","Редактирование","Редагування","Редактиране","Уређивање","Επεξεργασία" } },
{ "Full", { 0, "Vol","Voll","Plein","Lleno","Pieno","Заполнено","Заповнено","Пълно","Пуно","Πλήρες" } },
{ "Delivered", { 0, "Afgeleverd","Zugestellt","Remis","Entregado","Consegnato","Доставлено","Доставлено","Доставено","Испоручено","Παραδόθηκε" } },
{ "New contact", { 0, "Nieuw contact","Neuer Kontakt","Nouveau contact","Nuevo contacto","Nuovo contatto","Новый контакт","Новий контакт","Нов контакт","Нови контакт","Νέα επαφή" } },
{ "New DM", { 0, "Nieuw DM","Neue DN","Nouveau MP","Nuevo MD","Nuovo MD","Новое ЛС","Нове ПП","Ново ЛС","Нова ДП","Νέο ΠΜ" } },
{ "New channel msg", { 0, "Nieuw kanaalbericht","Neue Kanalnachricht","Nouveau msg de canal","Nuevo msj de canal","Nuovo msg canale","Новое сообщ. канала","Нове повід. каналу","Ново съобщ. в канал","Нова порука канала","Νέο μήνυμα καναλιού" } },
{ "New room msg", { 0, "Nieuw roombericht","Neue Raumnachricht","Nouveau msg de salon","Nuevo msj de sala","Nuovo msg stanza","Новое сообщ. комнаты","Нове повід. кімнати","Ново съобщ. в стая","Нова порука собе","Νέο μήνυμα δωματίου" } },
{ "Nearby on map", { 0, "Dichtbij op kaart","In der Nähe auf Karte","À proximité sur la carte","Cerca en el mapa","Vicino sulla mappa","Рядом на карте","Поблизу на карті","Наблизо на картата","У близини на мапи","Κοντά στον χάρτη" } },
{ "Advert policy saved", { 0, "Advertentiebeleid opgeslagen","Advert-Richtlinie gespeichert","Règle d'annonce enregistrée","Política de anuncio guardada","Criterio advert salvato","Политика advert сохранена","Політику advert збережено","Правилото за advert е запазено","Правило за advert сачувано","Η πολιτική advert αποθηκεύτηκε" } },
{ "Auto-add saved", { 0, "Auto-toevoegen opgeslagen","Auto-Hinzufügen gespeichert","Ajout auto enregistré","Auto-añadir guardado","Auto-aggiunta salvata","Автодобавление сохранено","Автододавання збережено","Авто-добавянето е запазено","Аутододавање сачувано","Η αυτόματη προσθήκη αποθηκεύτηκε" } },
{ "Experimental saved", { 0, "Experimenteel opgeslagen","Experimentell gespeichert","Expérimental enregistré","Experimental guardado","Sperimentale salvato","Эксперим. сохранено","Експерим. збережено","Експерим. запазено","Експериментално сачувано","Πειραματικά αποθηκεύτηκαν" } },
{ "Channel scope saved", { 0, "Kanaalbereik opgeslagen","Kanalbereich gespeichert","Portée du canal enregistrée","Alcance del canal guardado","Ambito canale salvato","Область канала сохранена","Область каналу збережено","Обхватът на канала е запазен","Опсег канала сачуван","Το εύρος καναλιού αποθηκεύτηκε" } },
{ "Couldn't delete", { 0, "Verwijderen mislukt","Löschen fehlgeschlagen","Suppression impossible","No se pudo eliminar","Impossibile eliminare","Не удалось удалить","Не вдалося видалити","Изтриването неуспешно","Брисање није успело","Αδυναμία διαγραφής" } },
{ "Create failed", { 0, "Aanmaken mislukt","Erstellen fehlgeschlagen","Échec de création","Error al crear","Creazione non riuscita","Не удалось создать","Не вдалося створити","Създаването неуспешно","Креирање није успело","Αποτυχία δημιουργίας" } },
{ "Delete failed", { 0, "Verwijderen mislukt","Löschen fehlgeschlagen","Échec de suppression","Error al eliminar","Eliminazione non riuscita","Не удалось удалить","Не вдалося видалити","Изтриването неуспешно","Брисање није успело","Αποτυχία διαγραφής" } },
{ "Save failed", { 0, "Opslaan mislukt","Speichern fehlgeschlagen","Échec d'enregistrement","Error al guardar","Salvataggio non riuscito","Не удалось сохранить","Не вдалося зберегти","Запазването неуспешно","Чување није успело","Αποτυχία αποθήκευσης" } },
{ "Channel not found", { 0, "Kanaal niet gevonden","Kanal nicht gefunden","Canal introuvable","Canal no encontrado","Canale non trovato","Канал не найден","Канал не знайдено","Каналът не е намерен","Канал није пронађен","Το κανάλι δεν βρέθηκε" } },
{ "Channel table is full", { 0, "Kanaaltabel is vol","Kanaltabelle ist voll","Table des canaux pleine","Tabla de canales llena","Tabella canali piena","Таблица каналов заполнена","Таблиця каналів заповнена","Таблицата с канали е пълна","Табела канала је пуна","Ο πίνακας καναλιών είναι πλήρης" } },
{ "Contact gone", { 0, "Contact weg","Kontakt weg","Contact perdu","Contacto perdido","Contatto perso","Контакт пропал","Контакт зник","Контактът изчезна","Контакт нестао","Η επαφή χάθηκε" } },
{ "Contact missing", { 0, "Contact ontbreekt","Kontakt fehlt","Contact manquant","Falta el contacto","Contatto mancante","Контакт отсутствует","Контакт відсутній","Контактът липсва","Контакт недостаје","Η επαφή λείπει" } },
{ "Contacts full", { 0, "Contacten vol","Kontakte voll","Contacts pleins","Contactos llenos","Contatti pieni","Контакты заполнены","Контакти заповнено","Контактите са пълни","Контакти пуни","Οι επαφές είναι πλήρεις" } },
{ "Cannot open file", { 0, "Kan bestand niet openen","Datei kann nicht geöffnet werden","Impossible d'ouvrir le fichier","No se puede abrir el archivo","Impossibile aprire il file","Не удаётся открыть файл","Не вдалося відкрити файл","Файлът не може да се отвори","Не могу да отворим датотеку","Αδυναμία ανοίγματος αρχείου" } },
{ "Connecting...", { 0, "Verbinden...","Verbinde...","Connexion...","Conectando...","Connessione...","Подключение...","Підключення...","Свързване...","Повезивање...","Σύνδεση..." } },
{ "Loading...", { 0, "Laden...","Lädt...","Chargement...","Cargando...","Caricamento...","Загрузка...","Завантаження...","Зареждане...","Учитавање...","Φόρτωση..." } },
{ "Loading map…", { 0, "Kaart laden…","Karte wird geladen…","Chargement de la carte…","Cargando mapa…","Caricamento mappa…","Загрузка карты…","Завантаження карти…","Зареждане на картата…","Учитавање мапе…","Φόρτωση χάρτη…" } },
{ "Scanning…", { 0, "Scannen…","Suche…","Analyse…","Buscando…","Scansione…","Сканирование…","Сканування…","Сканиране…","Скенирање…","Σάρωση…" } },
{ "Logging in…", { 0, "Inloggen…","Anmeldung…","Connexion…","Iniciando sesión…","Accesso…","Вход…","Вхід…","Вписване…","Пријава…","Σύνδεση…" } },
{ "Rebooting...", { 0, "Opnieuw opstarten...","Neustart...","Redémarrage...","Reiniciando...","Riavvio...","Перезагрузка...","Перезавантаження...","Рестартиране...","Рестартовање...","Επανεκκίνηση..." } },
{ "Rebooting…", { 0, "Opnieuw opstarten…","Neustart…","Redémarrage…","Reiniciando…","Riavvio…","Перезагрузка…","Перезавантаження…","Рестартиране…","Рестартовање…","Επανεκκίνηση…" } },
{ "Locking…", { 0, "Vergrendelen…","Sperren…","Verrouillage…","Bloqueando…","Blocco…","Блокировка…","Блокування…","Заключване…","Закључавање…","Κλείδωμα…" } },
{ "Unlocking…", { 0, "Ontgrendelen…","Entsperren…","Déverrouillage…","Desbloqueando…","Sblocco…","Разблокировка…","Розблокування…","Отключване…","Откључавање…","Ξεκλείδωμα…" } },
{ "Hashtag name", { 0, "Hashtagnaam","Hashtag-Name","Nom du hashtag","Nombre de hashtag","Nome hashtag","Имя хэштега","Назва хештегу","Име на хаштаг","Назив хаштага","Όνομα hashtag" } },
{ "Public key (64 hex chars)", { 0, "Publieke sleutel (64 hex)","Öffentl. Schlüssel (64 Hex)","Clé publique (64 hex)","Clave pública (64 hex)","Chiave pubblica (64 hex)","Открытый ключ (64 hex)","Відкритий ключ (64 hex)","Публичен ключ (64 hex)","Јавни кључ (64 hex)","Δημόσιο κλειδί (64 hex)" } },
{ "Secret (32 hex chars)", { 0, "Geheim (32 hex)","Geheimnis (32 Hex)","Secret (32 hex)","Secreto (32 hex)","Segreto (32 hex)","Секрет (32 hex)","Секрет (32 hex)","Тайна (32 hex)","Тајна (32 hex)","Μυστικό (32 hex)" } },
{ "Secret (32 hex, optional)", { 0, "Geheim (32 hex, optioneel)","Geheimnis (32 Hex, optional)","Secret (32 hex, facultatif)","Secreto (32 hex, opcional)","Segreto (32 hex, opzionale)","Секрет (32 hex, необяз.)","Секрет (32 hex, необов.)","Тайна (32 hex, по избор)","Тајна (32 hex, опционо)","Μυστικό (32 hex, προαιρ.)" } },
{ "Lock wallpaper", { 0, "Vergrendelachtergrond","Sperr-Hintergrund","Fond de verrouillage","Fondo de bloqueo","Sfondo blocco","Обои блокировки","Шпалери блокування","Тапет за заключване","Позадина закључавања","Ταπετσαρία κλειδώματος" } },
{ "Lock wallpaper set", { 0, "Vergrendelachtergrond ingesteld","Sperr-Hintergrund gesetzt","Fond de verrouillage défini","Fondo de bloqueo establecido","Sfondo blocco impostato","Обои блокировки заданы","Шпалери блокування встановлено","Тапетът за заключване е зададен","Позадина закључавања постављена","Ορίστηκε ταπετσαρία κλειδώματος" } },
{ "Lock text colour set", { 0, "Tekstkleur vergrendeling ingesteld","Sperrtext-Farbe gesetzt","Couleur du texte de verrouillage définie","Color de texto de bloqueo establecido","Colore testo blocco impostato","Цвет текста блокировки задан","Колір тексту блокування встановлено","Цветът на текста при заключване е зададен","Боја текста закључавања постављена","Ορίστηκε χρώμα κειμένου κλειδώματος" } },
{ "Radio off", { 0, "Radio uit","Funk aus","Radio désactivée","Radio apagada","Radio spenta","Радио выкл","Радіо вимк","Радиото е изключено","Радио искључен","Ράδιο ανενεργό" } },
{ "Radio: off", { 0, "Radio: uit","Funk: aus","Radio : désactivée","Radio: apagada","Radio: spenta","Радио: выкл","Радіо: вимк","Радио: изключено","Радио: искључен","Ράδιο: ανενεργό" } },
{ "Path reset", { 0, "Pad gereset","Pfad zurückgesetzt","Chemin réinitialisé","Ruta restablecida","Percorso reimpostato","Путь сброшен","Шлях скинуто","Пътят е нулиран","Путања ресетована","Η διαδρομή επαναφέρθηκε" } },
{ "Path reset failed", { 0, "Pad resetten mislukt","Pfad-Reset fehlgeschlagen","Échec de réinit. du chemin","Error al restablecer ruta","Reset percorso non riuscito","Не удалось сбросить путь","Не вдалося скинути шлях","Нулирането на пътя неуспешно","Ресет путање није успео","Αποτυχία επαναφοράς διαδρομής" } },
{ "Ping failed", { 0, "Ping mislukt","Ping fehlgeschlagen","Échec du ping","Error de ping","Ping non riuscito","Сбой ping","Помилка ping","Ping неуспешен","Ping није успео","Αποτυχία ping" } },
{ "Public channel ready", { 0, "Openbaar kanaal klaar","Öffentl. Kanal bereit","Canal public prêt","Canal público listo","Canale pubblico pronto","Публичный канал готов","Публічний канал готовий","Публичният канал е готов","Јавни канал спреман","Το δημόσιο κανάλι είναι έτοιμο" } },
{ "Password too long", { 0, "Wachtwoord te lang","Passwort zu lang","Mot de passe trop long","Contraseña demasiado larga","Password troppo lunga","Пароль слишком длинный","Пароль задовгий","Паролата е твърде дълга","Лозинка предуга","Ο κωδικός είναι πολύ μακρύς" } },
{ "SSID too long", { 0, "SSID te lang","SSID zu lang","SSID trop long","SSID demasiado largo","SSID troppo lungo","SSID слишком длинный","SSID задовгий","SSID е твърде дълъг","SSID предуг","Το SSID είναι πολύ μακρύ" } },
{ "Enter SSID first", { 0, "Voer eerst SSID in","Zuerst SSID eingeben","Saisissez d'abord le SSID","Introduce primero el SSID","Inserisci prima l'SSID","Сначала введите SSID","Спочатку введіть SSID","Първо въведете SSID","Прво унесите SSID","Εισαγάγετε πρώτα το SSID" } },
{ "Set an SSID first", { 0, "Stel eerst een SSID in","Zuerst SSID festlegen","Définissez d'abord un SSID","Establece primero un SSID","Imposta prima un SSID","Сначала задайте SSID","Спочатку задайте SSID","Първо задайте SSID","Прво поставите SSID","Ορίστε πρώτα ένα SSID" } },
{ "Set your location first", { 0, "Stel eerst je locatie in","Zuerst Standort festlegen","Définissez d'abord votre position","Establece primero tu ubicación","Imposta prima la posizione","Сначала задайте местоположение","Спочатку задайте місцезнаходження","Първо задайте местоположението","Прво поставите локацију","Ορίστε πρώτα την τοποθεσία σας" } },
{ "Wi-Fi saved (radio off)", { 0, "Wi-Fi opgeslagen (radio uit)","WLAN gespeichert (Funk aus)","Wi-Fi enregistré (radio off)","Wi-Fi guardado (radio off)","Wi-Fi salvato (radio off)","Wi-Fi сохранён (радио выкл)","Wi-Fi збережено (радіо вимк)","Wi-Fi запазен (радио изкл)","Wi-Fi сачуван (радио искљ)","Το Wi-Fi αποθηκεύτηκε (ράδιο off)" } },
{ "Wi-Fi saved, reconnecting", { 0, "Wi-Fi opgeslagen, opnieuw verbinden","WLAN gespeichert, verbinde neu","Wi-Fi enregistré, reconnexion","Wi-Fi guardado, reconectando","Wi-Fi salvato, riconnessione","Wi-Fi сохранён, переподключение","Wi-Fi збережено, перепідключення","Wi-Fi запазен, повторно свързване","Wi-Fi сачуван, поновно повезивање","Το Wi-Fi αποθηκεύτηκε, επανασύνδεση" } },
{ "Select a DM thread", { 0, "Kies een DM-gesprek","DN-Unterhaltung wählen","Choisir une conversation MP","Selecciona un hilo de MD","Seleziona una conversazione MD","Выберите тему ЛС","Виберіть гілку ПП","Изберете ЛС разговор","Изаберите ДП разговор","Επιλέξτε νήμα ΠΜ" } },
{ "No Bluetooth on this device", { 0, "Geen Bluetooth op dit apparaat","Kein Bluetooth auf diesem Gerät","Pas de Bluetooth sur cet appareil","Sin Bluetooth en este dispositivo","Nessun Bluetooth su questo dispositivo","Нет Bluetooth на этом устройстве","Немає Bluetooth на цьому пристрої","Няма Bluetooth на това устройство","Нема Bluetooth на овом уређају","Δεν υπάρχει Bluetooth σε αυτή τη συσκευή" } },
{ "No networks found (2.4 GHz only)", { 0, "Geen netwerken gevonden (alleen 2.4 GHz)","Keine Netzwerke gefunden (nur 2,4 GHz)","Aucun réseau trouvé (2,4 GHz uniquement)","No se encontraron redes (solo 2.4 GHz)","Nessuna rete trovata (solo 2.4 GHz)","Сети не найдены (только 2.4 ГГц)","Мережі не знайдено (лише 2.4 ГГц)","Няма намерени мрежи (само 2.4 GHz)","Нема мрежа (само 2.4 GHz)","Δεν βρέθηκαν δίκτυα (μόνο 2.4 GHz)" } },
{ "Insert emoji / symbol", { 0, "Emoji / symbool invoegen","Emoji / Symbol einfügen","Insérer emoji / symbole","Insertar emoji / símbolo","Inserisci emoji / simbolo","Вставить эмодзи / символ","Вставити емодзі / символ","Вмъкни емоджи / символ","Уметни емоџи / симбол","Εισαγωγή emoji / συμβόλου" } },
{ "tap to cancel", { 0, "tik om te annuleren","zum Abbrechen tippen","toucher pour annuler","toca para cancelar","tocca per annullare","нажмите для отмены","торкніться, щоб скасувати","докоснете за отказ","додирните за отказ","πατήστε για ακύρωση" } },
{ "keep holding", { 0, "blijf vasthouden","weiter halten","maintenez","mantén pulsado","tieni premuto","удерживайте","утримуйте","задръжте","држите","κρατήστε πατημένο" } },
{ "hold the trackball to unlock", { 0, "houd de trackball vast om te ontgrendelen","Trackball halten zum Entsperren","maintenez la boule pour déverrouiller","mantén el trackball para desbloquear","tieni premuta la trackball per sbloccare","удерживайте трекбол для разблокировки","утримуйте трекбол для розблокування","задръжте топчето за отключване","држите куглицу да откључате","κρατήστε το trackball για ξεκλείδωμα" } },
{ "Contacts", { 0, "Contacten","Kontakte","Contacts","Contactos","Contatti","Контакты","Контакти","Контакти","Контакти","Επαφές" ,"Contatos"} },
{ "Commands", { 0, "Opdrachten","Befehle","Commandes","Comandos","Comandi","Команды","Команди","Команди","Команде","Εντολές" ,"Comandos"} },
{ "Editing", { 0, "Bewerken","Bearbeiten","Modification","Editando","Modifica","Редактирование","Редагування","Редактиране","Уређивање","Επεξεργασία" ,"Editando"} },
{ "Full", { 0, "Vol","Voll","Plein","Lleno","Pieno","Заполнено","Заповнено","Пълно","Пуно","Πλήρες" ,"Cheio"} },
{ "Delivered", { 0, "Afgeleverd","Zugestellt","Remis","Entregado","Consegnato","Доставлено","Доставлено","Доставено","Испоручено","Παραδόθηκε" ,"Entregue"} },
{ "New contact", { 0, "Nieuw contact","Neuer Kontakt","Nouveau contact","Nuevo contacto","Nuovo contatto","Новый контакт","Новий контакт","Нов контакт","Нови контакт","Νέα επαφή" ,"Novo contato"} },
{ "New DM", { 0, "Nieuw DM","Neue DN","Nouveau MP","Nuevo MD","Nuovo MD","Новое ЛС","Нове ПП","Ново ЛС","Нова ДП","Νέο ΠΜ" ,"Nova DM"} },
{ "New channel msg", { 0, "Nieuw kanaalbericht","Neue Kanalnachricht","Nouveau msg de canal","Nuevo msj de canal","Nuovo msg canale","Новое сообщ. канала","Нове повід. каналу","Ново съобщ. в канал","Нова порука канала","Νέο μήνυμα καναλιού" ,"Nova msg de canal"} },
{ "New room msg", { 0, "Nieuw roombericht","Neue Raumnachricht","Nouveau msg de salon","Nuevo msj de sala","Nuovo msg stanza","Новое сообщ. комнаты","Нове повід. кімнати","Ново съобщ. в стая","Нова порука собе","Νέο μήνυμα δωματίου" ,"Nova msg de sala"} },
{ "Nearby on map", { 0, "Dichtbij op kaart","In der Nähe auf Karte","À proximité sur la carte","Cerca en el mapa","Vicino sulla mappa","Рядом на карте","Поблизу на карті","Наблизо на картата","У близини на мапи","Κοντά στον χάρτη" ,"Por perto no mapa"} },
{ "Advert policy saved", { 0, "Advertentiebeleid opgeslagen","Advert-Richtlinie gespeichert","Règle d'annonce enregistrée","Política de anuncio guardada","Criterio advert salvato","Политика advert сохранена","Політику advert збережено","Правилото за advert е запазено","Правило за advert сачувано","Η πολιτική advert αποθηκεύτηκε" ,"Política de advert salva"} },
{ "Auto-add saved", { 0, "Auto-toevoegen opgeslagen","Auto-Hinzufügen gespeichert","Ajout auto enregistré","Auto-añadir guardado","Auto-aggiunta salvata","Автодобавление сохранено","Автододавання збережено","Авто-добавянето е запазено","Аутододавање сачувано","Η αυτόματη προσθήκη αποθηκεύτηκε" ,"Auto-adição salva"} },
{ "Experimental saved", { 0, "Experimenteel opgeslagen","Experimentell gespeichert","Expérimental enregistré","Experimental guardado","Sperimentale salvato","Эксперим. сохранено","Експерим. збережено","Експерим. запазено","Експериментално сачувано","Πειραματικά αποθηκεύτηκαν" ,"Experimental salvo"} },
{ "Channel scope saved", { 0, "Kanaalbereik opgeslagen","Kanalbereich gespeichert","Portée du canal enregistrée","Alcance del canal guardado","Ambito canale salvato","Область канала сохранена","Область каналу збережено","Обхватът на канала е запазен","Опсег канала сачуван","Το εύρος καναλιού αποθηκεύτηκε" ,"Escopo do canal salvo"} },
{ "Couldn't delete", { 0, "Verwijderen mislukt","Löschen fehlgeschlagen","Suppression impossible","No se pudo eliminar","Impossibile eliminare","Не удалось удалить","Не вдалося видалити","Изтриването неуспешно","Брисање није успело","Αδυναμία διαγραφής" ,"Não foi possível excluir"} },
{ "Create failed", { 0, "Aanmaken mislukt","Erstellen fehlgeschlagen","Échec de création","Error al crear","Creazione non riuscita","Не удалось создать","Не вдалося створити","Създаването неуспешно","Креирање није успело","Αποτυχία δημιουργίας" ,"Falha ao criar"} },
{ "Delete failed", { 0, "Verwijderen mislukt","Löschen fehlgeschlagen","Échec de suppression","Error al eliminar","Eliminazione non riuscita","Не удалось удалить","Не вдалося видалити","Изтриването неуспешно","Брисање није успело","Αποτυχία διαγραφής" ,"Falha ao excluir"} },
{ "Save failed", { 0, "Opslaan mislukt","Speichern fehlgeschlagen","Échec d'enregistrement","Error al guardar","Salvataggio non riuscito","Не удалось сохранить","Не вдалося зберегти","Запазването неуспешно","Чување није успело","Αποτυχία αποθήκευσης" ,"Falha ao salvar"} },
{ "Channel not found", { 0, "Kanaal niet gevonden","Kanal nicht gefunden","Canal introuvable","Canal no encontrado","Canale non trovato","Канал не найден","Канал не знайдено","Каналът не е намерен","Канал није пронађен","Το κανάλι δεν βρέθηκε" ,"Canal não encontrado"} },
{ "Channel table is full", { 0, "Kanaaltabel is vol","Kanaltabelle ist voll","Table des canaux pleine","Tabla de canales llena","Tabella canali piena","Таблица каналов заполнена","Таблиця каналів заповнена","Таблицата с канали е пълна","Табела канала је пуна","Ο πίνακας καναλιών είναι πλήρης" ,"Tabela de canais cheia"} },
{ "Contact gone", { 0, "Contact weg","Kontakt weg","Contact perdu","Contacto perdido","Contatto perso","Контакт пропал","Контакт зник","Контактът изчезна","Контакт нестао","Η επαφή χάθηκε" ,"Contato sumiu"} },
{ "Contact missing", { 0, "Contact ontbreekt","Kontakt fehlt","Contact manquant","Falta el contacto","Contatto mancante","Контакт отсутствует","Контакт відсутній","Контактът липсва","Контакт недостаје","Η επαφή λείπει" ,"Contato ausente"} },
{ "Contacts full", { 0, "Contacten vol","Kontakte voll","Contacts pleins","Contactos llenos","Contatti pieni","Контакты заполнены","Контакти заповнено","Контактите са пълни","Контакти пуни","Οι επαφές είναι πλήρεις" ,"Contatos cheios"} },
{ "Cannot open file", { 0, "Kan bestand niet openen","Datei kann nicht geöffnet werden","Impossible d'ouvrir le fichier","No se puede abrir el archivo","Impossibile aprire il file","Не удаётся открыть файл","Не вдалося відкрити файл","Файлът не може да се отвори","Не могу да отворим датотеку","Αδυναμία ανοίγματος αρχείου" ,"Não foi possível abrir o arquivo"} },
{ "Connecting...", { 0, "Verbinden...","Verbinde...","Connexion...","Conectando...","Connessione...","Подключение...","Підключення...","Свързване...","Повезивање...","Σύνδεση..." ,"Conectando..."} },
{ "Loading...", { 0, "Laden...","Lädt...","Chargement...","Cargando...","Caricamento...","Загрузка...","Завантаження...","Зареждане...","Учитавање...","Φόρτωση..." ,"Carregando..."} },
{ "Loading map…", { 0, "Kaart laden…","Karte wird geladen…","Chargement de la carte…","Cargando mapa…","Caricamento mappa…","Загрузка карты…","Завантаження карти…","Зареждане на картата…","Учитавање мапе…","Φόρτωση χάρτη…" ,"Carregando mapa…"} },
{ "Scanning…", { 0, "Scannen…","Suche…","Analyse…","Buscando…","Scansione…","Сканирование…","Сканування…","Сканиране…","Скенирање…","Σάρωση…" ,"Buscando…"} },
{ "Logging in…", { 0, "Inloggen…","Anmeldung…","Connexion…","Iniciando sesión…","Accesso…","Вход…","Вхід…","Вписване…","Пријава…","Σύνδεση…" ,"Entrando…"} },
{ "Rebooting...", { 0, "Opnieuw opstarten...","Neustart...","Redémarrage...","Reiniciando...","Riavvio...","Перезагрузка...","Перезавантаження...","Рестартиране...","Рестартовање...","Επανεκκίνηση..." ,"Reiniciando..."} },
{ "Rebooting…", { 0, "Opnieuw opstarten…","Neustart…","Redémarrage…","Reiniciando…","Riavvio…","Перезагрузка…","Перезавантаження…","Рестартиране…","Рестартовање…","Επανεκκίνηση…" ,"Reiniciando…"} },
{ "Locking…", { 0, "Vergrendelen…","Sperren…","Verrouillage…","Bloqueando…","Blocco…","Блокировка…","Блокування…","Заключване…","Закључавање…","Κλείδωμα…" ,"Bloqueando…"} },
{ "Unlocking…", { 0, "Ontgrendelen…","Entsperren…","Déverrouillage…","Desbloqueando…","Sblocco…","Разблокировка…","Розблокування…","Отключване…","Откључавање…","Ξεκλείδωμα…" ,"Desbloqueando…"} },
{ "Hashtag name", { 0, "Hashtagnaam","Hashtag-Name","Nom du hashtag","Nombre de hashtag","Nome hashtag","Имя хэштега","Назва хештегу","Име на хаштаг","Назив хаштага","Όνομα hashtag" ,"Nome da hashtag"} },
{ "Public key (64 hex chars)", { 0, "Publieke sleutel (64 hex)","Öffentl. Schlüssel (64 Hex)","Clé publique (64 hex)","Clave pública (64 hex)","Chiave pubblica (64 hex)","Открытый ключ (64 hex)","Відкритий ключ (64 hex)","Публичен ключ (64 hex)","Јавни кључ (64 hex)","Δημόσιο κλειδί (64 hex)" ,"Chave pública (64 hex)"} },
{ "Secret (32 hex chars)", { 0, "Geheim (32 hex)","Geheimnis (32 Hex)","Secret (32 hex)","Secreto (32 hex)","Segreto (32 hex)","Секрет (32 hex)","Секрет (32 hex)","Тайна (32 hex)","Тајна (32 hex)","Μυστικό (32 hex)" ,"Segredo (32 hex)"} },
{ "Secret (32 hex, optional)", { 0, "Geheim (32 hex, optioneel)","Geheimnis (32 Hex, optional)","Secret (32 hex, facultatif)","Secreto (32 hex, opcional)","Segreto (32 hex, opzionale)","Секрет (32 hex, необяз.)","Секрет (32 hex, необов.)","Тайна (32 hex, по избор)","Тајна (32 hex, опционо)","Μυστικό (32 hex, προαιρ.)" ,"Segredo (32 hex, opcional)"} },
{ "Lock wallpaper", { 0, "Vergrendelachtergrond","Sperr-Hintergrund","Fond de verrouillage","Fondo de bloqueo","Sfondo blocco","Обои блокировки","Шпалери блокування","Тапет за заключване","Позадина закључавања","Ταπετσαρία κλειδώματος" ,"Papel de parede do bloqueio"} },
{ "Lock wallpaper set", { 0, "Vergrendelachtergrond ingesteld","Sperr-Hintergrund gesetzt","Fond de verrouillage défini","Fondo de bloqueo establecido","Sfondo blocco impostato","Обои блокировки заданы","Шпалери блокування встановлено","Тапетът за заключване е зададен","Позадина закључавања постављена","Ορίστηκε ταπετσαρία κλειδώματος" ,"Papel de parede definido"} },
{ "Lock text colour set", { 0, "Tekstkleur vergrendeling ingesteld","Sperrtext-Farbe gesetzt","Couleur du texte de verrouillage définie","Color de texto de bloqueo establecido","Colore testo blocco impostato","Цвет текста блокировки задан","Колір тексту блокування встановлено","Цветът на текста при заключване е зададен","Боја текста закључавања постављена","Ορίστηκε χρώμα κειμένου κλειδώματος" ,"Cor do texto de bloqueio definida"} },
{ "Radio off", { 0, "Radio uit","Funk aus","Radio désactivée","Radio apagada","Radio spenta","Радио выкл","Радіо вимк","Радиото е изключено","Радио искључен","Ράδιο ανενεργό" ,"Rádio desligado"} },
{ "Radio: off", { 0, "Radio: uit","Funk: aus","Radio : désactivée","Radio: apagada","Radio: spenta","Радио: выкл","Радіо: вимк","Радио: изключено","Радио: искључен","Ράδιο: ανενεργό" ,"Rádio: desligado"} },
{ "Path reset", { 0, "Pad gereset","Pfad zurückgesetzt","Chemin réinitialisé","Ruta restablecida","Percorso reimpostato","Путь сброшен","Шлях скинуто","Пътят е нулиран","Путања ресетована","Η διαδρομή επαναφέρθηκε" ,"Rota redefinida"} },
{ "Path reset failed", { 0, "Pad resetten mislukt","Pfad-Reset fehlgeschlagen","Échec de réinit. du chemin","Error al restablecer ruta","Reset percorso non riuscito","Не удалось сбросить путь","Не вдалося скинути шлях","Нулирането на пътя неуспешно","Ресет путање није успео","Αποτυχία επαναφοράς διαδρομής" ,"Falha ao redefinir rota"} },
{ "Ping failed", { 0, "Ping mislukt","Ping fehlgeschlagen","Échec du ping","Error de ping","Ping non riuscito","Сбой ping","Помилка ping","Ping неуспешен","Ping није успео","Αποτυχία ping" ,"Falha no ping"} },
{ "Public channel ready", { 0, "Openbaar kanaal klaar","Öffentl. Kanal bereit","Canal public prêt","Canal público listo","Canale pubblico pronto","Публичный канал готов","Публічний канал готовий","Публичният канал е готов","Јавни канал спреман","Το δημόσιο κανάλι είναι έτοιμο" ,"Canal público pronto"} },
{ "Password too long", { 0, "Wachtwoord te lang","Passwort zu lang","Mot de passe trop long","Contraseña demasiado larga","Password troppo lunga","Пароль слишком длинный","Пароль задовгий","Паролата е твърде дълга","Лозинка предуга","Ο κωδικός είναι πολύ μακρύς" ,"Senha muito longa"} },
{ "SSID too long", { 0, "SSID te lang","SSID zu lang","SSID trop long","SSID demasiado largo","SSID troppo lungo","SSID слишком длинный","SSID задовгий","SSID е твърде дълъг","SSID предуг","Το SSID είναι πολύ μακρύ" ,"SSID muito longo"} },
{ "Enter SSID first", { 0, "Voer eerst SSID in","Zuerst SSID eingeben","Saisissez d'abord le SSID","Introduce primero el SSID","Inserisci prima l'SSID","Сначала введите SSID","Спочатку введіть SSID","Първо въведете SSID","Прво унесите SSID","Εισαγάγετε πρώτα το SSID" ,"Digite o SSID primeiro"} },
{ "Set an SSID first", { 0, "Stel eerst een SSID in","Zuerst SSID festlegen","Définissez d'abord un SSID","Establece primero un SSID","Imposta prima un SSID","Сначала задайте SSID","Спочатку задайте SSID","Първо задайте SSID","Прво поставите SSID","Ορίστε πρώτα ένα SSID" ,"Defina um SSID primeiro"} },
{ "Set your location first", { 0, "Stel eerst je locatie in","Zuerst Standort festlegen","Définissez d'abord votre position","Establece primero tu ubicación","Imposta prima la posizione","Сначала задайте местоположение","Спочатку задайте місцезнаходження","Първо задайте местоположението","Прво поставите локацију","Ορίστε πρώτα την τοποθεσία σας" ,"Defina sua localização primeiro"} },
{ "Wi-Fi saved (radio off)", { 0, "Wi-Fi opgeslagen (radio uit)","WLAN gespeichert (Funk aus)","Wi-Fi enregistré (radio off)","Wi-Fi guardado (radio off)","Wi-Fi salvato (radio off)","Wi-Fi сохранён (радио выкл)","Wi-Fi збережено (радіо вимк)","Wi-Fi запазен (радио изкл)","Wi-Fi сачуван (радио искљ)","Το Wi-Fi αποθηκεύτηκε (ράδιο off)" ,"Wi-Fi salvo (rádio desligado)"} },
{ "Wi-Fi saved, reconnecting", { 0, "Wi-Fi opgeslagen, opnieuw verbinden","WLAN gespeichert, verbinde neu","Wi-Fi enregistré, reconnexion","Wi-Fi guardado, reconectando","Wi-Fi salvato, riconnessione","Wi-Fi сохранён, переподключение","Wi-Fi збережено, перепідключення","Wi-Fi запазен, повторно свързване","Wi-Fi сачуван, поновно повезивање","Το Wi-Fi αποθηκεύτηκε, επανασύνδεση" ,"Wi-Fi salvo, reconectando"} },
{ "Select a DM thread", { 0, "Kies een DM-gesprek","DN-Unterhaltung wählen","Choisir une conversation MP","Selecciona un hilo de MD","Seleziona una conversazione MD","Выберите тему ЛС","Виберіть гілку ПП","Изберете ЛС разговор","Изаберите ДП разговор","Επιλέξτε νήμα ΠΜ" ,"Selecione uma conversa de DM"} },
{ "No Bluetooth on this device", { 0, "Geen Bluetooth op dit apparaat","Kein Bluetooth auf diesem Gerät","Pas de Bluetooth sur cet appareil","Sin Bluetooth en este dispositivo","Nessun Bluetooth su questo dispositivo","Нет Bluetooth на этом устройстве","Немає Bluetooth на цьому пристрої","Няма Bluetooth на това устройство","Нема Bluetooth на овом уређају","Δεν υπάρχει Bluetooth σε αυτή τη συσκευή" ,"Sem Bluetooth neste aparelho"} },
{ "No networks found (2.4 GHz only)", { 0, "Geen netwerken gevonden (alleen 2.4 GHz)","Keine Netzwerke gefunden (nur 2,4 GHz)","Aucun réseau trouvé (2,4 GHz uniquement)","No se encontraron redes (solo 2.4 GHz)","Nessuna rete trovata (solo 2.4 GHz)","Сети не найдены (только 2.4 ГГц)","Мережі не знайдено (лише 2.4 ГГц)","Няма намерени мрежи (само 2.4 GHz)","Нема мрежа (само 2.4 GHz)","Δεν βρέθηκαν δίκτυα (μόνο 2.4 GHz)" ,"Nenhuma rede encontrada (só 2.4 GHz)"} },
{ "Insert emoji / symbol", { 0, "Emoji / symbool invoegen","Emoji / Symbol einfügen","Insérer emoji / symbole","Insertar emoji / símbolo","Inserisci emoji / simbolo","Вставить эмодзи / символ","Вставити емодзі / символ","Вмъкни емоджи / символ","Уметни емоџи / симбол","Εισαγωγή emoji / συμβόλου" ,"Inserir emoji / símbolo"} },
{ "tap to cancel", { 0, "tik om te annuleren","zum Abbrechen tippen","toucher pour annuler","toca para cancelar","tocca per annullare","нажмите для отмены","торкніться, щоб скасувати","докоснете за отказ","додирните за отказ","πατήστε για ακύρωση" ,"toque para cancelar"} },
{ "keep holding", { 0, "blijf vasthouden","weiter halten","maintenez","mantén pulsado","tieni premuto","удерживайте","утримуйте","задръжте","држите","κρατήστε πατημένο" ,"continue segurando"} },
{ "hold the trackball to unlock", { 0, "houd de trackball vast om te ontgrendelen","Trackball halten zum Entsperren","maintenez la boule pour déverrouiller","mantén el trackball para desbloquear","tieni premuta la trackball per sbloccare","удерживайте трекбол для разблокировки","утримуйте трекбол для розблокування","задръжте топчето за отключване","држите куглицу да откључате","κρατήστε το trackball για ξεκλείδωμα" ,"segure o trackball para desbloquear"} },
// ---- Sweep pass 4 (final): remaining alerts / hints ----
{ "Already exists or table full.", { 0, "Bestaat al of tabel vol.","Existiert bereits oder Tabelle voll.","Existe déjà ou table pleine.","Ya existe o tabla llena.","Esiste già o tabella piena.","Уже существует или таблица заполнена.","Вже існує або таблиця заповнена.","Вече съществува или таблицата е пълна.","Већ постоји или је табела пуна.","Υπάρχει ήδη ή ο πίνακας είναι πλήρης." } },
{ "Battery calibration reset to default", { 0, "Batterijkalibratie teruggezet","Batteriekalibrierung zurückgesetzt","Étalonnage batterie réinitialisé","Calibración de batería restablecida","Calibrazione batteria ripristinata","Калибровка батареи сброшена","Калібрування батареї скинуто","Калибрирането на батерията е нулирано","Калибрација батерије ресетована","Επαναφορά βαθμονόμησης μπαταρίας" } },
{ "Battery read too low — charge fully first", { 0, "Batterijwaarde te laag — laad eerst volledig op","Batteriewert zu niedrig — zuerst voll laden","Lecture batterie trop basse — chargez d'abord","Lectura de batería muy baja — carga primero","Lettura batteria troppo bassa — carica prima","Заряд слишком низкий — сначала зарядите","Заряд надто низький — спершу зарядіть","Отчитането е твърде ниско — заредете първо","Очитавање прениско — прво напуните","Πολύ χαμηλή μέτρηση — φορτίστε πρώτα" } },
{ "Connect Wi-Fi to reload tiles", { 0, "Verbind wifi om tegels te herladen","WLAN verbinden, um Kacheln neu zu laden","Connectez le Wi-Fi pour recharger les tuiles","Conecta Wi-Fi para recargar teselas","Connetti Wi-Fi per ricaricare i riquadri","Подключите Wi-Fi для перезагрузки тайлов","Підключіть Wi-Fi, щоб перезавантажити плитки","Свържете Wi-Fi за презареждане на плочките","Повежите Wi-Fi да поново учитате плочице","Συνδέστε Wi-Fi για επαναφόρτωση πλακιδίων" } },
{ "Diagnostics are on the About page.", { 0, "Diagnostiek staat op de pagina Over.","Diagnose ist auf der Seite Über.","Les diagnostics sont sur la page À propos.","El diagnóstico está en la página Acerca de.","La diagnostica è nella pagina Informazioni.","Диагностика на странице «О программе».","Діагностика на сторінці «Про програму».","Диагностиката е на страницата Относно.","Дијагностика је на страници О програму.","Τα διαγνωστικά είναι στη σελίδα Σχετικά." } },
{ "Edit in Settings → Quick replies", { 0, "Bewerk in Instellingen → Snelle antwoorden","Bearbeiten in Einstellungen → Schnellantworten","Modifier dans Paramètres → Réponses rapides","Editar en Ajustes → Respuestas rápidas","Modifica in Impostazioni → Risposte rapide","Изменить в Настройки → Быстрые ответы","Редагувати в Налаштування → Швидкі відповіді","Редактирай в Настройки → Бързи отговори","Уреди у Подешавања → Брзи одговори","Επεξεργασία στις Ρυθμίσεις → Γρήγορες απαντήσεις" } },
{ "Export failed (can't open file)", { 0, "Export mislukt (kan bestand niet openen)","Export fehlgeschlagen (Datei nicht zu öffnen)","Échec de l'export (fichier illisible)","Error de exportación (no se abre el archivo)","Esportazione non riuscita (file non apribile)","Сбой экспорта (не удаётся открыть файл)","Помилка експорту (не вдалося відкрити файл)","Експортът неуспешен (файлът не се отваря)","Извоз није успео (датотека се не отвара)","Αποτυχία εξαγωγής (αδυναμία ανοίγματος)" } },
{ "Favorites unsupported", { 0, "Favorieten niet ondersteund","Favoriten nicht unterstützt","Favoris non pris en charge","Favoritos no compatibles","Preferiti non supportati","Избранное не поддерживается","Обране не підтримується","Любими не се поддържат","Омиљени нису подржани","Τα αγαπημένα δεν υποστηρίζονται" } },
{ "Format failed (no card / SD error)", { 0, "Formatteren mislukt (geen kaart / SD-fout)","Formatieren fehlgeschlagen (keine Karte / SD-Fehler)","Échec du formatage (pas de carte / erreur SD)","Error de formato (sin tarjeta / error SD)","Formattazione non riuscita (nessuna scheda / errore SD)","Сбой форматирования (нет карты / ошибка SD)","Помилка форматування (немає картки / помилка SD)","Форматирането неуспешно (няма карта / SD грешка)","Форматирање није успело (нема картице / SD грешка)","Αποτυχία διαμόρφωσης (χωρίς κάρτα / σφάλμα SD)" } },
{ "Import failed (bad/unreadable JSON)", { 0, "Import mislukt (ongeldige JSON)","Import fehlgeschlagen (ungültiges JSON)","Échec de l'import (JSON illisible)","Error de importación (JSON no válido)","Importazione non riuscita (JSON non valido)","Сбой импорта (неверный JSON)","Помилка імпорту (некоректний JSON)","Импортът неуспешен (невалиден JSON)","Увоз није успео (неисправан JSON)","Αποτυχία εισαγωγής (μη έγκυρο JSON)" } },
{ "Import: storage unavailable", { 0, "Import: opslag niet beschikbaar","Import: Speicher nicht verfügbar","Import : stockage indisponible","Importar: almacenamiento no disponible","Importa: archiviazione non disponibile","Импорт: хранилище недоступно","Імпорт: сховище недоступне","Импорт: хранилището е недостъпно","Увоз: складиште недоступно","Εισαγωγή: μη διαθέσιμος χώρος" } },
{ "Internal has no folders", { 0, "Intern heeft geen mappen","Intern hat keine Ordner","Aucun dossier en interne","El interno no tiene carpetas","L'interno non ha cartelle","Во внутренней памяти нет папок","У внутрішній пам'яті немає тек","Вътрешната памет няма папки","Интерна нема фасцикле","Η εσωτερική δεν έχει φακέλους" } },
{ "Invalid max hops", { 0, "Ongeldige max hops","Ungültige max. Hops","Sauts max invalides","Saltos máx no válidos","Hop max non validi","Неверное макс. число хопов","Невірне макс. число хопів","Невалидни макс. скокове","Неисправни макс. скокови","Μη έγκυρα μέγ. άλματα" } },
{ "Invalid radio values", { 0, "Ongeldige radiowaarden","Ungültige Funkwerte","Valeurs radio invalides","Valores de radio no válidos","Valori radio non validi","Неверные значения радио","Невірні значення радіо","Невалидни радио стойности","Неисправне радио вредности","Μη έγκυρες τιμές ράδιο" } },
{ "Max zoom for this pack", { 0, "Max zoom voor dit pakket","Max. Zoom für dieses Paket","Zoom max pour ce pack","Zoom máx para este paquete","Zoom max per questo pacchetto","Макс. зум для этого пакета","Макс. зум для цього пакета","Макс. мащаб за този пакет","Макс. зум за овај пакет","Μέγ. ζουμ για αυτό το πακέτο" } },
{ "Min zoom for this pack", { 0, "Min zoom voor dit pakket","Min. Zoom für dieses Paket","Zoom min pour ce pack","Zoom mín para este paquete","Zoom min per questo pacchetto","Мин. зум для этого пакета","Мін. зум для цього пакета","Мин. мащаб за този пакет","Мин. зум за овај пакет","Ελάχ. ζουμ για αυτό το πακέτο" } },
{ "Mode: BLE starting / low memory", { 0, "Modus: BLE start / weinig geheugen","Modus: BLE startet / wenig Speicher","Mode : BLE démarre / mémoire faible","Modo: BLE iniciando / poca memoria","Modalità: BLE avvio / memoria scarsa","Режим: BLE запускается / мало памяти","Режим: BLE запускається / мало пам'яті","Режим: BLE стартира / малко памет","Режим: BLE стартује / мало меморије","Λειτουργία: BLE εκκινεί / λίγη μνήμη" } },
{ "Name can't be empty.", { 0, "Naam mag niet leeg zijn.","Name darf nicht leer sein.","Le nom ne peut pas être vide.","El nombre no puede estar vacío.","Il nome non può essere vuoto.","Имя не может быть пустым.","Ім'я не може бути порожнім.","Името не може да е празно.","Име не може бити празно.","Το όνομα δεν μπορεί να είναι κενό." } },
{ "No DM key", { 0, "Geen DM-sleutel","Kein DN-Schlüssel","Pas de clé MP","Sin clave de MD","Nessuna chiave MD","Нет ключа ЛС","Немає ключа ПП","Няма ключ за ЛС","Нема ДП кључа","Χωρίς κλειδί ΠΜ" } },
{ "No contact for thread", { 0, "Geen contact voor gesprek","Kein Kontakt für Unterhaltung","Aucun contact pour ce fil","Sin contacto para el hilo","Nessun contatto per la conversazione","Нет контакта для темы","Немає контакту для гілки","Няма контакт за разговора","Нема контакта за разговор","Καμία επαφή για το νήμα" } },
{ "No contact to trace", { 0, "Geen contact om te traceren","Kein Kontakt zum Verfolgen","Aucun contact à tracer","Sin contacto para rastrear","Nessun contatto da tracciare","Нет контакта для трассировки","Немає контакту для трасування","Няма контакт за проследяване","Нема контакта за праћење","Καμία επαφή για ιχνηλάτηση" } },
{ "No tiles (non-ESP32)", { 0, "Geen tegels (niet-ESP32)","Keine Kacheln (kein ESP32)","Pas de tuiles (non-ESP32)","Sin teselas (no ESP32)","Nessun riquadro (non-ESP32)","Нет тайлов (не ESP32)","Немає плиток (не ESP32)","Няма плочки (не ESP32)","Нема плочица (није ESP32)","Χωρίς πλακίδια (μη ESP32)" } },
{ "Pubkey must be 64 hex chars.", { 0, "Pubkey moet 64 hex-tekens zijn.","Pubkey muss 64 Hex-Zeichen haben.","La clé publique doit faire 64 hex.","La clave pública debe tener 64 hex.","La chiave pubblica deve avere 64 hex.","Откр. ключ — 64 hex-символа.","Відкр. ключ — 64 hex-символи.","Публичният ключ трябва да е 64 hex.","Јавни кључ мора бити 64 hex.","Το δημόσιο κλειδί πρέπει 64 hex." } },
{ "RX log", { 0, "RX-logboek","RX-Protokoll","Journal RX","Registro RX","Log RX","Журнал RX","Журнал RX","RX дневник","RX дневник","Αρχείο RX" } },
{ "Raw log", { 0, "Ruwe log","Roh-Protokoll","Journal brut","Registro sin procesar","Log grezzo","Сырой журнал","Сирий журнал","Суров дневник","Сирови дневник","Ακατέργαστο αρχείο" } },
{ "RangeTest failed", { 0, "RangeTest mislukt","RangeTest fehlgeschlagen","Échec du RangeTest","Error de RangeTest","RangeTest non riuscito","Сбой RangeTest","Помилка RangeTest","RangeTest неуспешен","RangeTest није успео","Αποτυχία RangeTest" } },
{ "Telemetry req failed", { 0, "Telemetrie-verzoek mislukt","Telemetrie-Anfrage fehlgeschlagen","Échec de la requête télémétrie","Error de solicitud de telemetría","Richiesta telemetria non riuscita","Сбой запроса телеметрии","Помилка запиту телеметрії","Заявката за телеметрия неуспешна","Захтев за телеметрију није успео","Αποτυχία αιτήματος τηλεμετρίας" } },
{ "Trace failed", { 0, "Tracering mislukt","Trace fehlgeschlagen","Échec du traçage","Error de rastreo","Tracciamento non riuscito","Сбой трассировки","Помилка трасування","Проследяването неуспешно","Праћење није успело","Αποτυχία ιχνηλάτησης" } },
{ "Tile reload needs Wi-Fi build", { 0, "Tegels herladen vereist wifi-build","Kachel-Neuladen braucht WLAN-Build","Le rechargement des tuiles requiert le build Wi-Fi","Recargar teselas requiere build Wi-Fi","La ricarica riquadri richiede build Wi-Fi","Перезагрузка тайлов требует Wi-Fi сборки","Перезавантаження плиток потребує Wi-Fi збірки","Презареждането изисква Wi-Fi компилация","Поновно учитавање захтева Wi-Fi билд","Η επαναφόρτωση χρειάζεται έκδοση Wi-Fi" } },
{ "Too large to edit (>8 KB)", { 0, "Te groot om te bewerken (>8 KB)","Zu groß zum Bearbeiten (>8 KB)","Trop grand à modifier (>8 Ko)","Demasiado grande para editar (>8 KB)","Troppo grande da modificare (>8 KB)","Слишком велик для редактирования (>8 КБ)","Завеликий для редагування (>8 КБ)","Твърде голям за редактиране (>8 KB)","Превелико за уређивање (>8 KB)","Πολύ μεγάλο για επεξεργασία (>8 KB)" } },
{ "Wi-Fi is not enabled in this build.", { 0, "Wi-Fi is niet ingeschakeld in deze build.","WLAN ist in diesem Build nicht aktiviert.","Le Wi-Fi n'est pas activé dans ce build.","Wi-Fi no está habilitado en este build.","Il Wi-Fi non è abilitato in questo build.","Wi-Fi не включён в этой сборке.","Wi-Fi не увімкнено в цій збірці.","Wi-Fi не е разрешен в тази компилация.","Wi-Fi није омогућен у овом билду.","Το Wi-Fi δεν είναι ενεργό σε αυτή την έκδοση." } },
{ "Wi-Fi isn't available on this build.", { 0, "Wi-Fi is niet beschikbaar in deze build.","WLAN ist in diesem Build nicht verfügbar.","Le Wi-Fi n'est pas disponible sur ce build.","Wi-Fi no está disponible en este build.","Il Wi-Fi non è disponibile su questo build.","Wi-Fi недоступен в этой сборке.","Wi-Fi недоступний у цій збірці.","Wi-Fi не е наличен в тази компилация.","Wi-Fi није доступан на овом билду.","Το Wi-Fi δεν είναι διαθέσιμο σε αυτή την έκδοση." } },
{ "Analyzing terrain…", { 0, "Terrein analyseren…","Gelände wird analysiert…","Analyse du terrain…","Analizando terreno…","Analisi del terreno…","Анализ рельефа…","Аналіз рельєфу…","Анализиране на терена…","Анализа терена…","Ανάλυση εδάφους…" } },
{ "Exporting settings…", { 0, "Instellingen exporteren…","Einstellungen werden exportiert…","Exportation des réglages…","Exportando ajustes…","Esportazione impostazioni…","Экспорт настроек…","Експорт налаштувань…","Експортиране на настройки…","Извоз подешавања…","Εξαγωγή ρυθμίσεων…" } },
{ "Importing settings…", { 0, "Instellingen importeren…","Einstellungen werden importiert…","Importation des réglages…","Importando ajustes…","Importazione impostazioni…","Импорт настроек…","Імпорт налаштувань…","Импортиране на настройки…","Увоз подешавања…","Εισαγωγή ρυθμίσεων…" } },
{ "Trace sent…", { 0, "Tracering verzonden…","Trace gesendet…","Traçage envoyé…","Rastreo enviado…","Traccia inviata…","Трассировка отправлена…","Трасування надіслано…","Проследяването е изпратено…","Праћење послато…","Στάλθηκε ιχνηλάτηση…" } },
{ "Tracing route…", { 0, "Route traceren…","Route wird verfolgt…","Traçage de l'itinéraire…","Rastreando ruta…","Tracciamento percorso…","Трассировка маршрута…","Трасування маршруту…","Проследяване на маршрута…","Праћење руте…","Ιχνηλάτηση διαδρομής…" } },
{ "Rotating… rebooting", { 0, "Draaien… opnieuw opstarten","Drehen… Neustart","Rotation… redémarrage","Girando… reiniciando","Rotazione… riavvio","Поворот… перезагрузка","Обертання… перезавантаження","Завъртане… рестарт","Ротација… рестарт","Περιστροφή… επανεκκίνηση" } },
{ "Saved — rebooting", { 0, "Opgeslagen — opnieuw opstarten","Gespeichert — Neustart","Enregistré — redémarrage","Guardado — reiniciando","Salvato — riavvio","Сохранено — перезагрузка","Збережено — перезавантаження","Запазено — рестарт","Сачувано — рестарт","Αποθηκεύτηκε — επανεκκίνηση" } },
{ "Saved — reconnecting…", { 0, "Opgeslagen — opnieuw verbinden…","Gespeichert — verbinde neu…","Enregistré — reconnexion…","Guardado — reconectando…","Salvato — riconnessione…","Сохранено — переподключение…","Збережено — перепідключення…","Запазено — повторно свързване…","Сачувано — поновно повезивање…","Αποθηκεύτηκε — επανασύνδεση…" } },
{ "SSID set — enter password, then Save", { 0, "SSID ingesteld — voer wachtwoord in, dan Opslaan","SSID gesetzt — Passwort eingeben, dann Speichern","SSID défini — saisissez le mot de passe, puis Enregistrer","SSID establecido — escribe la contraseña y Guardar","SSID impostato — inserisci la password, poi Salva","SSID задан — введите пароль, затем Сохранить","SSID задано — введіть пароль, потім Зберегти","SSID зададен — въведете парола, после Запази","SSID постављен — унесите лозинку, па Сачувај","Ορίστηκε SSID — εισαγάγετε κωδικό, μετά Αποθήκευση" } },
{ "Powering off… click trackball to wake", { 0, "Uitschakelen… klik trackball om te wekken","Schaltet aus… Trackball klicken zum Aufwecken","Extinction… cliquez la boule pour réveiller","Apagando… pulsa el trackball para despertar","Spegnimento… clicca la trackball per riattivare","Выключение… нажмите трекбол для пробуждения","Вимкнення… натисніть трекбол для пробудження","Изключване… щракнете топчето за събуждане","Гашење… кликните куглицу за буђење","Απενεργοποίηση… κλικ στο trackball για αφύπνιση" } },
{ "Roll to highlight • click to insert", { 0, "Rol om te markeren • klik om in te voegen","Rollen zum Hervorheben • Klick zum Einfügen","Faites rouler pour surligner • cliquez pour insérer","Desplaza para resaltar • pulsa para insertar","Scorri per evidenziare • clicca per inserire","Прокрутите для выбора • нажмите для вставки","Прокрутіть для вибору • натисніть для вставки","Превъртете за маркиране • щракнете за вмъкване","Котрљајте за истицање • кликните за уметање","Κυλήστε για επισήμανση • κλικ για εισαγωγή" } },
{ "mesh link armed", { 0, "mesh link actief","Mesh-Link scharf","liaison mesh armée","enlace mesh armado","link mesh armato","меш-канал готов","меш-зв'язок готовий","меш връзка готова","меш веза спремна","σύνδεσμος mesh ενεργός" } },
{ "n/a in this build", { 0, "n.v.t. in deze build","n. v. in diesem Build","n/d dans ce build","n/d en este build","n/d in questo build","н/д в этой сборке","н/д у цій збірці","н/п в тази компилация","н/д у овом билду","μ/δ σε αυτή την έκδοση" } },
{ "tap to exit full screen", { 0, "tik om volledig scherm te verlaten","tippen zum Verlassen des Vollbilds","toucher pour quitter le plein écran","toca para salir de pantalla completa","tocca per uscire da schermo intero","нажмите для выхода из полноэкранного режима","торкніться, щоб вийти з повноекранного режиму","докоснете за изход от цял екран","додирните за излаз из целог екрана","πατήστε για έξοδο από πλήρη οθόνη" } },
{ "double-tap SPACE cycles through the ones you enable", { 0, "dubbeltik op SPATIE wisselt tussen de ingeschakelde","Doppeltipp auf LEER wechselt die aktivierten","double-tapez ESPACE pour parcourir les activés","doble toque en ESPACIO recorre los activados","doppio tocco su SPAZIO scorre quelli attivi","двойное нажатие ПРОБЕЛ переключает включённые","подвійне натискання ПРОБІЛ перемикає увімкнені","двойно докосване на ИНТЕРВАЛ превърта включените","двоструки додир РАЗМАК пролази кроз омогућене","διπλό πάτημα ΔΙΑΣΤΗΜΑ εναλλάσσει τα ενεργά" } },
{ "pick accented letters as you type; off = plain typing", { 0, "kies letters met accenten tijdens het typen; uit = gewoon typen","Akzentbuchstaben beim Tippen wählen; aus = einfaches Tippen","choisir les lettres accentuées en tapant ; désactivé = saisie simple","elige letras acentuadas al escribir; desactivado = escritura simple","scegli lettere accentate mentre scrivi; off = scrittura semplice","выбор букв с акцентами при наборе; выкл = обычный набор","вибір літер з акцентами під час набору; вимк = звичайний набір","избор на букви с ударения при писане; изкл = обикновено писане","бирање слова са акцентима док куцате; искљ = обично куцање","επιλογή τονισμένων γραμμάτων κατά την πληκτρολόγηση· ανενεργό = απλή" } },
{ "tap the language key (e.g. EN) on the keyboard to cycle the ones you enable", { 0, "tik op de taaltoets (bijv. EN) op het toetsenbord om de ingeschakelde te doorlopen","Sprachtaste (z. B. EN) auf der Tastatur antippen, um die aktivierten zu wechseln","touchez la touche de langue (ex. EN) du clavier pour parcourir les activées","toca la tecla de idioma (p. ej. EN) del teclado para recorrer las activadas","tocca il tasto lingua (es. EN) sulla tastiera per scorrere quelle attive","нажмите клавишу языка (напр. EN) на клавиатуре для переключения включённых","натисніть клавішу мови (напр. EN) на клавіатурі для перемикання увімкнених","докоснете клавиша за език (напр. EN) на клавиатурата, за да превъртате включените","додирните тастер језика (нпр. EN) на тастатури да пролазите кроз омогућене","πατήστε το πλήκτρο γλώσσας (π.χ. EN) στο πληκτρολόγιο για εναλλαγή" } },
{ "Sample text", { 0, "Voorbeeldtekst","Beispieltext","Exemple de texte","Texto de ejemplo","Testo di esempio","Пример текста","Зразок тексту","Примерен текст","Пример текста","Δείγμα κειμένου" } },
{ "Save & restart", { 0, "Opslaan & herstarten","Speichern & neu starten","Enregistrer & redémarrer","Guardar y reiniciar","Salva e riavvia","Сохранить и перезагрузить","Зберегти й перезавантажити","Запази и рестартирай","Сачувај и рестартуј","Αποθήκευση & επανεκκίνηση" } },
{ "Save / turn on", { 0, "Opslaan / inschakelen","Speichern / einschalten","Enregistrer / activer","Guardar / activar","Salva / attiva","Сохранить / включить","Зберегти / увімкнути","Запази / включи","Сачувај / укључи","Αποθήκευση / ενεργοποίηση" } },
{ "Save auto-add", { 0, "Auto-toevoegen opslaan","Auto-Hinzufügen speichern","Enregistrer l'ajout auto","Guardar auto-añadir","Salva auto-aggiunta","Сохранить автодобавление","Зберегти автододавання","Запази авто-добавянето","Сачувај аутододавање","Αποθήκευση αυτόματης προσθήκης" } },
{ "Save experimental", { 0, "Experimenteel opslaan","Experimentell speichern","Enregistrer l'expérimental","Guardar experimental","Salva sperimentale","Сохранить экспериментальное","Зберегти експериментальне","Запази експерименталното","Сачувај експериментално","Αποθήκευση πειραματικών" } },
{ "Save quick replies", { 0, "Snelle antwoorden opslaan","Schnellantworten speichern","Enregistrer les réponses rapides","Guardar respuestas rápidas","Salva risposte rapide","Сохранить быстрые ответы","Зберегти швидкі відповіді","Запази бързите отговори","Сачувај брзе одговоре","Αποθήκευση γρήγορων απαντήσεων" } },
{ "Already exists or table full.", { 0, "Bestaat al of tabel vol.","Existiert bereits oder Tabelle voll.","Existe déjà ou table pleine.","Ya existe o tabla llena.","Esiste già o tabella piena.","Уже существует или таблица заполнена.","Вже існує або таблиця заповнена.","Вече съществува или таблицата е пълна.","Већ постоји или је табела пуна.","Υπάρχει ήδη ή ο πίνακας είναι πλήρης." ,"Já existe ou tabela cheia."} },
{ "Battery calibration reset to default", { 0, "Batterijkalibratie teruggezet","Batteriekalibrierung zurückgesetzt","Étalonnage batterie réinitialisé","Calibración de batería restablecida","Calibrazione batteria ripristinata","Калибровка батареи сброшена","Калібрування батареї скинуто","Калибрирането на батерията е нулирано","Калибрација батерије ресетована","Επαναφορά βαθμονόμησης μπαταρίας" ,"Calibração da bateria redefinida"} },
{ "Battery read too low — charge fully first", { 0, "Batterijwaarde te laag — laad eerst volledig op","Batteriewert zu niedrig — zuerst voll laden","Lecture batterie trop basse — chargez d'abord","Lectura de batería muy baja — carga primero","Lettura batteria troppo bassa — carica prima","Заряд слишком низкий — сначала зарядите","Заряд надто низький — спершу зарядіть","Отчитането е твърде ниско — заредете първо","Очитавање прениско — прво напуните","Πολύ χαμηλή μέτρηση — φορτίστε πρώτα" ,"Leitura da bateria muito baixa — carregue até o fim primeiro"} },
{ "Connect Wi-Fi to reload tiles", { 0, "Verbind wifi om tegels te herladen","WLAN verbinden, um Kacheln neu zu laden","Connectez le Wi-Fi pour recharger les tuiles","Conecta Wi-Fi para recargar teselas","Connetti Wi-Fi per ricaricare i riquadri","Подключите Wi-Fi для перезагрузки тайлов","Підключіть Wi-Fi, щоб перезавантажити плитки","Свържете Wi-Fi за презареждане на плочките","Повежите Wi-Fi да поново учитате плочице","Συνδέστε Wi-Fi για επαναφόρτωση πλακιδίων" ,"Conecte o Wi-Fi para recarregar os tiles"} },
{ "Diagnostics are on the About page.", { 0, "Diagnostiek staat op de pagina Over.","Diagnose ist auf der Seite Über.","Les diagnostics sont sur la page À propos.","El diagnóstico está en la página Acerca de.","La diagnostica è nella pagina Informazioni.","Диагностика на странице «О программе».","Діагностика на сторінці «Про програму».","Диагностиката е на страницата Относно.","Дијагностика је на страници О програму.","Τα διαγνωστικά είναι στη σελίδα Σχετικά." ,"O diagnóstico está na página Sobre."} },
{ "Edit in Settings → Quick replies", { 0, "Bewerk in Instellingen → Snelle antwoorden","Bearbeiten in Einstellungen → Schnellantworten","Modifier dans Paramètres → Réponses rapides","Editar en Ajustes → Respuestas rápidas","Modifica in Impostazioni → Risposte rapide","Изменить в Настройки → Быстрые ответы","Редагувати в Налаштування → Швидкі відповіді","Редактирай в Настройки → Бързи отговори","Уреди у Подешавања → Брзи одговори","Επεξεργασία στις Ρυθμίσεις → Γρήγορες απαντήσεις" ,"Edite em Ajustes → Respostas rápidas"} },
{ "Export failed (can't open file)", { 0, "Export mislukt (kan bestand niet openen)","Export fehlgeschlagen (Datei nicht zu öffnen)","Échec de l'export (fichier illisible)","Error de exportación (no se abre el archivo)","Esportazione non riuscita (file non apribile)","Сбой экспорта (не удаётся открыть файл)","Помилка експорту (не вдалося відкрити файл)","Експортът неуспешен (файлът не се отваря)","Извоз није успео (датотека се не отвара)","Αποτυχία εξαγωγής (αδυναμία ανοίγματος)" ,"Falha ao exportar (não abre o arquivo)"} },
{ "Favorites unsupported", { 0, "Favorieten niet ondersteund","Favoriten nicht unterstützt","Favoris non pris en charge","Favoritos no compatibles","Preferiti non supportati","Избранное не поддерживается","Обране не підтримується","Любими не се поддържат","Омиљени нису подржани","Τα αγαπημένα δεν υποστηρίζονται" ,"Favoritos sem suporte"} },
{ "Format failed (no card / SD error)", { 0, "Formatteren mislukt (geen kaart / SD-fout)","Formatieren fehlgeschlagen (keine Karte / SD-Fehler)","Échec du formatage (pas de carte / erreur SD)","Error de formato (sin tarjeta / error SD)","Formattazione non riuscita (nessuna scheda / errore SD)","Сбой форматирования (нет карты / ошибка SD)","Помилка форматування (немає картки / помилка SD)","Форматирането неуспешно (няма карта / SD грешка)","Форматирање није успело (нема картице / SD грешка)","Αποτυχία διαμόρφωσης (χωρίς κάρτα / σφάλμα SD)" ,"Falha ao formatar (sem cartão / erro SD)"} },
{ "Import failed (bad/unreadable JSON)", { 0, "Import mislukt (ongeldige JSON)","Import fehlgeschlagen (ungültiges JSON)","Échec de l'import (JSON illisible)","Error de importación (JSON no válido)","Importazione non riuscita (JSON non valido)","Сбой импорта (неверный JSON)","Помилка імпорту (некоректний JSON)","Импортът неуспешен (невалиден JSON)","Увоз није успео (неисправан JSON)","Αποτυχία εισαγωγής (μη έγκυρο JSON)" ,"Falha ao importar (JSON inválido/ilegível)"} },
{ "Import: storage unavailable", { 0, "Import: opslag niet beschikbaar","Import: Speicher nicht verfügbar","Import : stockage indisponible","Importar: almacenamiento no disponible","Importa: archiviazione non disponibile","Импорт: хранилище недоступно","Імпорт: сховище недоступне","Импорт: хранилището е недостъпно","Увоз: складиште недоступно","Εισαγωγή: μη διαθέσιμος χώρος" ,"Importar: armazenamento indisponível"} },
{ "Internal has no folders", { 0, "Intern heeft geen mappen","Intern hat keine Ordner","Aucun dossier en interne","El interno no tiene carpetas","L'interno non ha cartelle","Во внутренней памяти нет папок","У внутрішній пам'яті немає тек","Вътрешната памет няма папки","Интерна нема фасцикле","Η εσωτερική δεν έχει φακέλους" ,"O interno não tem pastas"} },
{ "Invalid max hops", { 0, "Ongeldige max hops","Ungültige max. Hops","Sauts max invalides","Saltos máx no válidos","Hop max non validi","Неверное макс. число хопов","Невірне макс. число хопів","Невалидни макс. скокове","Неисправни макс. скокови","Μη έγκυρα μέγ. άλματα" ,"Máx. saltos inválido"} },
{ "Invalid radio values", { 0, "Ongeldige radiowaarden","Ungültige Funkwerte","Valeurs radio invalides","Valores de radio no válidos","Valori radio non validi","Неверные значения радио","Невірні значення радіо","Невалидни радио стойности","Неисправне радио вредности","Μη έγκυρες τιμές ράδιο" ,"Valores de rádio inválidos"} },
{ "Max zoom for this pack", { 0, "Max zoom voor dit pakket","Max. Zoom für dieses Paket","Zoom max pour ce pack","Zoom máx para este paquete","Zoom max per questo pacchetto","Макс. зум для этого пакета","Макс. зум для цього пакета","Макс. мащаб за този пакет","Макс. зум за овај пакет","Μέγ. ζουμ για αυτό το πακέτο" ,"Zoom máx. para este pacote"} },
{ "Min zoom for this pack", { 0, "Min zoom voor dit pakket","Min. Zoom für dieses Paket","Zoom min pour ce pack","Zoom mín para este paquete","Zoom min per questo pacchetto","Мин. зум для этого пакета","Мін. зум для цього пакета","Мин. мащаб за този пакет","Мин. зум за овај пакет","Ελάχ. ζουμ για αυτό το πακέτο" ,"Zoom mín. para este pacote"} },
{ "Mode: BLE starting / low memory", { 0, "Modus: BLE start / weinig geheugen","Modus: BLE startet / wenig Speicher","Mode : BLE démarre / mémoire faible","Modo: BLE iniciando / poca memoria","Modalità: BLE avvio / memoria scarsa","Режим: BLE запускается / мало памяти","Режим: BLE запускається / мало пам'яті","Режим: BLE стартира / малко памет","Режим: BLE стартује / мало меморије","Λειτουργία: BLE εκκινεί / λίγη μνήμη" ,"Modo: BLE iniciando / pouca memória"} },
{ "Name can't be empty.", { 0, "Naam mag niet leeg zijn.","Name darf nicht leer sein.","Le nom ne peut pas être vide.","El nombre no puede estar vacío.","Il nome non può essere vuoto.","Имя не может быть пустым.","Ім'я не може бути порожнім.","Името не може да е празно.","Име не може бити празно.","Το όνομα δεν μπορεί να είναι κενό." ,"O nome não pode ficar vazio."} },
{ "No DM key", { 0, "Geen DM-sleutel","Kein DN-Schlüssel","Pas de clé MP","Sin clave de MD","Nessuna chiave MD","Нет ключа ЛС","Немає ключа ПП","Няма ключ за ЛС","Нема ДП кључа","Χωρίς κλειδί ΠΜ" ,"Sem chave de DM"} },
{ "No contact for thread", { 0, "Geen contact voor gesprek","Kein Kontakt für Unterhaltung","Aucun contact pour ce fil","Sin contacto para el hilo","Nessun contatto per la conversazione","Нет контакта для темы","Немає контакту для гілки","Няма контакт за разговора","Нема контакта за разговор","Καμία επαφή για το νήμα" ,"Sem contato para a conversa"} },
{ "No contact to trace", { 0, "Geen contact om te traceren","Kein Kontakt zum Verfolgen","Aucun contact à tracer","Sin contacto para rastrear","Nessun contatto da tracciare","Нет контакта для трассировки","Немає контакту для трасування","Няма контакт за проследяване","Нема контакта за праћење","Καμία επαφή για ιχνηλάτηση" ,"Sem contato para rastrear"} },
{ "No tiles (non-ESP32)", { 0, "Geen tegels (niet-ESP32)","Keine Kacheln (kein ESP32)","Pas de tuiles (non-ESP32)","Sin teselas (no ESP32)","Nessun riquadro (non-ESP32)","Нет тайлов (не ESP32)","Немає плиток (не ESP32)","Няма плочки (не ESP32)","Нема плочица (није ESP32)","Χωρίς πλακίδια (μη ESP32)" ,"Sem tiles (não ESP32)"} },
{ "Pubkey must be 64 hex chars.", { 0, "Pubkey moet 64 hex-tekens zijn.","Pubkey muss 64 Hex-Zeichen haben.","La clé publique doit faire 64 hex.","La clave pública debe tener 64 hex.","La chiave pubblica deve avere 64 hex.","Откр. ключ — 64 hex-символа.","Відкр. ключ — 64 hex-символи.","Публичният ключ трябва да е 64 hex.","Јавни кључ мора бити 64 hex.","Το δημόσιο κλειδί πρέπει 64 hex." ,"A chave pública deve ter 64 hex."} },
{ "RX log", { 0, "RX-logboek","RX-Protokoll","Journal RX","Registro RX","Log RX","Журнал RX","Журнал RX","RX дневник","RX дневник","Αρχείο RX" ,"Log RX"} },
{ "Raw log", { 0, "Ruwe log","Roh-Protokoll","Journal brut","Registro sin procesar","Log grezzo","Сырой журнал","Сирий журнал","Суров дневник","Сирови дневник","Ακατέργαστο αρχείο" ,"Log bruto"} },
{ "RangeTest failed", { 0, "RangeTest mislukt","RangeTest fehlgeschlagen","Échec du RangeTest","Error de RangeTest","RangeTest non riuscito","Сбой RangeTest","Помилка RangeTest","RangeTest неуспешен","RangeTest није успео","Αποτυχία RangeTest" ,"Falha no RangeTest"} },
{ "Telemetry req failed", { 0, "Telemetrie-verzoek mislukt","Telemetrie-Anfrage fehlgeschlagen","Échec de la requête télémétrie","Error de solicitud de telemetría","Richiesta telemetria non riuscita","Сбой запроса телеметрии","Помилка запиту телеметрії","Заявката за телеметрия неуспешна","Захтев за телеметрију није успео","Αποτυχία αιτήματος τηλεμετρίας" ,"Falha na req. de telemetria"} },
{ "Trace failed", { 0, "Tracering mislukt","Trace fehlgeschlagen","Échec du traçage","Error de rastreo","Tracciamento non riuscito","Сбой трассировки","Помилка трасування","Проследяването неуспешно","Праћење није успело","Αποτυχία ιχνηλάτησης" ,"Falha no rastreamento"} },
{ "Tile reload needs Wi-Fi build", { 0, "Tegels herladen vereist wifi-build","Kachel-Neuladen braucht WLAN-Build","Le rechargement des tuiles requiert le build Wi-Fi","Recargar teselas requiere build Wi-Fi","La ricarica riquadri richiede build Wi-Fi","Перезагрузка тайлов требует Wi-Fi сборки","Перезавантаження плиток потребує Wi-Fi збірки","Презареждането изисква Wi-Fi компилация","Поновно учитавање захтева Wi-Fi билд","Η επαναφόρτωση χρειάζεται έκδοση Wi-Fi" ,"Recarregar tiles exige build com Wi-Fi"} },
{ "Too large to edit (>8 KB)", { 0, "Te groot om te bewerken (>8 KB)","Zu groß zum Bearbeiten (>8 KB)","Trop grand à modifier (>8 Ko)","Demasiado grande para editar (>8 KB)","Troppo grande da modificare (>8 KB)","Слишком велик для редактирования (>8 КБ)","Завеликий для редагування (>8 КБ)","Твърде голям за редактиране (>8 KB)","Превелико за уређивање (>8 KB)","Πολύ μεγάλο για επεξεργασία (>8 KB)" ,"Grande demais para editar (>8 KB)"} },
{ "Wi-Fi is not enabled in this build.", { 0, "Wi-Fi is niet ingeschakeld in deze build.","WLAN ist in diesem Build nicht aktiviert.","Le Wi-Fi n'est pas activé dans ce build.","Wi-Fi no está habilitado en este build.","Il Wi-Fi non è abilitato in questo build.","Wi-Fi не включён в этой сборке.","Wi-Fi не увімкнено в цій збірці.","Wi-Fi не е разрешен в тази компилация.","Wi-Fi није омогућен у овом билду.","Το Wi-Fi δεν είναι ενεργό σε αυτή την έκδοση." ,"O Wi-Fi não está ativado neste build."} },
{ "Wi-Fi isn't available on this build.", { 0, "Wi-Fi is niet beschikbaar in deze build.","WLAN ist in diesem Build nicht verfügbar.","Le Wi-Fi n'est pas disponible sur ce build.","Wi-Fi no está disponible en este build.","Il Wi-Fi non è disponibile su questo build.","Wi-Fi недоступен в этой сборке.","Wi-Fi недоступний у цій збірці.","Wi-Fi не е наличен в тази компилация.","Wi-Fi није доступан на овом билду.","Το Wi-Fi δεν είναι διαθέσιμο σε αυτή την έκδοση." ,"O Wi-Fi não está disponível neste build."} },
{ "Analyzing terrain…", { 0, "Terrein analyseren…","Gelände wird analysiert…","Analyse du terrain…","Analizando terreno…","Analisi del terreno…","Анализ рельефа…","Аналіз рельєфу…","Анализиране на терена…","Анализа терена…","Ανάλυση εδάφους…" ,"Analisando o terreno…"} },
{ "Exporting settings…", { 0, "Instellingen exporteren…","Einstellungen werden exportiert…","Exportation des réglages…","Exportando ajustes…","Esportazione impostazioni…","Экспорт настроек…","Експорт налаштувань…","Експортиране на настройки…","Извоз подешавања…","Εξαγωγή ρυθμίσεων…" ,"Exportando ajustes…"} },
{ "Importing settings…", { 0, "Instellingen importeren…","Einstellungen werden importiert…","Importation des réglages…","Importando ajustes…","Importazione impostazioni…","Импорт настроек…","Імпорт налаштувань…","Импортиране на настройки…","Увоз подешавања…","Εισαγωγή ρυθμίσεων…" ,"Importando ajustes…"} },
{ "Trace sent…", { 0, "Tracering verzonden…","Trace gesendet…","Traçage envoyé…","Rastreo enviado…","Traccia inviata…","Трассировка отправлена…","Трасування надіслано…","Проследяването е изпратено…","Праћење послато…","Στάλθηκε ιχνηλάτηση…" ,"Rastreamento enviado…"} },
{ "Tracing route…", { 0, "Route traceren…","Route wird verfolgt…","Traçage de l'itinéraire…","Rastreando ruta…","Tracciamento percorso…","Трассировка маршрута…","Трасування маршруту…","Проследяване на маршрута…","Праћење руте…","Ιχνηλάτηση διαδρομής…" ,"Rastreando rota…"} },
{ "Rotating… rebooting", { 0, "Draaien… opnieuw opstarten","Drehen… Neustart","Rotation… redémarrage","Girando… reiniciando","Rotazione… riavvio","Поворот… перезагрузка","Обертання… перезавантаження","Завъртане… рестарт","Ротација… рестарт","Περιστροφή… επανεκκίνηση" ,"Girando… reiniciando"} },
{ "Saved — rebooting", { 0, "Opgeslagen — opnieuw opstarten","Gespeichert — Neustart","Enregistré — redémarrage","Guardado — reiniciando","Salvato — riavvio","Сохранено — перезагрузка","Збережено — перезавантаження","Запазено — рестарт","Сачувано — рестарт","Αποθηκεύτηκε — επανεκκίνηση" ,"Salvo — reiniciando"} },
{ "Saved — reconnecting…", { 0, "Opgeslagen — opnieuw verbinden…","Gespeichert — verbinde neu…","Enregistré — reconnexion…","Guardado — reconectando…","Salvato — riconnessione…","Сохранено — переподключение…","Збережено — перепідключення…","Запазено — повторно свързване…","Сачувано — поновно повезивање…","Αποθηκεύτηκε — επανασύνδεση…" ,"Salvo — reconectando…"} },
{ "SSID set — enter password, then Save", { 0, "SSID ingesteld — voer wachtwoord in, dan Opslaan","SSID gesetzt — Passwort eingeben, dann Speichern","SSID défini — saisissez le mot de passe, puis Enregistrer","SSID establecido — escribe la contraseña y Guardar","SSID impostato — inserisci la password, poi Salva","SSID задан — введите пароль, затем Сохранить","SSID задано — введіть пароль, потім Зберегти","SSID зададен — въведете парола, после Запази","SSID постављен — унесите лозинку, па Сачувај","Ορίστηκε SSID — εισαγάγετε κωδικό, μετά Αποθήκευση" ,"SSID definido — digite a senha e Salvar"} },
{ "Powering off… click trackball to wake", { 0, "Uitschakelen… klik trackball om te wekken","Schaltet aus… Trackball klicken zum Aufwecken","Extinction… cliquez la boule pour réveiller","Apagando… pulsa el trackball para despertar","Spegnimento… clicca la trackball per riattivare","Выключение… нажмите трекбол для пробуждения","Вимкнення… натисніть трекбол для пробудження","Изключване… щракнете топчето за събуждане","Гашење… кликните куглицу за буђење","Απενεργοποίηση… κλικ στο trackball για αφύπνιση" ,"Desligando… clique o trackball para acordar"} },
{ "Roll to highlight • click to insert", { 0, "Rol om te markeren • klik om in te voegen","Rollen zum Hervorheben • Klick zum Einfügen","Faites rouler pour surligner • cliquez pour insérer","Desplaza para resaltar • pulsa para insertar","Scorri per evidenziare • clicca per inserire","Прокрутите для выбора • нажмите для вставки","Прокрутіть для вибору • натисніть для вставки","Превъртете за маркиране • щракнете за вмъкване","Котрљајте за истицање • кликните за уметање","Κυλήστε για επισήμανση • κλικ για εισαγωγή" ,"Role para destacar • clique para inserir"} },
{ "mesh link armed", { 0, "mesh link actief","Mesh-Link scharf","liaison mesh armée","enlace mesh armado","link mesh armato","меш-канал готов","меш-зв'язок готовий","меш връзка готова","меш веза спремна","σύνδεσμος mesh ενεργός" ,"enlace mesh pronto"} },
{ "n/a in this build", { 0, "n.v.t. in deze build","n. v. in diesem Build","n/d dans ce build","n/d en este build","n/d in questo build","н/д в этой сборке","н/д у цій збірці","н/п в тази компилация","н/д у овом билду","μ/δ σε αυτή την έκδοση" ,"n/d neste build"} },
{ "tap to exit full screen", { 0, "tik om volledig scherm te verlaten","tippen zum Verlassen des Vollbilds","toucher pour quitter le plein écran","toca para salir de pantalla completa","tocca per uscire da schermo intero","нажмите для выхода из полноэкранного режима","торкніться, щоб вийти з повноекранного режиму","докоснете за изход от цял екран","додирните за излаз из целог екрана","πατήστε για έξοδο από πλήρη οθόνη" ,"toque para sair da tela cheia"} },
{ "double-tap SPACE cycles through the ones you enable", { 0, "dubbeltik op SPATIE wisselt tussen de ingeschakelde","Doppeltipp auf LEER wechselt die aktivierten","double-tapez ESPACE pour parcourir les activés","doble toque en ESPACIO recorre los activados","doppio tocco su SPAZIO scorre quelli attivi","двойное нажатие ПРОБЕЛ переключает включённые","подвійне натискання ПРОБІЛ перемикає увімкнені","двойно докосване на ИНТЕРВАЛ превърта включените","двоструки додир РАЗМАК пролази кроз омогућене","διπλό πάτημα ΔΙΑΣΤΗΜΑ εναλλάσσει τα ενεργά" ,"toque duplo em ESPAÇO percorre os que você ativar"} },
{ "pick accented letters as you type; off = plain typing", { 0, "kies letters met accenten tijdens het typen; uit = gewoon typen","Akzentbuchstaben beim Tippen wählen; aus = einfaches Tippen","choisir les lettres accentuées en tapant ; désactivé = saisie simple","elige letras acentuadas al escribir; desactivado = escritura simple","scegli lettere accentate mentre scrivi; off = scrittura semplice","выбор букв с акцентами при наборе; выкл = обычный набор","вибір літер з акцентами під час набору; вимк = звичайний набір","избор на букви с ударения при писане; изкл = обикновено писане","бирање слова са акцентима док куцате; искљ = обично куцање","επιλογή τονισμένων γραμμάτων κατά την πληκτρολόγηση· ανενεργό = απλή" ,"escolha letras acentuadas ao digitar; desligado = digitação simples"} },
{ "tap the language key (e.g. EN) on the keyboard to cycle the ones you enable", { 0, "tik op de taaltoets (bijv. EN) op het toetsenbord om de ingeschakelde te doorlopen","Sprachtaste (z. B. EN) auf der Tastatur antippen, um die aktivierten zu wechseln","touchez la touche de langue (ex. EN) du clavier pour parcourir les activées","toca la tecla de idioma (p. ej. EN) del teclado para recorrer las activadas","tocca il tasto lingua (es. EN) sulla tastiera per scorrere quelle attive","нажмите клавишу языка (напр. EN) на клавиатуре для переключения включённых","натисніть клавішу мови (напр. EN) на клавіатурі для перемикання увімкнених","докоснете клавиша за език (напр. EN) на клавиатурата, за да превъртате включените","додирните тастер језика (нпр. EN) на тастатури да пролазите кроз омогућене","πατήστε το πλήκτρο γλώσσας (π.χ. EN) στο πληκτρολόγιο για εναλλαγή" ,"toque na tecla de idioma (ex. EN) no teclado para percorrer os que você ativar"} },
{ "Sample text", { 0, "Voorbeeldtekst","Beispieltext","Exemple de texte","Texto de ejemplo","Testo di esempio","Пример текста","Зразок тексту","Примерен текст","Пример текста","Δείγμα κειμένου" ,"Texto de exemplo"} },
{ "Save & restart", { 0, "Opslaan & herstarten","Speichern & neu starten","Enregistrer & redémarrer","Guardar y reiniciar","Salva e riavvia","Сохранить и перезагрузить","Зберегти й перезавантажити","Запази и рестартирай","Сачувај и рестартуј","Αποθήκευση & επανεκκίνηση" ,"Salvar e reiniciar"} },
{ "Save / turn on", { 0, "Opslaan / inschakelen","Speichern / einschalten","Enregistrer / activer","Guardar / activar","Salva / attiva","Сохранить / включить","Зберегти / увімкнути","Запази / включи","Сачувај / укључи","Αποθήκευση / ενεργοποίηση" ,"Salvar / ligar"} },
{ "Save auto-add", { 0, "Auto-toevoegen opslaan","Auto-Hinzufügen speichern","Enregistrer l'ajout auto","Guardar auto-añadir","Salva auto-aggiunta","Сохранить автодобавление","Зберегти автододавання","Запази авто-добавянето","Сачувај аутододавање","Αποθήκευση αυτόματης προσθήκης" ,"Salvar auto-adição"} },
{ "Save experimental", { 0, "Experimenteel opslaan","Experimentell speichern","Enregistrer l'expérimental","Guardar experimental","Salva sperimentale","Сохранить экспериментальное","Зберегти експериментальне","Запази експерименталното","Сачувај експериментално","Αποθήκευση πειραματικών" ,"Salvar experimental"} },
{ "Save quick replies", { 0, "Snelle antwoorden opslaan","Schnellantworten speichern","Enregistrer les réponses rapides","Guardar respuestas rápidas","Salva risposte rapide","Сохранить быстрые ответы","Зберегти швидкі відповіді","Запази бързите отговори","Сачувај брзе одговоре","Αποθήκευση γρήγορων απαντήσεων" ,"Salvar respostas rápidas"} },
// ---- Sweep pass 5 (final): long help / error dialogs ----
{ "32-hex secret. Anyone with this can read the channel.", { 0, "32-hex geheim. Iedereen hiermee kan het kanaal lezen.","32-Hex-Geheimnis. Wer es hat, kann den Kanal lesen.","Secret 32 hex. Quiconque l'a peut lire le canal.","Secreto de 32 hex. Cualquiera con esto puede leer el canal.","Segreto 32 hex. Chiunque lo abbia può leggere il canale.","Секрет 32 hex. Любой с ним может читать канал.","Секрет 32 hex. Будь-хто з ним може читати канал.","32-hex тайна. Всеки с нея може да чете канала.","32-hex тајна. Свако с њом може читати канал.","Μυστικό 32 hex. Όποιος το έχει διαβάζει το κανάλι." } },
{ "Anyone can join. Key is derived from the hashtag (lowercase).", { 0, "Iedereen kan deelnemen. Sleutel komt van de hashtag (kleine letters).","Jeder kann beitreten. Schlüssel aus dem Hashtag (Kleinbuchstaben).","Tout le monde peut rejoindre. Clé dérivée du hashtag (minuscules).","Cualquiera puede unirse. La clave deriva del hashtag (minúsculas).","Chiunque può unirsi. La chiave deriva dall'hashtag (minuscolo).","Любой может присоединиться. Ключ из хэштега (нижний регистр).","Будь-хто може приєднатися. Ключ із хештегу (нижній регістр).","Всеки може да се присъедини. Ключът е от хаштага (малки букви).","Свако може да се придружи. Кључ из хаштага (мала слова).","Όλοι μπορούν να συμμετάσχουν. Κλειδί από το hashtag (πεζά)." } },
{ "Enter the 32-hex secret shared by the channel creator.", { 0, "Voer het 32-hex geheim in dat de kanaalmaker deelt.","Geben Sie das 32-Hex-Geheimnis des Erstellers ein.","Saisissez le secret 32 hex partagé par le créateur du canal.","Introduce el secreto de 32 hex que comparte el creador del canal.","Inserisci il segreto 32 hex condiviso dal creatore del canale.","Введите секрет 32 hex, переданный создателем канала.","Введіть секрет 32 hex від творця каналу.","Въведете 32-hex тайната, споделена от създателя на канала.","Унесите 32-hex тајну коју је поделио творац канала.","Εισαγάγετε το μυστικό 32 hex του δημιουργού του καναλιού." } },
{ "Paste the 64-hex public key and a name. Useful when you want to DM someone before their advert has arrived.", { 0, "Plak de 64-hex publieke sleutel en een naam. Handig om iemand te DM'en voordat hun advert is aangekomen.","Fügen Sie den 64-Hex-Schlüssel und einen Namen ein. Nützlich, um jemandem zu schreiben, bevor sein Advert eintraf.","Collez la clé publique 64 hex et un nom. Utile pour envoyer un MP avant l'arrivée de l'annonce.","Pega la clave pública de 64 hex y un nombre. Útil para enviar un MD antes de que llegue su anuncio.","Incolla la chiave pubblica 64 hex e un nome. Utile per inviare un MD prima dell'advert.","Вставьте открытый ключ 64 hex и имя. Удобно написать до прихода advert.","Вставте відкритий ключ 64 hex та ім'я. Зручно написати до приходу advert.","Поставете 64-hex публичния ключ и име. Полезно за ЛС преди advert-а.","Налепите 64-hex јавни кључ и име. Корисно за ДП пре advert-а.","Επικολλήστε το δημόσιο κλειδί 64 hex και ένα όνομα. Χρήσιμο για ΠΜ πριν το advert." } },
{ "Share the 32-char secret so others can join. Leave the secret empty to generate a random one.", { 0, "Deel het 32-teken geheim zodat anderen kunnen deelnemen. Leeg laten voor een willekeurig geheim.","Teilen Sie das 32-Zeichen-Geheimnis, damit andere beitreten. Leer lassen für ein zufälliges.","Partagez le secret de 32 caractères pour que d'autres rejoignent. Laissez vide pour un aléatoire.","Comparte el secreto de 32 caracteres para que otros se unan. Déjalo vacío para uno aleatorio.","Condividi il segreto di 32 caratteri per far unire altri. Lascia vuoto per uno casuale.","Поделитесь секретом из 32 символов, чтобы другие присоединились. Пусто = случайный.","Поділіться секретом із 32 символів, щоб інші приєдналися. Порожньо = випадковий.","Споделете 32-символната тайна, за да се присъединят други. Празно = случайна.","Поделите тајну од 32 знака да се други придруже. Празно = насумична.","Μοιραστείτε το μυστικό 32 χαρακτήρων για να συμμετάσχουν άλλοι. Κενό = τυχαίο." } },
{ "Region scope (#tag) for this channel.\nBlank = use the default scope.", { 0, "Regiobereik (#tag) voor dit kanaal.\nLeeg = standaardbereik gebruiken.","Regionsbereich (#tag) für diesen Kanal.\nLeer = Standardbereich verwenden.","Portée régionale (#tag) pour ce canal.\nVide = portée par défaut.","Alcance de región (#tag) para este canal.\nVacío = alcance predeterminado.","Ambito regione (#tag) per questo canale.\nVuoto = ambito predefinito.","Регион (#tag) для этого канала.\nПусто = область по умолчанию.","Регіон (#tag) для цього каналу.\nПорожньо = область за умовчанням.","Регионален обхват (#tag) за този канал.\nПразно = обхват по подразбиране.","Опсег региона (#tag) за овај канал.\nПразно = подразумевани опсег.","Εύρος περιοχής (#tag) για αυτό το κανάλι.\nΚενό = προεπιλεγμένο εύρος." } },
{ "Map storage error.\nReflash the tiles partition.", { 0, "Kaartopslagfout.\nFlash de tiles-partitie opnieuw.","Kartenspeicherfehler.\nTiles-Partition neu flashen.","Erreur de stockage carte.\nReflashez la partition des tuiles.","Error de almacenamiento del mapa.\nReinstala la partición de teselas.","Errore archiviazione mappa.\nRiflasha la partizione tiles.","Ошибка хранилища карты.\nПерепрошейте раздел tiles.","Помилка сховища карти.\nПерепрошийте розділ tiles.","Грешка в хранилището на картата.\nПрефлашнете дяла tiles.","Грешка складишта мапе.\nПоново флешујте tiles партицију.","Σφάλμα αποθήκευσης χάρτη.\nΞαναφλασάρετε το tiles partition." } },
{ "Map storage error.\n\nSD card detected but the\ntile cache didn't mount.\nReboot to retry.", { 0, "Kaartopslagfout.\n\nSD-kaart gedetecteerd maar de\ntegelcache kon niet koppelen.\nHerstart om opnieuw te proberen.","Kartenspeicherfehler.\n\nSD-Karte erkannt, aber der\nKachel-Cache wurde nicht eingebunden.\nNeustart zum Wiederholen.","Erreur de stockage carte.\n\nCarte SD détectée mais le\ncache de tuiles n'a pas monté.\nRedémarrez pour réessayer.","Error de almacenamiento del mapa.\n\nTarjeta SD detectada pero la\ncaché de teselas no se montó.\nReinicia para reintentar.","Errore archiviazione mappa.\n\nScheda SD rilevata ma la\ncache riquadri non si è montata.\nRiavvia per riprovare.","Ошибка хранилища карты.\n\nSD-карта найдена, но кэш\nтайлов не смонтирован.\nПерезагрузите для повтора.","Помилка сховища карти.\n\nSD-картку знайдено, але кеш\nплиток не змонтовано.\nПерезавантажте для повтору.","Грешка в хранилището.\n\nSD картата е открита, но кешът\nна плочките не се монтира.\nРестартирайте за нов опит.","Грешка складишта мапе.\n\nSD картица откривена, али кеш\nплочица није монтиран.\nРестартујте за поновни покушај.","Σφάλμα αποθήκευσης χάρτη.\n\nΕντοπίστηκε κάρτα SD αλλά η\nμνήμη πλακιδίων δεν προσαρτήθηκε.\nΕπανεκκίνηση για νέα προσπάθεια." } },
{ "Map — set your location\nin Settings → Profile to\nshow the map here.", { 0, "Kaart — stel je locatie in\nbij Instellingen → Profiel om\nde kaart hier te tonen.","Karte — Standort in\nEinstellungen → Profil festlegen,\num die Karte hier zu zeigen.","Carte — définissez votre position\ndans Paramètres → Profil pour\nafficher la carte ici.","Mapa — establece tu ubicación\nen Ajustes → Perfil para\nmostrar el mapa aquí.","Mappa — imposta la posizione\nin Impostazioni → Profilo per\nmostrare la mappa qui.","Карта — задайте местоположение\nв Настройки → Профиль, чтобы\nпоказать карту здесь.","Карта — задайте місцезнаходження\nу Налаштування → Профіль, щоб\nпоказати карту тут.","Карта — задайте местоположението\nв Настройки → Профил, за да\nсе покаже картата тук.","Мапа — поставите локацију\nу Подешавања → Профил да\nприкажете мапу овде.","Χάρτης — ορίστε την τοποθεσία\nστις Ρυθμίσεις → Προφίλ για\nνα εμφανιστεί ο χάρτης εδώ." } },
{ "No .json backups found.\nExport one first, or copy a\nmeshcore-backup.json to the\nSD card or internal flash.", { 0, "Geen .json back-ups gevonden.\nExporteer er eerst een, of kopieer\neen meshcore-backup.json naar de\nSD-kaart of interne flash.","Keine .json-Backups gefunden.\nZuerst eins exportieren oder\nmeshcore-backup.json auf die\nSD-Karte oder den Flash kopieren.","Aucune sauvegarde .json trouvée.\nExportez-en une, ou copiez un\nmeshcore-backup.json sur la\ncarte SD ou le flash interne.","No se hallaron copias .json.\nExporta una, o copia un\nmeshcore-backup.json a la\ntarjeta SD o flash interna.","Nessun backup .json trovato.\nEsportane uno, o copia un\nmeshcore-backup.json sulla\nscheda SD o flash interna.","Резервные копии .json не найдены.\nЭкспортируйте одну или скопируйте\nmeshcore-backup.json на\nSD-карту или внутр. флеш.","Резервних копій .json не знайдено.\nЕкспортуйте одну або скопіюйте\nmeshcore-backup.json на\nSD-картку чи внутр. флеш.","Няма намерени .json архиви.\nЕкспортирайте един или копирайте\nmeshcore-backup.json на\nSD картата или вътр. флаш.","Нема .json резервних копија.\nИзвезите једну или копирајте\nmeshcore-backup.json на\nSD картицу или интерни флеш.","Δεν βρέθηκαν αντίγραφα .json.\nΕξαγάγετε ένα ή αντιγράψτε\nmeshcore-backup.json στην\nκάρτα SD ή στο εσωτ. flash." } },
{ "No JPEGs found in /lock/.\nAdd images via the file\nmanager or an SD card.", { 0, "Geen JPEG's gevonden in /lock/.\nVoeg afbeeldingen toe via de\nbestandsbeheerder of een SD-kaart.","Keine JPEGs in /lock/ gefunden.\nBilder über den Dateimanager\noder eine SD-Karte hinzufügen.","Aucun JPEG dans /lock/.\nAjoutez des images via le\ngestionnaire ou une carte SD.","No hay JPEG en /lock/.\nAñade imágenes con el gestor\nde archivos o una tarjeta SD.","Nessun JPEG in /lock/.\nAggiungi immagini dal gestore\nfile o da una scheda SD.","В /lock/ нет JPEG.\nДобавьте изображения через\nфайловый менеджер или SD-карту.","У /lock/ немає JPEG.\nДодайте зображення через\nфайловий менеджер або SD-картку.","Няма JPEG в /lock/.\nДобавете изображения чрез\nфайловия мениджър или SD карта.","Нема JPEG у /lock/.\nДодајте слике преко менаџера\nдатотека или SD картице.","Δεν βρέθηκαν JPEG στο /lock/.\nΠροσθέστε εικόνες μέσω του\nδιαχειριστή αρχείων ή κάρτας SD." } },
{ "No blocked users.\n\nLong-press a message and tap\nBlock to add one.", { 0, "Geen geblokkeerde gebruikers.\n\nHoud een bericht ingedrukt en tik\nop Blokkeren om er een toe te voegen.","Keine blockierten Benutzer.\n\nNachricht lange drücken und auf\nBlockieren tippen, um einen hinzuzufügen.","Aucun utilisateur bloqué.\n\nAppui long sur un message puis\ntouchez Bloquer pour en ajouter un.","Sin usuarios bloqueados.\n\nMantén pulsado un mensaje y toca\nBloquear para añadir uno.","Nessun utente bloccato.\n\nTieni premuto un messaggio e tocca\nBlocca per aggiungerne uno.","Нет заблокированных.\n\nДолго нажмите сообщение и нажмите\nБлокировать, чтобы добавить.","Немає заблокованих.\n\nЗатисніть повідомлення й натисніть\nБлокувати, щоб додати.","Няма блокирани потребители.\n\nЗадръжте съобщение и докоснете\nБлокирай, за да добавите.","Нема блокираних корисника.\n\nДуго притисните поруку и додирните\nБлокирај да додате једног.","Κανένας αποκλεισμένος χρήστης.\n\nΠατήστε παρατεταμένα ένα μήνυμα και\nΑποκλεισμός για προσθήκη." } },
{ "No contacts have shared\na GPS location yet.", { 0, "Nog geen contacten hebben\neen GPS-locatie gedeeld.","Noch keine Kontakte haben\neinen GPS-Standort geteilt.","Aucun contact n'a encore\npartagé de position GPS.","Ningún contacto ha compartido\nuna ubicación GPS aún.","Nessun contatto ha ancora\ncondiviso una posizione GPS.","Контакты ещё не делились\nGPS-местоположением.","Контакти ще не ділилися\nGPS-місцезнаходженням.","Все още няма контакти,\nсподелили GPS местоположение.","Још ниједан контакт није\nподелио GPS локацију.","Καμία επαφή δεν έχει\nμοιραστεί θέση GPS ακόμη." } },
{ "No map storage.\n\nInsert an SD card to cache\nWi-Fi tiles (or reflash to\nrestore the tiles partition).", { 0, "Geen kaartopslag.\n\nPlaats een SD-kaart om wifi-tegels\nte cachen (of flash opnieuw om\nde tiles-partitie te herstellen).","Kein Kartenspeicher.\n\nSD-Karte einlegen, um WLAN-Kacheln\nzu cachen (oder neu flashen, um die\nTiles-Partition wiederherzustellen).","Pas de stockage carte.\n\nInsérez une carte SD pour les\ntuiles Wi-Fi (ou reflashez pour\nrestaurer la partition des tuiles).","Sin almacenamiento de mapa.\n\nInserta una tarjeta SD para teselas\nWi-Fi (o reinstala para restaurar\nla partición de teselas).","Nessuna archiviazione mappa.\n\nInserisci una scheda SD per i\nriquadri Wi-Fi (o riflasha per\nripristinare la partizione tiles).","Нет хранилища карты.\n\nВставьте SD-карту для кэша\nWi-Fi тайлов (или перепрошейте\nдля раздела tiles).","Немає сховища карти.\n\nВставте SD-картку для кешу\nWi-Fi плиток (або перепрошийте\nдля розділу tiles).","Няма хранилище за карта.\n\nПоставете SD карта за кеш на\nWi-Fi плочки (или префлашнете за\nдяла tiles).","Нема складишта мапе.\n\nУбаците SD картицу за кеш\nWi-Fi плочица (или флешујте за\ntiles партицију).","Χωρίς αποθήκευση χάρτη.\n\nΕισαγάγετε κάρτα SD για πλακίδια\nWi-Fi (ή ξαναφλασάρετε για\nτο tiles partition)." } },
{ "PNG isn't supported here\nUse a JPEG", { 0, "PNG wordt hier niet ondersteund\nGebruik een JPEG","PNG wird hier nicht unterstützt\nVerwenden Sie ein JPEG","PNG non pris en charge ici\nUtilisez un JPEG","PNG no se admite aquí\nUsa un JPEG","PNG non supportato qui\nUsa un JPEG","PNG здесь не поддерживается\nИспользуйте JPEG","PNG тут не підтримується\nВикористайте JPEG","PNG не се поддържа тук\nИзползвайте JPEG","PNG овде није подржан\nКористите JPEG","Το PNG δεν υποστηρίζεται εδώ\nΧρησιμοποιήστε JPEG" } },
{ "Still analyzing the previous\npath… try again in a moment.", { 0, "Vorige pad wordt nog\ngeanalyseerd… probeer zo opnieuw.","Vorheriger Pfad wird noch\nanalysiert… gleich erneut versuchen.","Analyse du chemin précédent\nen cours… réessayez bientôt.","Aún analizando la ruta\nanterior… reinténtalo en un momento.","Analisi del percorso precedente\nin corso… riprova tra poco.","Ещё анализируется предыдущий\nпуть… повторите чуть позже.","Ще аналізується попередній\nшлях… спробуйте за мить.","Все още се анализира предишният\nпът… опитайте пак след малко.","Још се анализира претходна\nпутања… покушајте ускоро.","Ακόμη αναλύεται η προηγούμενη\nδιαδρομή… δοκιμάστε σε λίγο." } },
{ "This contact hasn't shared\na GPS position.", { 0, "Dit contact heeft geen\nGPS-positie gedeeld.","Dieser Kontakt hat keine\nGPS-Position geteilt.","Ce contact n'a pas partagé\nde position GPS.","Este contacto no ha compartido\nuna posición GPS.","Questo contatto non ha condiviso\nuna posizione GPS.","Этот контакт не делился\nGPS-позицией.","Цей контакт не ділився\nGPS-позицією.","Този контакт не е споделил\nGPS позиция.","Овај контакт није поделио\nGPS позицију.","Αυτή η επαφή δεν μοιράστηκε\nθέση GPS." } },
{ "Tip: you can scan for networks in Settings → Network after setup.", { 0, "Tip: je kunt netwerken scannen in Instellingen → Netwerk na de installatie.","Tipp: Nach der Einrichtung in Einstellungen → Netzwerk scannen.","Astuce : recherchez des réseaux dans Paramètres → Réseau après la configuration.","Consejo: puedes buscar redes en Ajustes → Red tras la configuración.","Suggerimento: cerca reti in Impostazioni → Rete dopo la configurazione.","Совет: сканировать сети можно в Настройки → Сеть после настройки.","Порада: шукати мережі можна в Налаштування → Мережа після налаштування.","Съвет: можете да сканирате мрежи в Настройки → Мрежа след настройката.","Савет: мреже можете скенирати у Подешавања → Мрежа након подешавања.","Συμβουλή: σαρώστε δίκτυα στις Ρυθμίσεις → Δίκτυο μετά τη ρύθμιση." } },
{ "Type your network name — or switch to Wi-Fi in Settings to scan", { 0, "Typ je netwerknaam — of schakel naar wifi in Instellingen om te scannen","Netzwerknamen eingeben — oder in Einstellungen auf WLAN wechseln zum Scannen","Saisissez le nom du réseau — ou passez au Wi-Fi dans Paramètres pour scanner","Escribe el nombre de la red — o cambia a Wi-Fi en Ajustes para buscar","Digita il nome della rete — o passa al Wi-Fi in Impostazioni per scansionare","Введите имя сети — или включите Wi-Fi в Настройках для поиска","Введіть назву мережі — або увімкніть Wi-Fi у Налаштуваннях для пошуку","Въведете името на мрежата — или включете Wi-Fi в Настройки за сканиране","Унесите назив мреже — или пређите на Wi-Fi у Подешавањима за скенирање","Πληκτρολογήστε το όνομα δικτύου — ή στρέψτε σε Wi-Fi στις Ρυθμίσεις για σάρωση" } },
{ "Update manually at flasher.wadamesh.com\n(Wi-Fi update is paused for now)", { 0, "Werk handmatig bij op flasher.wadamesh.com\n(wifi-update is voorlopig gepauzeerd)","Manuell aktualisieren auf flasher.wadamesh.com\n(WLAN-Update ist vorerst pausiert)","Mettez à jour sur flasher.wadamesh.com\n(la MAJ Wi-Fi est en pause pour l'instant)","Actualiza en flasher.wadamesh.com\n(la actualización Wi-Fi está en pausa)","Aggiorna su flasher.wadamesh.com\n(l'aggiornamento Wi-Fi è in pausa)","Обновите вручную на flasher.wadamesh.com\n(обновление по Wi-Fi пока приостановлено)","Оновіть вручну на flasher.wadamesh.com\n(оновлення через Wi-Fi поки призупинено)","Обновете ръчно на flasher.wadamesh.com\n(Wi-Fi обновяването е спряно засега)","Ажурирајте ручно на flasher.wadamesh.com\n(Wi-Fi ажурирање је за сада паузирано)","Ενημερώστε χειροκίνητα στο flasher.wadamesh.com\n(η ενημέρωση Wi-Fi είναι σε παύση)" } },
{ "Wi-Fi needed to fetch the\nterrain profile for this path.\nConnect in Settings → Wi-Fi.", { 0, "Wifi nodig om het terreinprofiel\nvoor dit pad op te halen.\nVerbind in Instellingen → Wi-Fi.","WLAN nötig, um das Geländeprofil\nfür diesen Pfad zu laden.\nVerbinden in Einstellungen → Wi-Fi.","Wi-Fi requis pour le profil\nde terrain de ce chemin.\nConnectez dans Paramètres → Wi-Fi.","Wi-Fi necesario para el perfil\ndel terreno de esta ruta.\nConéctate en Ajustes → Wi-Fi.","Wi-Fi necessario per il profilo\ndel terreno di questo percorso.\nConnetti in Impostazioni → Wi-Fi.","Нужен Wi-Fi для профиля\nрельефа этого пути.\nПодключите в Настройки → Wi-Fi.","Потрібен Wi-Fi для профілю\nрельєфу цього шляху.\nПідключіть у Налаштування → Wi-Fi.","Нужен е Wi-Fi за профила на\nтерена за този път.\nСвържете в Настройки → Wi-Fi.","Потребан Wi-Fi за профил\nтерена ове путање.\nПовежите у Подешавања → Wi-Fi.","Απαιτείται Wi-Fi για το προφίλ\nεδάφους αυτής της διαδρομής.\nΣυνδεθείτε στις Ρυθμίσεις → Wi-Fi." } },
{ "You can run setup later in Settings → Device", { 0, "Je kunt de installatie later uitvoeren in Instellingen → Apparaat","Sie können die Einrichtung später in Einstellungen → Gerät ausführen","Vous pouvez lancer la configuration plus tard dans Paramètres → Appareil","Puedes ejecutar la configuración luego en Ajustes → Dispositivo","Puoi eseguire la configurazione dopo in Impostazioni → Dispositivo","Настройку можно запустить позже в Настройки → Устройство","Налаштування можна запустити пізніше в Налаштування → Пристрій","Можете да стартирате настройката по-късно в Настройки → Устройство","Подешавање можете покренути касније у Подешавања → Уређај","Μπορείτε να εκτελέσετε τη ρύθμιση αργότερα στις Ρυθμίσεις → Συσκευή" } },
{ "Your location is unknown.\nSet GPS / position in\nSettings → Profile first.", { 0, "Je locatie is onbekend.\nStel GPS / positie eerst in bij\nInstellingen → Profiel.","Ihr Standort ist unbekannt.\nGPS / Position zuerst in\nEinstellungen → Profil festlegen.","Votre position est inconnue.\nDéfinissez GPS / position dans\nParamètres → Profil d'abord.","Tu ubicación es desconocida.\nEstablece GPS / posición en\nAjustes → Perfil primero.","Posizione sconosciuta.\nImposta prima GPS / posizione in\nImpostazioni → Profilo.","Местоположение неизвестно.\nСначала задайте GPS / позицию в\nНастройки → Профиль.","Місцезнаходження невідоме.\nСпочатку задайте GPS / позицію в\nНалаштування → Профіль.","Местоположението е неизвестно.\nПърво задайте GPS / позиция в\nНастройки → Профил.","Локација је непозната.\nПрво поставите GPS / позицију у\nПодешавања → Профил.","Η τοποθεσία σας είναι άγνωστη.\nΟρίστε GPS / θέση στις\nΡυθμίσεις → Προφίλ πρώτα." } },
{ "Can't display image\n(JPEG only, <= 1024 px)", { 0, "Kan afbeelding niet tonen\n(alleen JPEG, <= 1024 px)","Bild kann nicht angezeigt werden\n(nur JPEG, <= 1024 px)","Impossible d'afficher l'image\n(JPEG uniquement, <= 1024 px)","No se puede mostrar la imagen\n(solo JPEG, <= 1024 px)","Impossibile mostrare l'immagine\n(solo JPEG, <= 1024 px)","Не удаётся показать изображение\n(только JPEG, <= 1024 пикс)","Не вдалося показати зображення\n(лише JPEG, <= 1024 пікс)","Изображението не може да се покаже\n(само JPEG, <= 1024 px)","Не могу приказати слику\n(само JPEG, <= 1024 px)","Αδυναμία εμφάνισης εικόνας\n(μόνο JPEG, <= 1024 px)" } },
{ "Couldn't start the analyzer.\nTry again.", { 0, "Kon de analyser niet starten.\nProbeer opnieuw.","Analyzer konnte nicht starten.\nErneut versuchen.","Impossible de démarrer l'analyseur.\nRéessayez.","No se pudo iniciar el analizador.\nInténtalo de nuevo.","Impossibile avviare l'analizzatore.\nRiprova.","Не удалось запустить анализатор.\nПопробуйте снова.","Не вдалося запустити аналізатор.\nСпробуйте знову.","Анализаторът не можа да стартира.\nОпитайте пак.","Анализатор није могао да се покрене.\nПокушајте поново.","Αδυναμία εκκίνησης αναλυτή.\nΔοκιμάστε ξανά." } },
{ "32-hex secret. Anyone with this can read the channel.", { 0, "32-hex geheim. Iedereen hiermee kan het kanaal lezen.","32-Hex-Geheimnis. Wer es hat, kann den Kanal lesen.","Secret 32 hex. Quiconque l'a peut lire le canal.","Secreto de 32 hex. Cualquiera con esto puede leer el canal.","Segreto 32 hex. Chiunque lo abbia può leggere il canale.","Секрет 32 hex. Любой с ним может читать канал.","Секрет 32 hex. Будь-хто з ним може читати канал.","32-hex тайна. Всеки с нея може да чете канала.","32-hex тајна. Свако с њом може читати канал.","Μυστικό 32 hex. Όποιος το έχει διαβάζει το κανάλι." ,"Segredo de 32 hex. Qualquer um com isto lê o canal."} },
{ "Anyone can join. Key is derived from the hashtag (lowercase).", { 0, "Iedereen kan deelnemen. Sleutel komt van de hashtag (kleine letters).","Jeder kann beitreten. Schlüssel aus dem Hashtag (Kleinbuchstaben).","Tout le monde peut rejoindre. Clé dérivée du hashtag (minuscules).","Cualquiera puede unirse. La clave deriva del hashtag (minúsculas).","Chiunque può unirsi. La chiave deriva dall'hashtag (minuscolo).","Любой может присоединиться. Ключ из хэштега (нижний регистр).","Будь-хто може приєднатися. Ключ із хештегу (нижній регістр).","Всеки може да се присъедини. Ключът е от хаштага (малки букви).","Свако може да се придружи. Кључ из хаштага (мала слова).","Όλοι μπορούν να συμμετάσχουν. Κλειδί από το hashtag (πεζά)." ,"Qualquer um pode entrar. A chave vem da hashtag (minúsculas)."} },
{ "Enter the 32-hex secret shared by the channel creator.", { 0, "Voer het 32-hex geheim in dat de kanaalmaker deelt.","Geben Sie das 32-Hex-Geheimnis des Erstellers ein.","Saisissez le secret 32 hex partagé par le créateur du canal.","Introduce el secreto de 32 hex que comparte el creador del canal.","Inserisci il segreto 32 hex condiviso dal creatore del canale.","Введите секрет 32 hex, переданный создателем канала.","Введіть секрет 32 hex від творця каналу.","Въведете 32-hex тайната, споделена от създателя на канала.","Унесите 32-hex тајну коју је поделио творац канала.","Εισαγάγετε το μυστικό 32 hex του δημιουργού του καναλιού." ,"Digite o segredo de 32 hex compartilhado pelo criador do canal."} },
{ "Paste the 64-hex public key and a name. Useful when you want to DM someone before their advert has arrived.", { 0, "Plak de 64-hex publieke sleutel en een naam. Handig om iemand te DM'en voordat hun advert is aangekomen.","Fügen Sie den 64-Hex-Schlüssel und einen Namen ein. Nützlich, um jemandem zu schreiben, bevor sein Advert eintraf.","Collez la clé publique 64 hex et un nom. Utile pour envoyer un MP avant l'arrivée de l'annonce.","Pega la clave pública de 64 hex y un nombre. Útil para enviar un MD antes de que llegue su anuncio.","Incolla la chiave pubblica 64 hex e un nome. Utile per inviare un MD prima dell'advert.","Вставьте открытый ключ 64 hex и имя. Удобно написать до прихода advert.","Вставте відкритий ключ 64 hex та ім'я. Зручно написати до приходу advert.","Поставете 64-hex публичния ключ и име. Полезно за ЛС преди advert-а.","Налепите 64-hex јавни кључ и име. Корисно за ДП пре advert-а.","Επικολλήστε το δημόσιο κλειδί 64 hex και ένα όνομα. Χρήσιμο για ΠΜ πριν το advert." ,"Cole a chave pública de 64 hex e um nome. Útil para mandar DM a alguém antes do advert chegar."} },
{ "Share the 32-char secret so others can join. Leave the secret empty to generate a random one.", { 0, "Deel het 32-teken geheim zodat anderen kunnen deelnemen. Leeg laten voor een willekeurig geheim.","Teilen Sie das 32-Zeichen-Geheimnis, damit andere beitreten. Leer lassen für ein zufälliges.","Partagez le secret de 32 caractères pour que d'autres rejoignent. Laissez vide pour un aléatoire.","Comparte el secreto de 32 caracteres para que otros se unan. Déjalo vacío para uno aleatorio.","Condividi il segreto di 32 caratteri per far unire altri. Lascia vuoto per uno casuale.","Поделитесь секретом из 32 символов, чтобы другие присоединились. Пусто = случайный.","Поділіться секретом із 32 символів, щоб інші приєдналися. Порожньо = випадковий.","Споделете 32-символната тайна, за да се присъединят други. Празно = случайна.","Поделите тајну од 32 знака да се други придруже. Празно = насумична.","Μοιραστείτε το μυστικό 32 χαρακτήρων για να συμμετάσχουν άλλοι. Κενό = τυχαίο." ,"Compartilhe o segredo de 32 caracteres para outros entrarem. Deixe vazio para gerar um aleatório."} },
{ "Region scope (#tag) for this channel.\nBlank = use the default scope.", { 0, "Regiobereik (#tag) voor dit kanaal.\nLeeg = standaardbereik gebruiken.","Regionsbereich (#tag) für diesen Kanal.\nLeer = Standardbereich verwenden.","Portée régionale (#tag) pour ce canal.\nVide = portée par défaut.","Alcance de región (#tag) para este canal.\nVacío = alcance predeterminado.","Ambito regione (#tag) per questo canale.\nVuoto = ambito predefinito.","Регион (#tag) для этого канала.\nПусто = область по умолчанию.","Регіон (#tag) для цього каналу.\nПорожньо = область за умовчанням.","Регионален обхват (#tag) за този канал.\nПразно = обхват по подразбиране.","Опсег региона (#tag) за овај канал.\nПразно = подразумевани опсег.","Εύρος περιοχής (#tag) για αυτό το κανάλι.\nΚενό = προεπιλεγμένο εύρος." ,"Escopo de região (#tag) para este canal.\nVazio = usar o escopo padrão."} },
{ "Map storage error.\nReflash the tiles partition.", { 0, "Kaartopslagfout.\nFlash de tiles-partitie opnieuw.","Kartenspeicherfehler.\nTiles-Partition neu flashen.","Erreur de stockage carte.\nReflashez la partition des tuiles.","Error de almacenamiento del mapa.\nReinstala la partición de teselas.","Errore archiviazione mappa.\nRiflasha la partizione tiles.","Ошибка хранилища карты.\nПерепрошейте раздел tiles.","Помилка сховища карти.\nПерепрошийте розділ tiles.","Грешка в хранилището на картата.\nПрефлашнете дяла tiles.","Грешка складишта мапе.\nПоново флешујте tiles партицију.","Σφάλμα αποθήκευσης χάρτη.\nΞαναφλασάρετε το tiles partition." ,"Erro de armazenamento do mapa.\nRegrave a partição de tiles."} },
{ "Map storage error.\n\nSD card detected but the\ntile cache didn't mount.\nReboot to retry.", { 0, "Kaartopslagfout.\n\nSD-kaart gedetecteerd maar de\ntegelcache kon niet koppelen.\nHerstart om opnieuw te proberen.","Kartenspeicherfehler.\n\nSD-Karte erkannt, aber der\nKachel-Cache wurde nicht eingebunden.\nNeustart zum Wiederholen.","Erreur de stockage carte.\n\nCarte SD détectée mais le\ncache de tuiles n'a pas monté.\nRedémarrez pour réessayer.","Error de almacenamiento del mapa.\n\nTarjeta SD detectada pero la\ncaché de teselas no se montó.\nReinicia para reintentar.","Errore archiviazione mappa.\n\nScheda SD rilevata ma la\ncache riquadri non si è montata.\nRiavvia per riprovare.","Ошибка хранилища карты.\n\nSD-карта найдена, но кэш\nтайлов не смонтирован.\nПерезагрузите для повтора.","Помилка сховища карти.\n\nSD-картку знайдено, але кеш\nплиток не змонтовано.\nПерезавантажте для повтору.","Грешка в хранилището.\n\nSD картата е открита, но кешът\nна плочките не се монтира.\nРестартирайте за нов опит.","Грешка складишта мапе.\n\nSD картица откривена, али кеш\nплочица није монтиран.\nРестартујте за поновни покушај.","Σφάλμα αποθήκευσης χάρτη.\n\nΕντοπίστηκε κάρτα SD αλλά η\nμνήμη πλακιδίων δεν προσαρτήθηκε.\nΕπανεκκίνηση για νέα προσπάθεια." ,"Erro de armazenamento do mapa.\n\nCartão SD detectado mas o\ncache de tiles não montou.\nReinicie para tentar de novo."} },
{ "Map — set your location\nin Settings → Profile to\nshow the map here.", { 0, "Kaart — stel je locatie in\nbij Instellingen → Profiel om\nde kaart hier te tonen.","Karte — Standort in\nEinstellungen → Profil festlegen,\num die Karte hier zu zeigen.","Carte — définissez votre position\ndans Paramètres → Profil pour\nafficher la carte ici.","Mapa — establece tu ubicación\nen Ajustes → Perfil para\nmostrar el mapa aquí.","Mappa — imposta la posizione\nin Impostazioni → Profilo per\nmostrare la mappa qui.","Карта — задайте местоположение\nв Настройки → Профиль, чтобы\nпоказать карту здесь.","Карта — задайте місцезнаходження\nу Налаштування → Профіль, щоб\nпоказати карту тут.","Карта — задайте местоположението\nв Настройки → Профил, за да\nсе покаже картата тук.","Мапа — поставите локацију\nу Подешавања → Профил да\nприкажете мапу овде.","Χάρτης — ορίστε την τοποθεσία\nστις Ρυθμίσεις → Προφίλ για\nνα εμφανιστεί ο χάρτης εδώ." ,"Mapa — defina sua localização\nem Ajustes → Perfil para\nmostrar o mapa aqui."} },
{ "No .json backups found.\nExport one first, or copy a\nmeshcore-backup.json to the\nSD card or internal flash.", { 0, "Geen .json back-ups gevonden.\nExporteer er eerst een, of kopieer\neen meshcore-backup.json naar de\nSD-kaart of interne flash.","Keine .json-Backups gefunden.\nZuerst eins exportieren oder\nmeshcore-backup.json auf die\nSD-Karte oder den Flash kopieren.","Aucune sauvegarde .json trouvée.\nExportez-en une, ou copiez un\nmeshcore-backup.json sur la\ncarte SD ou le flash interne.","No se hallaron copias .json.\nExporta una, o copia un\nmeshcore-backup.json a la\ntarjeta SD o flash interna.","Nessun backup .json trovato.\nEsportane uno, o copia un\nmeshcore-backup.json sulla\nscheda SD o flash interna.","Резервные копии .json не найдены.\nЭкспортируйте одну или скопируйте\nmeshcore-backup.json на\nSD-карту или внутр. флеш.","Резервних копій .json не знайдено.\nЕкспортуйте одну або скопіюйте\nmeshcore-backup.json на\nSD-картку чи внутр. флеш.","Няма намерени .json архиви.\nЕкспортирайте един или копирайте\nmeshcore-backup.json на\nSD картата или вътр. флаш.","Нема .json резервних копија.\nИзвезите једну или копирајте\nmeshcore-backup.json на\nSD картицу или интерни флеш.","Δεν βρέθηκαν αντίγραφα .json.\nΕξαγάγετε ένα ή αντιγράψτε\nmeshcore-backup.json στην\nκάρτα SD ή στο εσωτ. flash." ,"Nenhum backup .json encontrado.\nExporte um primeiro, ou copie um\nmeshcore-backup.json para o\ncartão SD ou a flash interna."} },
{ "No JPEGs found in /lock/.\nAdd images via the file\nmanager or an SD card.", { 0, "Geen JPEG's gevonden in /lock/.\nVoeg afbeeldingen toe via de\nbestandsbeheerder of een SD-kaart.","Keine JPEGs in /lock/ gefunden.\nBilder über den Dateimanager\noder eine SD-Karte hinzufügen.","Aucun JPEG dans /lock/.\nAjoutez des images via le\ngestionnaire ou une carte SD.","No hay JPEG en /lock/.\nAñade imágenes con el gestor\nde archivos o una tarjeta SD.","Nessun JPEG in /lock/.\nAggiungi immagini dal gestore\nfile o da una scheda SD.","В /lock/ нет JPEG.\nДобавьте изображения через\nфайловый менеджер или SD-карту.","У /lock/ немає JPEG.\nДодайте зображення через\nфайловий менеджер або SD-картку.","Няма JPEG в /lock/.\nДобавете изображения чрез\nфайловия мениджър или SD карта.","Нема JPEG у /lock/.\nДодајте слике преко менаџера\nдатотека или SD картице.","Δεν βρέθηκαν JPEG στο /lock/.\nΠροσθέστε εικόνες μέσω του\nδιαχειριστή αρχείων ή κάρτας SD." ,"Nenhum JPEG em /lock/.\nAdicione imagens pelo gerenciador\nde arquivos ou um cartão SD."} },
{ "No blocked users.\n\nLong-press a message and tap\nBlock to add one.", { 0, "Geen geblokkeerde gebruikers.\n\nHoud een bericht ingedrukt en tik\nop Blokkeren om er een toe te voegen.","Keine blockierten Benutzer.\n\nNachricht lange drücken und auf\nBlockieren tippen, um einen hinzuzufügen.","Aucun utilisateur bloqué.\n\nAppui long sur un message puis\ntouchez Bloquer pour en ajouter un.","Sin usuarios bloqueados.\n\nMantén pulsado un mensaje y toca\nBloquear para añadir uno.","Nessun utente bloccato.\n\nTieni premuto un messaggio e tocca\nBlocca per aggiungerne uno.","Нет заблокированных.\n\nДолго нажмите сообщение и нажмите\nБлокировать, чтобы добавить.","Немає заблокованих.\n\nЗатисніть повідомлення й натисніть\nБлокувати, щоб додати.","Няма блокирани потребители.\n\nЗадръжте съобщение и докоснете\nБлокирай, за да добавите.","Нема блокираних корисника.\n\nДуго притисните поруку и додирните\nБлокирај да додате једног.","Κανένας αποκλεισμένος χρήστης.\n\nΠατήστε παρατεταμένα ένα μήνυμα και\nΑποκλεισμός για προσθήκη." ,"Nenhum usuário bloqueado.\n\nSegure uma mensagem e toque\nBloquear para adicionar."} },
{ "No contacts have shared\na GPS location yet.", { 0, "Nog geen contacten hebben\neen GPS-locatie gedeeld.","Noch keine Kontakte haben\neinen GPS-Standort geteilt.","Aucun contact n'a encore\npartagé de position GPS.","Ningún contacto ha compartido\nuna ubicación GPS aún.","Nessun contatto ha ancora\ncondiviso una posizione GPS.","Контакты ещё не делились\nGPS-местоположением.","Контакти ще не ділилися\nGPS-місцезнаходженням.","Все още няма контакти,\nсподелили GPS местоположение.","Још ниједан контакт није\nподелио GPS локацију.","Καμία επαφή δεν έχει\nμοιραστεί θέση GPS ακόμη." ,"Nenhum contato compartilhou\numa localização GPS ainda."} },
{ "No map storage.\n\nInsert an SD card to cache\nWi-Fi tiles (or reflash to\nrestore the tiles partition).", { 0, "Geen kaartopslag.\n\nPlaats een SD-kaart om wifi-tegels\nte cachen (of flash opnieuw om\nde tiles-partitie te herstellen).","Kein Kartenspeicher.\n\nSD-Karte einlegen, um WLAN-Kacheln\nzu cachen (oder neu flashen, um die\nTiles-Partition wiederherzustellen).","Pas de stockage carte.\n\nInsérez une carte SD pour les\ntuiles Wi-Fi (ou reflashez pour\nrestaurer la partition des tuiles).","Sin almacenamiento de mapa.\n\nInserta una tarjeta SD para teselas\nWi-Fi (o reinstala para restaurar\nla partición de teselas).","Nessuna archiviazione mappa.\n\nInserisci una scheda SD per i\nriquadri Wi-Fi (o riflasha per\nripristinare la partizione tiles).","Нет хранилища карты.\n\nВставьте SD-карту для кэша\nWi-Fi тайлов (или перепрошейте\nдля раздела tiles).","Немає сховища карти.\n\nВставте SD-картку для кешу\nWi-Fi плиток (або перепрошийте\nдля розділу tiles).","Няма хранилище за карта.\n\nПоставете SD карта за кеш на\nWi-Fi плочки (или префлашнете за\nдяла tiles).","Нема складишта мапе.\n\nУбаците SD картицу за кеш\nWi-Fi плочица (или флешујте за\ntiles партицију).","Χωρίς αποθήκευση χάρτη.\n\nΕισαγάγετε κάρτα SD για πλακίδια\nWi-Fi (ή ξαναφλασάρετε για\nτο tiles partition)." ,"Sem armazenamento de mapa.\n\nInsira um cartão SD para\nguardar tiles do Wi-Fi (ou regrave\npara restaurar a partição de tiles)."} },
{ "PNG isn't supported here\nUse a JPEG", { 0, "PNG wordt hier niet ondersteund\nGebruik een JPEG","PNG wird hier nicht unterstützt\nVerwenden Sie ein JPEG","PNG non pris en charge ici\nUtilisez un JPEG","PNG no se admite aquí\nUsa un JPEG","PNG non supportato qui\nUsa un JPEG","PNG здесь не поддерживается\nИспользуйте JPEG","PNG тут не підтримується\nВикористайте JPEG","PNG не се поддържа тук\nИзползвайте JPEG","PNG овде није подржан\nКористите JPEG","Το PNG δεν υποστηρίζεται εδώ\nΧρησιμοποιήστε JPEG" ,"PNG não é suportado aqui\nUse um JPEG"} },
{ "Still analyzing the previous\npath… try again in a moment.", { 0, "Vorige pad wordt nog\ngeanalyseerd… probeer zo opnieuw.","Vorheriger Pfad wird noch\nanalysiert… gleich erneut versuchen.","Analyse du chemin précédent\nen cours… réessayez bientôt.","Aún analizando la ruta\nanterior… reinténtalo en un momento.","Analisi del percorso precedente\nin corso… riprova tra poco.","Ещё анализируется предыдущий\nпуть… повторите чуть позже.","Ще аналізується попередній\nшлях… спробуйте за мить.","Все още се анализира предишният\nпът… опитайте пак след малко.","Још се анализира претходна\nпутања… покушајте ускоро.","Ακόμη αναλύεται η προηγούμενη\nδιαδρομή… δοκιμάστε σε λίγο." ,"Ainda analisando a rota\nanterior… tente de novo em instantes."} },
{ "This contact hasn't shared\na GPS position.", { 0, "Dit contact heeft geen\nGPS-positie gedeeld.","Dieser Kontakt hat keine\nGPS-Position geteilt.","Ce contact n'a pas partagé\nde position GPS.","Este contacto no ha compartido\nuna posición GPS.","Questo contatto non ha condiviso\nuna posizione GPS.","Этот контакт не делился\nGPS-позицией.","Цей контакт не ділився\nGPS-позицією.","Този контакт не е споделил\nGPS позиция.","Овај контакт није поделио\nGPS позицију.","Αυτή η επαφή δεν μοιράστηκε\nθέση GPS." ,"Este contato não compartilhou\numa posição GPS."} },
{ "Tip: you can scan for networks in Settings → Network after setup.", { 0, "Tip: je kunt netwerken scannen in Instellingen → Netwerk na de installatie.","Tipp: Nach der Einrichtung in Einstellungen → Netzwerk scannen.","Astuce : recherchez des réseaux dans Paramètres → Réseau après la configuration.","Consejo: puedes buscar redes en Ajustes → Red tras la configuración.","Suggerimento: cerca reti in Impostazioni → Rete dopo la configurazione.","Совет: сканировать сети можно в Настройки → Сеть после настройки.","Порада: шукати мережі можна в Налаштування → Мережа після налаштування.","Съвет: можете да сканирате мрежи в Настройки → Мрежа след настройката.","Савет: мреже можете скенирати у Подешавања → Мрежа након подешавања.","Συμβουλή: σαρώστε δίκτυα στις Ρυθμίσεις → Δίκτυο μετά τη ρύθμιση." ,"Dica: você pode buscar redes em Ajustes → Rede após a configuração."} },
{ "Type your network name — or switch to Wi-Fi in Settings to scan", { 0, "Typ je netwerknaam — of schakel naar wifi in Instellingen om te scannen","Netzwerknamen eingeben — oder in Einstellungen auf WLAN wechseln zum Scannen","Saisissez le nom du réseau — ou passez au Wi-Fi dans Paramètres pour scanner","Escribe el nombre de la red — o cambia a Wi-Fi en Ajustes para buscar","Digita il nome della rete — o passa al Wi-Fi in Impostazioni per scansionare","Введите имя сети — или включите Wi-Fi в Настройках для поиска","Введіть назву мережі — або увімкніть Wi-Fi у Налаштуваннях для пошуку","Въведете името на мрежата — или включете Wi-Fi в Настройки за сканиране","Унесите назив мреже — или пређите на Wi-Fi у Подешавањима за скенирање","Πληκτρολογήστε το όνομα δικτύου — ή στρέψτε σε Wi-Fi στις Ρυθμίσεις για σάρωση" ,"Digite o nome da sua rede — ou mude para Wi-Fi em Ajustes para buscar"} },
{ "Update manually at flasher.wadamesh.com\n(Wi-Fi update is paused for now)", { 0, "Werk handmatig bij op flasher.wadamesh.com\n(wifi-update is voorlopig gepauzeerd)","Manuell aktualisieren auf flasher.wadamesh.com\n(WLAN-Update ist vorerst pausiert)","Mettez à jour sur flasher.wadamesh.com\n(la MAJ Wi-Fi est en pause pour l'instant)","Actualiza en flasher.wadamesh.com\n(la actualización Wi-Fi está en pausa)","Aggiorna su flasher.wadamesh.com\n(l'aggiornamento Wi-Fi è in pausa)","Обновите вручную на flasher.wadamesh.com\n(обновление по Wi-Fi пока приостановлено)","Оновіть вручну на flasher.wadamesh.com\n(оновлення через Wi-Fi поки призупинено)","Обновете ръчно на flasher.wadamesh.com\n(Wi-Fi обновяването е спряно засега)","Ажурирајте ручно на flasher.wadamesh.com\n(Wi-Fi ажурирање је за сада паузирано)","Ενημερώστε χειροκίνητα στο flasher.wadamesh.com\n(η ενημέρωση Wi-Fi είναι σε παύση)" ,"Atualize manualmente em flasher.wadamesh.com\n(a atualização por Wi-Fi está pausada por ora)"} },
{ "Wi-Fi needed to fetch the\nterrain profile for this path.\nConnect in Settings → Wi-Fi.", { 0, "Wifi nodig om het terreinprofiel\nvoor dit pad op te halen.\nVerbind in Instellingen → Wi-Fi.","WLAN nötig, um das Geländeprofil\nfür diesen Pfad zu laden.\nVerbinden in Einstellungen → Wi-Fi.","Wi-Fi requis pour le profil\nde terrain de ce chemin.\nConnectez dans Paramètres → Wi-Fi.","Wi-Fi necesario para el perfil\ndel terreno de esta ruta.\nConéctate en Ajustes → Wi-Fi.","Wi-Fi necessario per il profilo\ndel terreno di questo percorso.\nConnetti in Impostazioni → Wi-Fi.","Нужен Wi-Fi для профиля\nрельефа этого пути.\nПодключите в Настройки → Wi-Fi.","Потрібен Wi-Fi для профілю\nрельєфу цього шляху.\nПідключіть у Налаштування → Wi-Fi.","Нужен е Wi-Fi за профила на\nтерена за този път.\nСвържете в Настройки → Wi-Fi.","Потребан Wi-Fi за профил\nтерена ове путање.\nПовежите у Подешавања → Wi-Fi.","Απαιτείται Wi-Fi για το προφίλ\nεδάφους αυτής της διαδρομής.\nΣυνδεθείτε στις Ρυθμίσεις → Wi-Fi." ,"É preciso Wi-Fi para obter o perfil\ndo terreno desta rota.\nConecte em Ajustes → Wi-Fi."} },
{ "You can run setup later in Settings → Device", { 0, "Je kunt de installatie later uitvoeren in Instellingen → Apparaat","Sie können die Einrichtung später in Einstellungen → Gerät ausführen","Vous pouvez lancer la configuration plus tard dans Paramètres → Appareil","Puedes ejecutar la configuración luego en Ajustes → Dispositivo","Puoi eseguire la configurazione dopo in Impostazioni → Dispositivo","Настройку можно запустить позже в Настройки → Устройство","Налаштування можна запустити пізніше в Налаштування → Пристрій","Можете да стартирате настройката по-късно в Настройки → Устройство","Подешавање можете покренути касније у Подешавања → Уређај","Μπορείτε να εκτελέσετε τη ρύθμιση αργότερα στις Ρυθμίσεις → Συσκευή" ,"Você pode rodar a configuração depois em Ajustes → Aparelho"} },
{ "Your location is unknown.\nSet GPS / position in\nSettings → Profile first.", { 0, "Je locatie is onbekend.\nStel GPS / positie eerst in bij\nInstellingen → Profiel.","Ihr Standort ist unbekannt.\nGPS / Position zuerst in\nEinstellungen → Profil festlegen.","Votre position est inconnue.\nDéfinissez GPS / position dans\nParamètres → Profil d'abord.","Tu ubicación es desconocida.\nEstablece GPS / posición en\nAjustes → Perfil primero.","Posizione sconosciuta.\nImposta prima GPS / posizione in\nImpostazioni → Profilo.","Местоположение неизвестно.\nСначала задайте GPS / позицию в\nНастройки → Профиль.","Місцезнаходження невідоме.\nСпочатку задайте GPS / позицію в\nНалаштування → Профіль.","Местоположението е неизвестно.\nПърво задайте GPS / позиция в\nНастройки → Профил.","Локација је непозната.\nПрво поставите GPS / позицију у\nПодешавања → Профил.","Η τοποθεσία σας είναι άγνωστη.\nΟρίστε GPS / θέση στις\nΡυθμίσεις → Προφίλ πρώτα." ,"Sua localização é desconhecida.\nDefina GPS / posição em\nAjustes → Perfil primeiro."} },
{ "Can't display image\n(JPEG only, <= 1024 px)", { 0, "Kan afbeelding niet tonen\n(alleen JPEG, <= 1024 px)","Bild kann nicht angezeigt werden\n(nur JPEG, <= 1024 px)","Impossible d'afficher l'image\n(JPEG uniquement, <= 1024 px)","No se puede mostrar la imagen\n(solo JPEG, <= 1024 px)","Impossibile mostrare l'immagine\n(solo JPEG, <= 1024 px)","Не удаётся показать изображение\n(только JPEG, <= 1024 пикс)","Не вдалося показати зображення\n(лише JPEG, <= 1024 пікс)","Изображението не може да се покаже\n(само JPEG, <= 1024 px)","Не могу приказати слику\n(само JPEG, <= 1024 px)","Αδυναμία εμφάνισης εικόνας\n(μόνο JPEG, <= 1024 px)" ,"Não dá para exibir a imagem\n(só JPEG, <= 1024 px)"} },
{ "Couldn't start the analyzer.\nTry again.", { 0, "Kon de analyser niet starten.\nProbeer opnieuw.","Analyzer konnte nicht starten.\nErneut versuchen.","Impossible de démarrer l'analyseur.\nRéessayez.","No se pudo iniciar el analizador.\nInténtalo de nuevo.","Impossibile avviare l'analizzatore.\nRiprova.","Не удалось запустить анализатор.\nПопробуйте снова.","Не вдалося запустити аналізатор.\nСпробуйте знову.","Анализаторът не можа да стартира.\nОпитайте пак.","Анализатор није могао да се покрене.\nПокушајте поново.","Αδυναμία εκκίνησης αναλυτή.\nΔοκιμάστε ξανά." ,"Não foi possível iniciar o analisador.\nTente de novo."} },
// ---- Sweep pass 6: section headers, control labels, setup wizard, control center ----
{ "Auto-add contacts", { 0, "Contacten automatisch toevoegen","Kontakte automatisch hinzufügen","Ajout auto de contacts","Auto-añadir contactos","Aggiunta automatica contatti","Автодобавление контактов","Автододавання контактів","Автоматично добавяне на контакти","Аутоматско додавање контаката","Αυτόματη προσθήκη επαφών" } },
{ "Experimental", { 0, "Experimenteel","Experimentell","Expérimental","Experimental","Sperimentale","Экспериментальное","Експериментальне","Експериментално","Експериментално","Πειραματικά" } },
{ "Discovered", { 0, "Ontdekt","Entdeckt","Découverts","Descubiertos","Scoperti","Обнаружено","Виявлено","Открити","Откривени","Ανακαλύφθηκαν" } },
{ "Create private channel", { 0, "Privékanaal aanmaken","Privaten Kanal erstellen","Créer un canal privé","Crear canal privado","Crea canale privato","Создать приватный канал","Створити приватний канал","Създай частен канал","Направи приватни канал","Δημιουργία ιδιωτικού καναλιού" } },
{ "Join hashtag channel", { 0, "Deelnemen aan hashtagkanaal","Hashtag-Kanal beitreten","Rejoindre un canal hashtag","Unirse a canal de hashtag","Unisciti a canale hashtag","Войти в канал хэштега","Приєднатися до каналу хештегу","Присъедини се към хаштаг канал","Придружи се хаштаг каналу","Συμμετοχή σε κανάλι hashtag" } },
{ "Join private channel", { 0, "Deelnemen aan privékanaal","Privaten Kanal beitreten","Rejoindre un canal privé","Unirse a canal privado","Unisciti a canale privato","Войти в приватный канал","Приєднатися до приватного каналу","Присъедини се към частен канал","Придружи се приватном каналу","Συμμετοχή σε ιδιωτικό κανάλι" } },
{ "Send advert", { 0, "Advert verzenden","Advert senden","Envoyer une annonce","Enviar anuncio","Invia advert","Отправить advert","Надіслати advert","Изпрати advert","Пошаљи advert","Αποστολή advert" } },
{ "Share channel", { 0, "Kanaal delen","Kanal teilen","Partager le canal","Compartir canal","Condividi canale","Поделиться каналом","Поділитися каналом","Сподели канала","Подели канал","Κοινή χρήση καναλιού" } },
{ "Advert", { 0, "Advert","Advert","Annonce","Anuncio","Advert","Advert","Advert","Advert","Advert","Advert" } },
{ "Advert location", { 0, "Advertlocatie","Advert-Standort","Position de l'annonce","Ubicación del anuncio","Posizione advert","Местоположение advert","Місцезнаходження advert","Местоположение на advert","Локација advert-а","Τοποθεσία advert" } },
{ "Latitude", { 0, "Breedtegraad","Breitengrad","Latitude","Latitud","Latitudine","Широта","Широта","Географска ширина","Географска ширина","Γεωγραφικό πλάτος" } },
{ "Longitude", { 0, "Lengtegraad","Längengrad","Longitude","Longitud","Longitudine","Долгота","Довгота","Географска дължина","Географска дужина","Γεωγραφικό μήκος" } },
{ "Share location in advert", { 0, "Locatie delen in advert","Standort im Advert teilen","Partager la position dans l'annonce","Compartir ubicación en anuncio","Condividi posizione in advert","Делиться местоположением в advert","Ділитися місцезнаходженням в advert","Споделяй местоположението в advert","Дели локацију у advert-у","Κοινή χρήση θέσης στο advert" } },
{ "Node name", { 0, "Nodenaam","Node-Name","Nom du nœud","Nombre del nodo","Nome nodo","Имя узла","Назва вузла","Име на възела","Назив чвора","Όνομα κόμβου" } },
{ "Display name", { 0, "Weergavenaam","Anzeigename","Nom affiché","Nombre visible","Nome visualizzato","Отображаемое имя","Відображуване ім'я","Показвано име","Приказно име","Εμφανιζόμενο όνομα" } },
{ "Identity (public key)", { 0, "Identiteit (publieke sleutel)","Identität (öffentl. Schlüssel)","Identité (clé publique)","Identidad (clave pública)","Identità (chiave pubblica)","Идентификатор (открытый ключ)","Ідентичність (відкритий ключ)","Идентичност (публичен ключ)","Идентитет (јавни кључ)","Ταυτότητα (δημόσιο κλειδί)" } },
{ "Community preset (meshcomod web)", { 0, "Community-preset (meshcomod web)","Community-Vorgabe (meshcomod web)","Préréglage communauté (meshcomod web)","Preajuste comunidad (meshcomod web)","Preset community (meshcomod web)","Пресет сообщества (meshcomod web)","Пресет спільноти (meshcomod web)","Пресет на общността (meshcomod web)","Унапред (meshcomod web)","Προεπιλογή κοινότητας (meshcomod web)" } },
{ "Frequency / bandwidth", { 0, "Frequentie / bandbreedte","Frequenz / Bandbreite","Fréquence / bande passante","Frecuencia / ancho de banda","Frequenza / larghezza di banda","Частота / полоса","Частота / смуга","Честота / лента","Фреквенција / опсег","Συχνότητα / εύρος ζώνης" } },
{ "SF / CR / TX / AF", { 0, "SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF" } },
{ "Multi-byte routing (path hash)", { 0, "Multi-byte routing (pad-hash)","Multibyte-Routing (Pfad-Hash)","Routage multi-octet (hash de chemin)","Enrutamiento multibyte (hash de ruta)","Routing multi-byte (hash percorso)","Многобайтовая маршрутизация (хеш пути)","Багатобайтова маршрутизація (хеш шляху)","Многобайтово маршрутиране (хеш на пътя)","Вишебајтно рутирање (хеш путање)","Δρομολόγηση πολλών byte (hash διαδρομής)" } },
{ "Region scope (#tag, blank = none)", { 0, "Regiobereik (#tag, leeg = geen)","Regionsbereich (#tag, leer = keiner)","Portée régionale (#tag, vide = aucune)","Alcance de región (#tag, vacío = ninguno)","Ambito regione (#tag, vuoto = nessuno)","Регион (#tag, пусто = нет)","Регіон (#tag, порожньо = немає)","Регионален обхват (#tag, празно = няма)","Опсег региона (#tag, празно = ниједан)","Εύρος περιοχής (#tag, κενό = κανένα)" } },
{ "Auto chat", { 0, "Auto chat","Auto-Chat","Chat auto","Chat auto","Chat auto","Авто чат","Авто чат","Авто чат","Ауто ћаскање","Αυτόματη συνομιλία" } },
{ "Auto repeater", { 0, "Auto repeater","Auto-Repeater","Répéteur auto","Repetidor auto","Ripetitore auto","Авто ретранслятор","Авто ретранслятор","Авто ретранслатор","Ауто репетитор","Αυτόματος αναμεταδότης" } },
{ "Auto room", { 0, "Auto room","Auto-Raum","Salon auto","Sala auto","Stanza auto","Авто комната","Авто кімната","Авто стая","Ауто соба","Αυτόματο δωμάτιο" } },
{ "Auto sensor", { 0, "Auto sensor","Auto-Sensor","Capteur auto","Sensor auto","Sensore auto","Авто датчик","Авто датчик","Авто сензор","Ауто сензор","Αυτόματος αισθητήρας" } },
{ "Overwrite oldest", { 0, "Oudste overschrijven","Älteste überschreiben","Écraser le plus ancien","Sobrescribir el más antiguo","Sovrascrivi il più vecchio","Перезаписать старейший","Перезаписати найстаріший","Презапиши най-стария","Препиши најстарији","Αντικατάσταση παλαιότερου" } },
{ "Multi ACKs", { 0, "Multi-ACK's","Multi-ACKs","Multi-ACK","Multi-ACK","Multi-ACK","Множественные ACK","Множинні ACK","Множествени ACK","Више ACK","Πολλαπλά ACK" } },
{ "Client repeat", { 0, "Client herhaling","Client-Wiederholung","Répétition client","Repetición de cliente","Ripetizione client","Повтор клиента","Повтор клієнта","Повтаряне от клиент","Понављање клијента","Επανάληψη πελάτη" } },
{ "RX boosted gain", { 0, "RX versterkte gain","RX verstärkte Verstärkung","Gain RX boosté","Ganancia RX aumentada","Guadagno RX potenziato","Усиленное RX","Підсилений RX","Усилено RX","Појачан RX","Ενισχυμένο κέρδος RX" } },
{ "Duty meter", { 0, "Duty-meter","Duty-Anzeige","Indicateur de cycle","Medidor de ciclo","Misuratore duty","Индикатор нагрузки","Індикатор завантаження","Измервател на цикъла","Мерач циклуса","Μετρητής κύκλου" } },
{ "Network name", { 0, "Netwerknaam","Netzwerkname","Nom du réseau","Nombre de red","Nome rete","Имя сети","Назва мережі","Име на мрежата","Назив мреже","Όνομα δικτύου" } },
{ "Name fragment", { 0, "Naamfragment","Namensfragment","Fragment de nom","Fragmento de nombre","Frammento nome","Часть имени","Частина імені","Част от име","Део имена","Τμήμα ονόματος" } },
{ "Theme", { 0, "Thema","Thema","Thème","Tema","Tema","Тема","Тема","Тема","Тема","Θέμα" } },
{ "Lock", { 0, "Vergrendelen","Sperren","Verrouiller","Bloquear","Blocca","Блокировка","Блокування","Заключи","Закључај","Κλείδωμα" } },
{ "Switch", { 0, "Wisselen","Wechseln","Basculer","Cambiar","Cambia","Переключить","Перемкнути","Превключи","Пребаци","Εναλλαγή" } },
{ "Wi-Fi on", { 0, "Wi-Fi aan","WLAN ein","Wi-Fi activé","Wi-Fi activado","Wi-Fi acceso","Wi-Fi вкл","Wi-Fi увімк","Wi-Fi вкл","Wi-Fi укљ","Wi-Fi ναι" } },
{ "Wi-Fi off", { 0, "Wi-Fi uit","WLAN aus","Wi-Fi désactivé","Wi-Fi apagado","Wi-Fi spento","Wi-Fi выкл","Wi-Fi вимк","Wi-Fi изкл","Wi-Fi искљ","Wi-Fi όχι" } },
{ "BLE on", { 0, "BLE aan","BLE ein","BLE activé","BLE activado","BLE acceso","BLE вкл","BLE увімк","BLE вкл","BLE укљ","BLE ναι" } },
{ "BLE off", { 0, "BLE uit","BLE aus","BLE désactivé","BLE apagado","BLE spento","BLE выкл","BLE вимк","BLE изкл","BLE искљ","BLE όχι" } },
{ "Bluetooth on", { 0, "Bluetooth aan","Bluetooth ein","Bluetooth activé","Bluetooth activado","Bluetooth acceso","Bluetooth вкл","Bluetooth увімк","Bluetooth вкл","Bluetooth укљ","Bluetooth ναι" } },
{ "Bluetooth off", { 0, "Bluetooth uit","Bluetooth aus","Bluetooth désactivé","Bluetooth apagado","Bluetooth spento","Bluetooth выкл","Bluetooth вимк","Bluetooth изкл","Bluetooth искљ","Bluetooth όχι" } },
{ "Bluetooth already on", { 0, "Bluetooth al aan","Bluetooth bereits ein","Bluetooth déjà activé","Bluetooth ya activado","Bluetooth già acceso","Bluetooth уже вкл","Bluetooth вже увімк","Bluetooth вече е вкл","Bluetooth већ укључен","Το Bluetooth είναι ήδη ενεργό" } },
{ "Bluetooth already off", { 0, "Bluetooth al uit","Bluetooth bereits aus","Bluetooth déjà désactivé","Bluetooth ya apagado","Bluetooth già spento","Bluetooth уже выкл","Bluetooth вже вимк","Bluetooth вече е изкл","Bluetooth већ искључен","Το Bluetooth είναι ήδη ανενεργό" } },
{ "GPS on", { 0, "GPS aan","GPS ein","GPS activé","GPS activado","GPS acceso","GPS вкл","GPS увімк","GPS вкл","GPS укљ","GPS ναι" } },
{ "GPS off", { 0, "GPS uit","GPS aus","GPS désactivé","GPS apagado","GPS spento","GPS выкл","GPS вимк","GPS изкл","GPS искљ","GPS όχι" } },
{ "Sound on", { 0, "Geluid aan","Ton ein","Son activé","Sonido activado","Suono acceso","Звук вкл","Звук увімк","Звук вкл","Звук укљ","Ήχος ναι" } },
{ "Sound off", { 0, "Geluid uit","Ton aus","Son désactivé","Sonido apagado","Suono spento","Звук выкл","Звук вимк","Звук изкл","Звук искљ","Ήχος όχι" } },
{ "TCP on", { 0, "TCP aan","TCP ein","TCP activé","TCP activado","TCP acceso","TCP вкл","TCP увімк","TCP вкл","TCP укљ","TCP ναι" } },
{ "TCP off", { 0, "TCP uit","TCP aus","TCP désactivé","TCP apagado","TCP spento","TCP выкл","TCP вимк","TCP изкл","TCP искљ","TCP όχι" } },
{ "Welcome to WADAMESH", { 0, "Welkom bij WADAMESH","Willkommen bei WADAMESH","Bienvenue sur WADAMESH","Bienvenido a WADAMESH","Benvenuto in WADAMESH","Добро пожаловать в WADAMESH","Ласкаво просимо до WADAMESH","Добре дошли в WADAMESH","Добро дошли у WADAMESH","Καλώς ήρθατε στο WADAMESH" } },
{ "Get Started", { 0, "Aan de slag","Loslegen","Commencer","Empezar","Inizia","Начать","Почати","Започни","Започни","Ξεκινήστε" } },
{ "Choose your name", { 0, "Kies je naam","Wählen Sie Ihren Namen","Choisissez votre nom","Elige tu nombre","Scegli il tuo nome","Выберите имя","Виберіть ім'я","Изберете името си","Изаберите своје име","Επιλέξτε το όνομά σας" } },
{ "Your name", { 0, "Je naam","Ihr Name","Votre nom","Tu nombre","Il tuo nome","Ваше имя","Ваше ім'я","Вашето име","Ваше име","Το όνομά σας" } },
{ "Choose your region", { 0, "Kies je regio","Wählen Sie Ihre Region","Choisissez votre région","Elige tu región","Scegli la tua regione","Выберите регион","Виберіть регіон","Изберете региона си","Изаберите регион","Επιλέξτε την περιοχή σας" } },
{ "Next", { 0, "Volgende","Weiter","Suivant","Siguiente","Avanti","Далее","Далі","Напред","Даље","Επόμενο" } },
{ "Skip", { 0, "Overslaan","Überspringen","Passer","Omitir","Salta","Пропустить","Пропустити","Пропусни","Прескочи","Παράλειψη" } },
{ "Finish", { 0, "Voltooien","Fertig","Terminer","Finalizar","Fine","Готово","Готово","Готово","Заврши","Τέλος" } },
{ "Connect to Wi-Fi", { 0, "Verbinden met wifi","Mit WLAN verbinden","Se connecter au Wi-Fi","Conectar a Wi-Fi","Connetti al Wi-Fi","Подключиться к Wi-Fi","Підключитися до Wi-Fi","Свържи се с Wi-Fi","Повежи се на Wi-Fi","Σύνδεση σε Wi-Fi" } },
{ "Password (empty = skip Wi-Fi)", { 0, "Wachtwoord (leeg = wifi overslaan)","Passwort (leer = WLAN überspringen)","Mot de passe (vide = ignorer le Wi-Fi)","Contraseña (vacío = omitir Wi-Fi)","Password (vuoto = salta Wi-Fi)","Пароль (пусто = пропустить Wi-Fi)","Пароль (порожньо = пропустити Wi-Fi)","Парола (празно = пропусни Wi-Fi)","Лозинка (празно = прескочи Wi-Fi)","Κωδικός (κενό = παράλειψη Wi-Fi)" } },
{ "Auto-add contacts", { 0, "Contacten automatisch toevoegen","Kontakte automatisch hinzufügen","Ajout auto de contacts","Auto-añadir contactos","Aggiunta automatica contatti","Автодобавление контактов","Автододавання контактів","Автоматично добавяне на контакти","Аутоматско додавање контаката","Αυτόματη προσθήκη επαφών" ,"Auto-adicionar contatos"} },
{ "Experimental", { 0, "Experimenteel","Experimentell","Expérimental","Experimental","Sperimentale","Экспериментальное","Експериментальне","Експериментално","Експериментално","Πειραματικά" ,"Experimental"} },
{ "Discovered", { 0, "Ontdekt","Entdeckt","Découverts","Descubiertos","Scoperti","Обнаружено","Виявлено","Открити","Откривени","Ανακαλύφθηκαν" ,"Descobertos"} },
{ "Create private channel", { 0, "Privékanaal aanmaken","Privaten Kanal erstellen","Créer un canal privé","Crear canal privado","Crea canale privato","Создать приватный канал","Створити приватний канал","Създай частен канал","Направи приватни канал","Δημιουργία ιδιωτικού καναλιού" ,"Criar canal privado"} },
{ "Join hashtag channel", { 0, "Deelnemen aan hashtagkanaal","Hashtag-Kanal beitreten","Rejoindre un canal hashtag","Unirse a canal de hashtag","Unisciti a canale hashtag","Войти в канал хэштега","Приєднатися до каналу хештегу","Присъедини се към хаштаг канал","Придружи се хаштаг каналу","Συμμετοχή σε κανάλι hashtag" ,"Entrar em canal de hashtag"} },
{ "Join private channel", { 0, "Deelnemen aan privékanaal","Privaten Kanal beitreten","Rejoindre un canal privé","Unirse a canal privado","Unisciti a canale privato","Войти в приватный канал","Приєднатися до приватного каналу","Присъедини се към частен канал","Придружи се приватном каналу","Συμμετοχή σε ιδιωτικό κανάλι" ,"Entrar em canal privado"} },
{ "Send advert", { 0, "Advert verzenden","Advert senden","Envoyer une annonce","Enviar anuncio","Invia advert","Отправить advert","Надіслати advert","Изпрати advert","Пошаљи advert","Αποστολή advert" ,"Enviar advert"} },
{ "Share channel", { 0, "Kanaal delen","Kanal teilen","Partager le canal","Compartir canal","Condividi canale","Поделиться каналом","Поділитися каналом","Сподели канала","Подели канал","Κοινή χρήση καναλιού" ,"Compartilhar canal"} },
{ "Advert", { 0, "Advert","Advert","Annonce","Anuncio","Advert","Advert","Advert","Advert","Advert","Advert" ,"Advert"} },
{ "Advert location", { 0, "Advertlocatie","Advert-Standort","Position de l'annonce","Ubicación del anuncio","Posizione advert","Местоположение advert","Місцезнаходження advert","Местоположение на advert","Локација advert-а","Τοποθεσία advert" ,"Local do advert"} },
{ "Latitude", { 0, "Breedtegraad","Breitengrad","Latitude","Latitud","Latitudine","Широта","Широта","Географска ширина","Географска ширина","Γεωγραφικό πλάτος" ,"Latitude"} },
{ "Longitude", { 0, "Lengtegraad","Längengrad","Longitude","Longitud","Longitudine","Долгота","Довгота","Географска дължина","Географска дужина","Γεωγραφικό μήκος" ,"Longitude"} },
{ "Share location in advert", { 0, "Locatie delen in advert","Standort im Advert teilen","Partager la position dans l'annonce","Compartir ubicación en anuncio","Condividi posizione in advert","Делиться местоположением в advert","Ділитися місцезнаходженням в advert","Споделяй местоположението в advert","Дели локацију у advert-у","Κοινή χρήση θέσης στο advert" ,"Compartilhar local no advert"} },
{ "Node name", { 0, "Nodenaam","Node-Name","Nom du nœud","Nombre del nodo","Nome nodo","Имя узла","Назва вузла","Име на възела","Назив чвора","Όνομα κόμβου" ,"Nome do nó"} },
{ "Display name", { 0, "Weergavenaam","Anzeigename","Nom affiché","Nombre visible","Nome visualizzato","Отображаемое имя","Відображуване ім'я","Показвано име","Приказно име","Εμφανιζόμενο όνομα" ,"Nome exibido"} },
{ "Identity (public key)", { 0, "Identiteit (publieke sleutel)","Identität (öffentl. Schlüssel)","Identité (clé publique)","Identidad (clave pública)","Identità (chiave pubblica)","Идентификатор (открытый ключ)","Ідентичність (відкритий ключ)","Идентичност (публичен ключ)","Идентитет (јавни кључ)","Ταυτότητα (δημόσιο κλειδί)" ,"Identidade (chave pública)"} },
{ "Community preset (meshcomod web)", { 0, "Community-preset (meshcomod web)","Community-Vorgabe (meshcomod web)","Préréglage communauté (meshcomod web)","Preajuste comunidad (meshcomod web)","Preset community (meshcomod web)","Пресет сообщества (meshcomod web)","Пресет спільноти (meshcomod web)","Пресет на общността (meshcomod web)","Унапред (meshcomod web)","Προεπιλογή κοινότητας (meshcomod web)" ,"Predefinição da comunidade (meshcomod web)"} },
{ "Frequency / bandwidth", { 0, "Frequentie / bandbreedte","Frequenz / Bandbreite","Fréquence / bande passante","Frecuencia / ancho de banda","Frequenza / larghezza di banda","Частота / полоса","Частота / смуга","Честота / лента","Фреквенција / опсег","Συχνότητα / εύρος ζώνης" ,"Frequência / largura de banda"} },
{ "SF / CR / TX / AF", { 0, "SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF","SF / CR / TX / AF" ,"SF / CR / TX / AF"} },
{ "Multi-byte routing (path hash)", { 0, "Multi-byte routing (pad-hash)","Multibyte-Routing (Pfad-Hash)","Routage multi-octet (hash de chemin)","Enrutamiento multibyte (hash de ruta)","Routing multi-byte (hash percorso)","Многобайтовая маршрутизация (хеш пути)","Багатобайтова маршрутизація (хеш шляху)","Многобайтово маршрутиране (хеш на пътя)","Вишебајтно рутирање (хеш путање)","Δρομολόγηση πολλών byte (hash διαδρομής)" ,"Roteamento multibyte (hash de rota)"} },
{ "Region scope (#tag, blank = none)", { 0, "Regiobereik (#tag, leeg = geen)","Regionsbereich (#tag, leer = keiner)","Portée régionale (#tag, vide = aucune)","Alcance de región (#tag, vacío = ninguno)","Ambito regione (#tag, vuoto = nessuno)","Регион (#tag, пусто = нет)","Регіон (#tag, порожньо = немає)","Регионален обхват (#tag, празно = няма)","Опсег региона (#tag, празно = ниједан)","Εύρος περιοχής (#tag, κενό = κανένα)" ,"Escopo de região (#tag, vazio = nenhum)"} },
{ "Auto chat", { 0, "Auto chat","Auto-Chat","Chat auto","Chat auto","Chat auto","Авто чат","Авто чат","Авто чат","Ауто ћаскање","Αυτόματη συνομιλία" ,"Auto chat"} },
{ "Auto repeater", { 0, "Auto repeater","Auto-Repeater","Répéteur auto","Repetidor auto","Ripetitore auto","Авто ретранслятор","Авто ретранслятор","Авто ретранслатор","Ауто репетитор","Αυτόματος αναμεταδότης" ,"Auto repetidor"} },
{ "Auto room", { 0, "Auto room","Auto-Raum","Salon auto","Sala auto","Stanza auto","Авто комната","Авто кімната","Авто стая","Ауто соба","Αυτόματο δωμάτιο" ,"Auto sala"} },
{ "Auto sensor", { 0, "Auto sensor","Auto-Sensor","Capteur auto","Sensor auto","Sensore auto","Авто датчик","Авто датчик","Авто сензор","Ауто сензор","Αυτόματος αισθητήρας" ,"Auto sensor"} },
{ "Overwrite oldest", { 0, "Oudste overschrijven","Älteste überschreiben","Écraser le plus ancien","Sobrescribir el más antiguo","Sovrascrivi il più vecchio","Перезаписать старейший","Перезаписати найстаріший","Презапиши най-стария","Препиши најстарији","Αντικατάσταση παλαιότερου" ,"Sobrescrever o mais antigo"} },
{ "Multi ACKs", { 0, "Multi-ACK's","Multi-ACKs","Multi-ACK","Multi-ACK","Multi-ACK","Множественные ACK","Множинні ACK","Множествени ACK","Више ACK","Πολλαπλά ACK" ,"Multi ACKs"} },
{ "Client repeat", { 0, "Client herhaling","Client-Wiederholung","Répétition client","Repetición de cliente","Ripetizione client","Повтор клиента","Повтор клієнта","Повтаряне от клиент","Понављање клијента","Επανάληψη πελάτη" ,"Repetição do cliente"} },
{ "RX boosted gain", { 0, "RX versterkte gain","RX verstärkte Verstärkung","Gain RX boosté","Ganancia RX aumentada","Guadagno RX potenziato","Усиленное RX","Підсилений RX","Усилено RX","Појачан RX","Ενισχυμένο κέρδος RX" ,"Ganho RX reforçado"} },
{ "Duty meter", { 0, "Duty-meter","Duty-Anzeige","Indicateur de cycle","Medidor de ciclo","Misuratore duty","Индикатор нагрузки","Індикатор завантаження","Измервател на цикъла","Мерач циклуса","Μετρητής κύκλου" ,"Medidor de ciclo"} },
{ "Network name", { 0, "Netwerknaam","Netzwerkname","Nom du réseau","Nombre de red","Nome rete","Имя сети","Назва мережі","Име на мрежата","Назив мреже","Όνομα δικτύου" ,"Nome da rede"} },
{ "Name fragment", { 0, "Naamfragment","Namensfragment","Fragment de nom","Fragmento de nombre","Frammento nome","Часть имени","Частина імені","Част от име","Део имена","Τμήμα ονόματος" ,"Fragmento de nome"} },
{ "Theme", { 0, "Thema","Thema","Thème","Tema","Tema","Тема","Тема","Тема","Тема","Θέμα" ,"Tema"} },
{ "Lock", { 0, "Vergrendelen","Sperren","Verrouiller","Bloquear","Blocca","Блокировка","Блокування","Заключи","Закључај","Κλείδωμα" ,"Bloquear"} },
{ "Switch", { 0, "Wisselen","Wechseln","Basculer","Cambiar","Cambia","Переключить","Перемкнути","Превключи","Пребаци","Εναλλαγή" ,"Trocar"} },
{ "Wi-Fi on", { 0, "Wi-Fi aan","WLAN ein","Wi-Fi activé","Wi-Fi activado","Wi-Fi acceso","Wi-Fi вкл","Wi-Fi увімк","Wi-Fi вкл","Wi-Fi укљ","Wi-Fi ναι" ,"Wi-Fi ligado"} },
{ "Wi-Fi off", { 0, "Wi-Fi uit","WLAN aus","Wi-Fi désactivé","Wi-Fi apagado","Wi-Fi spento","Wi-Fi выкл","Wi-Fi вимк","Wi-Fi изкл","Wi-Fi искљ","Wi-Fi όχι" ,"Wi-Fi desligado"} },
{ "BLE on", { 0, "BLE aan","BLE ein","BLE activé","BLE activado","BLE acceso","BLE вкл","BLE увімк","BLE вкл","BLE укљ","BLE ναι" ,"BLE ligado"} },
{ "BLE off", { 0, "BLE uit","BLE aus","BLE désactivé","BLE apagado","BLE spento","BLE выкл","BLE вимк","BLE изкл","BLE искљ","BLE όχι" ,"BLE desligado"} },
{ "Bluetooth on", { 0, "Bluetooth aan","Bluetooth ein","Bluetooth activé","Bluetooth activado","Bluetooth acceso","Bluetooth вкл","Bluetooth увімк","Bluetooth вкл","Bluetooth укљ","Bluetooth ναι" ,"Bluetooth ligado"} },
{ "Bluetooth off", { 0, "Bluetooth uit","Bluetooth aus","Bluetooth désactivé","Bluetooth apagado","Bluetooth spento","Bluetooth выкл","Bluetooth вимк","Bluetooth изкл","Bluetooth искљ","Bluetooth όχι" ,"Bluetooth desligado"} },
{ "Bluetooth already on", { 0, "Bluetooth al aan","Bluetooth bereits ein","Bluetooth déjà activé","Bluetooth ya activado","Bluetooth già acceso","Bluetooth уже вкл","Bluetooth вже увімк","Bluetooth вече е вкл","Bluetooth већ укључен","Το Bluetooth είναι ήδη ενεργό" ,"Bluetooth já ligado"} },
{ "Bluetooth already off", { 0, "Bluetooth al uit","Bluetooth bereits aus","Bluetooth déjà désactivé","Bluetooth ya apagado","Bluetooth già spento","Bluetooth уже выкл","Bluetooth вже вимк","Bluetooth вече е изкл","Bluetooth већ искључен","Το Bluetooth είναι ήδη ανενεργό" ,"Bluetooth já desligado"} },
{ "GPS on", { 0, "GPS aan","GPS ein","GPS activé","GPS activado","GPS acceso","GPS вкл","GPS увімк","GPS вкл","GPS укљ","GPS ναι" ,"GPS ligado"} },
{ "GPS off", { 0, "GPS uit","GPS aus","GPS désactivé","GPS apagado","GPS spento","GPS выкл","GPS вимк","GPS изкл","GPS искљ","GPS όχι" ,"GPS desligado"} },
{ "Sound on", { 0, "Geluid aan","Ton ein","Son activé","Sonido activado","Suono acceso","Звук вкл","Звук увімк","Звук вкл","Звук укљ","Ήχος ναι" ,"Som ligado"} },
{ "Sound off", { 0, "Geluid uit","Ton aus","Son désactivé","Sonido apagado","Suono spento","Звук выкл","Звук вимк","Звук изкл","Звук искљ","Ήχος όχι" ,"Som desligado"} },
{ "TCP on", { 0, "TCP aan","TCP ein","TCP activé","TCP activado","TCP acceso","TCP вкл","TCP увімк","TCP вкл","TCP укљ","TCP ναι" ,"TCP ligado"} },
{ "TCP off", { 0, "TCP uit","TCP aus","TCP désactivé","TCP apagado","TCP spento","TCP выкл","TCP вимк","TCP изкл","TCP искљ","TCP όχι" ,"TCP desligado"} },
{ "Welcome to WADAMESH", { 0, "Welkom bij WADAMESH","Willkommen bei WADAMESH","Bienvenue sur WADAMESH","Bienvenido a WADAMESH","Benvenuto in WADAMESH","Добро пожаловать в WADAMESH","Ласкаво просимо до WADAMESH","Добре дошли в WADAMESH","Добро дошли у WADAMESH","Καλώς ήρθατε στο WADAMESH" ,"Bem-vindo ao WADAMESH"} },
{ "Get Started", { 0, "Aan de slag","Loslegen","Commencer","Empezar","Inizia","Начать","Почати","Започни","Започни","Ξεκινήστε" ,"Começar"} },
{ "Choose your name", { 0, "Kies je naam","Wählen Sie Ihren Namen","Choisissez votre nom","Elige tu nombre","Scegli il tuo nome","Выберите имя","Виберіть ім'я","Изберете името си","Изаберите своје име","Επιλέξτε το όνομά σας" ,"Escolha seu nome"} },
{ "Your name", { 0, "Je naam","Ihr Name","Votre nom","Tu nombre","Il tuo nome","Ваше имя","Ваше ім'я","Вашето име","Ваше име","Το όνομά σας" ,"Seu nome"} },
{ "Choose your region", { 0, "Kies je regio","Wählen Sie Ihre Region","Choisissez votre région","Elige tu región","Scegli la tua regione","Выберите регион","Виберіть регіон","Изберете региона си","Изаберите регион","Επιλέξτε την περιοχή σας" ,"Escolha sua região"} },
{ "Next", { 0, "Volgende","Weiter","Suivant","Siguiente","Avanti","Далее","Далі","Напред","Даље","Επόμενο" ,"Avançar"} },
{ "Skip", { 0, "Overslaan","Überspringen","Passer","Omitir","Salta","Пропустить","Пропустити","Пропусни","Прескочи","Παράλειψη" ,"Pular"} },
{ "Finish", { 0, "Voltooien","Fertig","Terminer","Finalizar","Fine","Готово","Готово","Готово","Заврши","Τέλος" ,"Concluir"} },
{ "Connect to Wi-Fi", { 0, "Verbinden met wifi","Mit WLAN verbinden","Se connecter au Wi-Fi","Conectar a Wi-Fi","Connetti al Wi-Fi","Подключиться к Wi-Fi","Підключитися до Wi-Fi","Свържи се с Wi-Fi","Повежи се на Wi-Fi","Σύνδεση σε Wi-Fi" ,"Conectar ao Wi-Fi"} },
{ "Password (empty = skip Wi-Fi)", { 0, "Wachtwoord (leeg = wifi overslaan)","Passwort (leer = WLAN überspringen)","Mot de passe (vide = ignorer le Wi-Fi)","Contraseña (vacío = omitir Wi-Fi)","Password (vuoto = salta Wi-Fi)","Пароль (пусто = пропустить Wi-Fi)","Пароль (порожньо = пропустити Wi-Fi)","Парола (празно = пропусни Wi-Fi)","Лозинка (празно = прескочи Wi-Fi)","Κωδικός (κενό = παράλειψη Wi-Fi)" ,"Senha (vazio = pular Wi-Fi)"} },
// ---- Sweep pass 7: action sheet / file manager / composer / status alerts ----
{ "Copy", { 0, "Kopiëren","Kopieren","Copier","Copiar","Copia","Копировать","Копіювати","Копирай","Копирај","Αντιγραφή" } },
{ "Cut", { 0, "Knippen","Ausschneiden","Couper","Cortar","Taglia","Вырезать","Вирізати","Изрежи","Исеци","Αποκοπή" } },
{ "Delete", { 0, "Verwijderen","Löschen","Supprimer","Eliminar","Elimina","Удалить","Видалити","Изтрий","Обриши","Διαγραφή" } },
{ "Remove", { 0, "Verwijderen","Entfernen","Retirer","Quitar","Rimuovi","Убрать","Прибрати","Премахни","Уклони","Αφαίρεση" } },
{ "Rename", { 0, "Hernoemen","Umbenennen","Renommer","Renombrar","Rinomina","Переименовать","Перейменувати","Преименувай","Преименуј","Μετονομασία" } },
{ "Format", { 0, "Formatteren","Formatieren","Formater","Formatear","Formatta","Форматировать","Форматувати","Форматирай","Форматирај","Διαμόρφωση" } },
{ "Paste", { 0, "Plakken","Einfügen","Coller","Pegar","Incolla","Вставить","Вставити","Постави","Налепи","Επικόλληση" } },
{ "Back", { 0, "Terug","Zurück","Retour","Atrás","Indietro","Назад","Назад","Назад","Назад","Πίσω" } },
{ "Reboot", { 0, "Herstarten","Neustart","Redémarrer","Reiniciar","Riavvia","Перезагрузка","Перезавантажити","Рестартирай","Рестартуј","Επανεκκίνηση" } },
{ "Mark read", { 0, "Markeer als gelezen","Als gelesen markieren","Marquer comme lu","Marcar como leído","Segna come letto","Отметить прочитанным","Позначити прочитаним","Маркирай прочетено","Означи прочитано","Σήμανση ως αναγνωσμένο" } },
{ "New file", { 0, "Nieuw bestand","Neue Datei","Nouveau fichier","Nuevo archivo","Nuovo file","Новый файл","Новий файл","Нов файл","Нова датотека","Νέο αρχείο" } },
{ "New folder", { 0, "Nieuwe map","Neuer Ordner","Nouveau dossier","Nueva carpeta","Nuova cartella","Новая папка","Нова тека","Нова папка","Нова фасцикла","Νέος φάκελος" } },
{ "Folder created", { 0, "Map aangemaakt","Ordner erstellt","Dossier créé","Carpeta creada","Cartella creata","Папка создана","Теку створено","Папката е създадена","Фасцикла направљена","Ο φάκελος δημιουργήθηκε" } },
{ "Empty file", { 0, "Leeg bestand","Leere Datei","Fichier vide","Archivo vacío","File vuoto","Пустой файл","Порожній файл","Празен файл","Празна датотека","Κενό αρχείο" } },
{ "Renamed", { 0, "Hernoemd","Umbenannt","Renommé","Renombrado","Rinominato","Переименовано","Перейменовано","Преименувано","Преименовано","Μετονομάστηκε" } },
{ "Rename failed", { 0, "Hernoemen mislukt","Umbenennen fehlgeschlagen","Échec du renommage","Error al renombrar","Rinomina non riuscita","Не удалось переименовать","Не вдалося перейменувати","Преименуването неуспешно","Преименовање није успело","Αποτυχία μετονομασίας" } },
{ "Pasted", { 0, "Geplakt","Eingefügt","Collé","Pegado","Incollato","Вставлено","Вставлено","Поставено","Налепљено","Επικολλήθηκε" } },
{ "Paste failed", { 0, "Plakken mislukt","Einfügen fehlgeschlagen","Échec du collage","Error al pegar","Incolla non riuscito","Не удалось вставить","Не вдалося вставити","Поставянето неуспешно","Лепљење није успело","Αποτυχία επικόλλησης" } },
{ "Copied - Paste in a folder", { 0, "Gekopieerd - plak in een map","Kopiert - in einen Ordner einfügen","Copié - collez dans un dossier","Copiado - pega en una carpeta","Copiato - incolla in una cartella","Скопировано - вставьте в папку","Скопійовано - вставте в теку","Копирано - поставете в папка","Копирано - налепи у фасциклу","Αντιγράφηκε - επικολλήστε σε φάκελο" } },
{ "Cut - Paste in a folder", { 0, "Geknipt - plak in een map","Ausgeschnitten - in einen Ordner einfügen","Coupé - collez dans un dossier","Cortado - pega en una carpeta","Tagliato - incolla in una cartella","Вырезано - вставьте в папку","Вирізано - вставте в теку","Изрязано - поставете в папка","Исечено - налепи у фасциклу","Αποκόπηκε - επικολλήστε σε φάκελο" } },
{ "mkdir failed", { 0, "map maken mislukt","Ordner anlegen fehlgeschlagen","Échec de création du dossier","Error al crear carpeta","Creazione cartella non riuscita","Не удалось создать папку","Не вдалося створити теку","Създаването на папка неуспешно","Прављење фасцикле није успело","Αποτυχία δημιουργίας φακέλου" } },
{ "Image too large (>4 MB)", { 0, "Afbeelding te groot (>4 MB)","Bild zu groß (>4 MB)","Image trop grande (>4 Mo)","Imagen demasiado grande (>4 MB)","Immagine troppo grande (>4 MB)","Изображение слишком большое (>4 МБ)","Зображення завелике (>4 МБ)","Изображението е твърде голямо (>4 MB)","Слика превелика (>4 MB)","Πολύ μεγάλη εικόνα (>4 MB)" } },
{ "Type a message...", { 0, "Typ een bericht...","Nachricht eingeben...","Saisissez un message...","Escribe un mensaje...","Scrivi un messaggio...","Введите сообщение...","Введіть повідомлення...","Въведете съобщение...","Унесите поруку...","Πληκτρολογήστε μήνυμα..." } },
{ "search", { 0, "zoeken","Suche","rechercher","buscar","cerca","поиск","пошук","търсене","претрага","αναζήτηση" } },
{ "leave empty to generate", { 0, "leeg laten om te genereren","leer lassen zum Erzeugen","laisser vide pour générer","dejar vacío para generar","lascia vuoto per generare","оставьте пустым для генерации","залиште порожнім для генерації","оставете празно за генериране","оставите празно за генерисање","κενό για δημιουργία" } },
{ "32 hex characters", { 0, "32 hex-tekens","32 Hex-Zeichen","32 caractères hex","32 caracteres hex","32 caratteri hex","32 hex-символа","32 hex-символи","32 hex знака","32 hex знака","32 hex χαρακτήρες" } },
{ "Enter exactly 32 hex characters.", { 0, "Voer precies 32 hex-tekens in.","Genau 32 Hex-Zeichen eingeben.","Saisissez exactement 32 caractères hex.","Introduce exactamente 32 caracteres hex.","Inserisci esattamente 32 caratteri hex.","Введите ровно 32 hex-символа.","Введіть рівно 32 hex-символи.","Въведете точно 32 hex знака.","Унесите тачно 32 hex знака.","Εισαγάγετε ακριβώς 32 hex χαρακτήρες." } },
{ "Secret must be 32 hex chars (or empty).", { 0, "Geheim moet 32 hex-tekens zijn (of leeg).","Geheimnis muss 32 Hex-Zeichen sein (oder leer).","Le secret doit faire 32 hex (ou vide).","El secreto debe tener 32 hex (o vacío).","Il segreto deve essere 32 hex (o vuoto).","Секрет — 32 hex-символа (или пусто).","Секрет — 32 hex-символи (або порожньо).","Тайната трябва да е 32 hex (или празно).","Тајна мора бити 32 hex (или празно).","Το μυστικό πρέπει 32 hex (ή κενό)." } },
{ "Optional — type your network, or leave blank to skip.", { 0, "Optioneel — typ je netwerk, of laat leeg om over te slaan.","Optional — Netzwerk eingeben oder leer lassen.","Facultatif — saisissez votre réseau, ou laissez vide.","Opcional — escribe tu red, o déjalo en blanco.","Opzionale — digita la rete, o lascia vuoto.","Необязательно — введите сеть или оставьте пустым.","Необов'язково — введіть мережу або залиште порожнім.","По избор — въведете мрежата си или оставете празно.","Опционо — унесите мрежу или оставите празно.","Προαιρετικό — πληκτρολογήστε δίκτυο ή αφήστε κενό." } },
{ "Saved", { 0, "Opgeslagen","Gespeichert","Enregistré","Guardado","Salvato","Сохранено","Збережено","Запазено","Сачувано","Αποθηκεύτηκε" } },
{ "Advert sent", { 0, "Advert verzonden","Advert gesendet","Annonce envoyée","Anuncio enviado","Advert inviato","Advert отправлен","Advert надіслано","Advert изпратен","Advert послат","Στάλθηκε advert" } },
{ "Advert failed", { 0, "Advert mislukt","Advert fehlgeschlagen","Échec de l'annonce","Error en el anuncio","Advert non riuscito","Сбой advert","Помилка advert","Advert неуспешен","Advert није успео","Αποτυχία advert" } },
{ "Flood advert sent", { 0, "Flood-advert verzonden","Flood-Advert gesendet","Annonce flood envoyée","Anuncio flood enviado","Advert flood inviato","Flood advert отправлен","Flood advert надіслано","Flood advert изпратен","Flood advert послат","Στάλθηκε flood advert" } },
{ "Zero-hop advert sent", { 0, "Zero-hop advert verzonden","Zero-Hop-Advert gesendet","Annonce zero-hop envoyée","Anuncio zero-hop enviado","Advert zero-hop inviato","Zero-hop advert отправлен","Zero-hop advert надіслано","Zero-hop advert изпратен","Zero-hop advert послат","Στάλθηκε zero-hop advert" } },
{ "Radio applied", { 0, "Radio toegepast","Funk angewendet","Radio appliquée","Radio aplicada","Radio applicata","Радио применено","Радіо застосовано","Радиото е приложено","Радио примењен","Εφαρμόστηκε ράδιο" } },
{ "Radio + region set", { 0, "Radio + regio ingesteld","Funk + Region gesetzt","Radio + région définies","Radio + región establecidas","Radio + regione impostate","Радио + регион заданы","Радіо + регіон задано","Радио + регион зададени","Радио + регион постављени","Ορίστηκαν ράδιο + περιοχή" } },
{ "Chat removed", { 0, "Chat verwijderd","Chat entfernt","Discussion supprimée","Chat eliminado","Chat rimossa","Чат удалён","Чат видалено","Чатът е премахнат","Ћаскање уклоњено","Η συνομιλία αφαιρέθηκε" } },
{ "Channel removed", { 0, "Kanaal verwijderd","Kanal entfernt","Canal supprimé","Canal eliminado","Canale rimosso","Канал удалён","Канал видалено","Каналът е премахнат","Канал уклоњен","Το κανάλι αφαιρέθηκε" } },
{ "Sender blocked", { 0, "Afzender geblokkeerd","Absender blockiert","Expéditeur bloqué","Remitente bloqueado","Mittente bloccato","Отправитель заблокирован","Відправника заблоковано","Подателят е блокиран","Пошиљалац блокиран","Ο αποστολέας αποκλείστηκε" } },
{ "Can't block — not a contact", { 0, "Kan niet blokkeren — geen contact","Blockieren nicht möglich — kein Kontakt","Blocage impossible — pas un contact","No se puede bloquear — no es un contacto","Impossibile bloccare — non è un contatto","Нельзя заблокировать — не контакт","Не можна заблокувати — не контакт","Не може да се блокира — не е контакт","Не могу да блокирам — није контакт","Αδυναμία αποκλεισμού — δεν είναι επαφή" } },
{ "Added to favorites", { 0, "Toegevoegd aan favorieten","Zu Favoriten hinzugefügt","Ajouté aux favoris","Añadido a favoritos","Aggiunto ai preferiti","Добавлено в избранное","Додано до обраного","Добавено в любими","Додато у омиљене","Προστέθηκε στα αγαπημένα" } },
{ "Removed from favorites", { 0, "Verwijderd uit favorieten","Aus Favoriten entfernt","Retiré des favoris","Quitado de favoritos","Rimosso dai preferiti","Удалено из избранного","Видалено з обраного","Премахнато от любими","Уклоњено из омиљених","Αφαιρέθηκε από τα αγαπημένα" } },
{ "Taste the rainbow!", { 0, "Proef de regenboog!","Schmecke den Regenbogen!","Goûtez l'arc-en-ciel !","¡Saborea el arcoíris!","Assapora l'arcobaleno!","Попробуй радугу!","Скуштуй веселку!","Вкуси дъгата!","Окуси дугу!","Γευτείτε το ουράνιο τόξο!" } },
{ "Chat bubbles: plain", { 0, "Chatbubbels: effen","Chat-Blasen: schlicht","Bulles : simples","Burbujas: lisas","Fumetti: semplici","Пузыри: простые","Бульбашки: прості","Балончета: семпли","Облачићи: обични","Φούσκες: απλές" } },
{ "Accent popups: on", { 0, "Accent-popups: aan","Akzent-Popups: ein","Fenêtres d'accents : activé","Ventanas de acentos: activado","Popup accenti: attivi","Подсказки акцентов: вкл","Підказки акцентів: увімк","Изскачащи акценти: вкл","Искачући акценти: укљ","Αναδυόμενα τόνων: ναι" } },
{ "Accent popups: off", { 0, "Accent-popups: uit","Akzent-Popups: aus","Fenêtres d'accents : désactivé","Ventanas de acentos: desactivado","Popup accenti: disattivi","Подсказки акцентов: выкл","Підказки акцентів: вимк","Изскачащи акценти: изкл","Искачући акценти: искљ","Αναδυόμενα τόνων: όχι" } },
{ "Failed to save channel.", { 0, "Kan kanaal niet opslaan.","Kanal konnte nicht gespeichert werden.","Échec de l'enregistrement du canal.","Error al guardar el canal.","Impossibile salvare il canale.","Не удалось сохранить канал.","Не вдалося зберегти канал.","Каналът не можа да се запази.","Чување канала није успело.","Αποτυχία αποθήκευσης καναλιού." } },
{ "Distance: km", { 0, "Afstand: km","Entfernung: km","Distance : km","Distancia: km","Distanza: km","Расстояние: км","Відстань: км","Разстояние: км","Растојање: km","Απόσταση: km" } },
{ "Distance: miles", { 0, "Afstand: mijlen","Entfernung: Meilen","Distance : miles","Distancia: millas","Distanza: miglia","Расстояние: мили","Відстань: милі","Разстояние: мили","Растојање: миље","Απόσταση: μίλια" } },
{ "Map tiles: microSD", { 0, "Kaarttegels: microSD","Kartenkacheln: microSD","Tuiles : microSD","Teselas: microSD","Riquadri: microSD","Тайлы карты: microSD","Плитки карти: microSD","Плочки: microSD","Плочице: microSD","Πλακίδια: microSD" } },
{ "Map tiles: server", { 0, "Kaarttegels: server","Kartenkacheln: Server","Tuiles : serveur","Teselas: servidor","Riquadri: server","Тайлы карты: сервер","Плитки карти: сервер","Плочки: сървър","Плочице: сервер","Πλακίδια: διακομιστής" } },
{ "Mark all chats and channels as read?", { 0, "Alle chats en kanalen als gelezen markeren?","Alle Chats und Kanäle als gelesen markieren?","Marquer tous les chats et canaux comme lus ?","¿Marcar todos los chats y canales como leídos?","Segnare tutte le chat e i canali come letti?","Отметить все чаты и каналы прочитанными?","Позначити всі чати й канали прочитаними?","Да отбележа всички чатове и канали като прочетени?","Означити сва ћаскања и канале као прочитане?","Σήμανση όλων ως αναγνωσμένα;" } },
{ "Import this backup?\nReplaces identity,\nchannels & contacts,\nthen reboots.", { 0, "Deze back-up importeren?\nVervangt identiteit,\nkanalen & contacten,\nherstart daarna.","Dieses Backup importieren?\nErsetzt Identität,\nKanäle & Kontakte,\ndann Neustart.","Importer cette sauvegarde ?\nRemplace l'identité,\nles canaux & contacts,\npuis redémarre.","¿Importar esta copia?\nReemplaza identidad,\ncanales y contactos,\nluego reinicia.","Importare questo backup?\nSostituisce identità,\ncanali e contatti,\npoi riavvia.","Импортировать копию?\nЗаменит личность,\nканалы и контакты,\nзатем перезагрузка.","Імпортувати копію?\nЗамінить ідентичність,\nканали й контакти,\nпотім перезавантаження.","Да внеса този архив?\nЗаменя самоличност,\nканали и контакти,\nпосле рестартира.","Увези ову копију?\nЗамењује идентитет,\nканале и контакте,\nпа рестартује.","Εισαγωγή αντιγράφου;\nΑντικαθιστά ταυτότητα,\nκανάλια & επαφές,\nμετά επανεκκινεί." } },
{ "Every node you talk to must match. You can change this later in Settings.", { 0, "Elke node waarmee je praat moet overeenkomen. Je kunt dit later wijzigen in Instellingen.","Jeder Knoten muss übereinstimmen. Sie können dies später in den Einstellungen ändern.","Chaque nœud doit correspondre. Modifiable plus tard dans Paramètres.","Cada nodo debe coincidir. Puedes cambiarlo luego en Ajustes.","Ogni nodo deve corrispondere. Modificabile dopo in Impostazioni.","Все узлы должны совпадать. Можно изменить позже в Настройках.","Усі вузли мають збігатися. Можна змінити пізніше в Налаштуваннях.","Всеки възел трябва да съвпада. Можете да промените по-късно в Настройки.","Сваки чвор мора да се поклапа. Касније у Подешавањима.","Κάθε κόμβος πρέπει να ταιριάζει. Αλλαγή αργότερα στις Ρυθμίσεις." } },
{ "How you'll appear to other nodes. You can change this later in Settings.", { 0, "Hoe je verschijnt voor andere nodes. Je kunt dit later wijzigen in Instellingen.","Wie Sie für andere Knoten erscheinen. Später in den Einstellungen änderbar.","Comment vous apparaîtrez aux autres nœuds. Modifiable dans Paramètres.","Cómo aparecerás ante otros nodos. Puedes cambiarlo en Ajustes.","Come apparirai agli altri nodi. Modificabile in Impostazioni.","Как вы будете видны другим узлам. Можно изменить в Настройках.","Як вас бачитимуть інші вузли. Можна змінити в Налаштуваннях.","Как ще изглеждате за други възли. Промяна в Настройки.","Како ћете изгледати другим чворовима. У Подешавањима.","Πώς θα εμφανίζεστε σε άλλους κόμβους. Αλλαγή στις Ρυθμίσεις." } },
{ "e.g. Family", { 0, "bijv. Familie","z. B. Familie","ex. Famille","p. ej. Familia","es. Famiglia","напр. Семья","напр. Сім'я","напр. Семейство","нпр. Породица","π.χ. Οικογένεια" } },
{ "e.g. mesh", { 0, "bijv. mesh","z. B. mesh","ex. mesh","p. ej. mesh","es. mesh","напр. mesh","напр. mesh","напр. mesh","нпр. mesh","π.χ. mesh" } },
{ "Copy", { 0, "Kopiëren","Kopieren","Copier","Copiar","Copia","Копировать","Копіювати","Копирай","Копирај","Αντιγραφή" ,"Copiar"} },
{ "Cut", { 0, "Knippen","Ausschneiden","Couper","Cortar","Taglia","Вырезать","Вирізати","Изрежи","Исеци","Αποκοπή" ,"Recortar"} },
{ "Delete", { 0, "Verwijderen","Löschen","Supprimer","Eliminar","Elimina","Удалить","Видалити","Изтрий","Обриши","Διαγραφή" ,"Excluir"} },
{ "Remove", { 0, "Verwijderen","Entfernen","Retirer","Quitar","Rimuovi","Убрать","Прибрати","Премахни","Уклони","Αφαίρεση" ,"Remover"} },
{ "Rename", { 0, "Hernoemen","Umbenennen","Renommer","Renombrar","Rinomina","Переименовать","Перейменувати","Преименувай","Преименуј","Μετονομασία" ,"Renomear"} },
{ "Format", { 0, "Formatteren","Formatieren","Formater","Formatear","Formatta","Форматировать","Форматувати","Форматирай","Форматирај","Διαμόρφωση" ,"Formatar"} },
{ "Paste", { 0, "Plakken","Einfügen","Coller","Pegar","Incolla","Вставить","Вставити","Постави","Налепи","Επικόλληση" ,"Colar"} },
{ "Back", { 0, "Terug","Zurück","Retour","Atrás","Indietro","Назад","Назад","Назад","Назад","Πίσω" ,"Voltar"} },
{ "Reboot", { 0, "Herstarten","Neustart","Redémarrer","Reiniciar","Riavvia","Перезагрузка","Перезавантажити","Рестартирай","Рестартуј","Επανεκκίνηση" ,"Reiniciar"} },
{ "Mark read", { 0, "Markeer als gelezen","Als gelesen markieren","Marquer comme lu","Marcar como leído","Segna come letto","Отметить прочитанным","Позначити прочитаним","Маркирай прочетено","Означи прочитано","Σήμανση ως αναγνωσμένο" ,"Marcar como lido"} },
{ "New file", { 0, "Nieuw bestand","Neue Datei","Nouveau fichier","Nuevo archivo","Nuovo file","Новый файл","Новий файл","Нов файл","Нова датотека","Νέο αρχείο" ,"Novo arquivo"} },
{ "New folder", { 0, "Nieuwe map","Neuer Ordner","Nouveau dossier","Nueva carpeta","Nuova cartella","Новая папка","Нова тека","Нова папка","Нова фасцикла","Νέος φάκελος" ,"Nova pasta"} },
{ "Folder created", { 0, "Map aangemaakt","Ordner erstellt","Dossier créé","Carpeta creada","Cartella creata","Папка создана","Теку створено","Папката е създадена","Фасцикла направљена","Ο φάκελος δημιουργήθηκε" ,"Pasta criada"} },
{ "Empty file", { 0, "Leeg bestand","Leere Datei","Fichier vide","Archivo vacío","File vuoto","Пустой файл","Порожній файл","Празен файл","Празна датотека","Κενό αρχείο" ,"Arquivo vazio"} },
{ "Renamed", { 0, "Hernoemd","Umbenannt","Renommé","Renombrado","Rinominato","Переименовано","Перейменовано","Преименувано","Преименовано","Μετονομάστηκε" ,"Renomeado"} },
{ "Rename failed", { 0, "Hernoemen mislukt","Umbenennen fehlgeschlagen","Échec du renommage","Error al renombrar","Rinomina non riuscita","Не удалось переименовать","Не вдалося перейменувати","Преименуването неуспешно","Преименовање није успело","Αποτυχία μετονομασίας" ,"Falha ao renomear"} },
{ "Pasted", { 0, "Geplakt","Eingefügt","Collé","Pegado","Incollato","Вставлено","Вставлено","Поставено","Налепљено","Επικολλήθηκε" ,"Colado"} },
{ "Paste failed", { 0, "Plakken mislukt","Einfügen fehlgeschlagen","Échec du collage","Error al pegar","Incolla non riuscito","Не удалось вставить","Не вдалося вставити","Поставянето неуспешно","Лепљење није успело","Αποτυχία επικόλλησης" ,"Falha ao colar"} },
{ "Copied - Paste in a folder", { 0, "Gekopieerd - plak in een map","Kopiert - in einen Ordner einfügen","Copié - collez dans un dossier","Copiado - pega en una carpeta","Copiato - incolla in una cartella","Скопировано - вставьте в папку","Скопійовано - вставте в теку","Копирано - поставете в папка","Копирано - налепи у фасциклу","Αντιγράφηκε - επικολλήστε σε φάκελο" ,"Copiado - cole numa pasta"} },
{ "Cut - Paste in a folder", { 0, "Geknipt - plak in een map","Ausgeschnitten - in einen Ordner einfügen","Coupé - collez dans un dossier","Cortado - pega en una carpeta","Tagliato - incolla in una cartella","Вырезано - вставьте в папку","Вирізано - вставте в теку","Изрязано - поставете в папка","Исечено - налепи у фасциклу","Αποκόπηκε - επικολλήστε σε φάκελο" ,"Recortado - cole numa pasta"} },
{ "mkdir failed", { 0, "map maken mislukt","Ordner anlegen fehlgeschlagen","Échec de création du dossier","Error al crear carpeta","Creazione cartella non riuscita","Не удалось создать папку","Не вдалося створити теку","Създаването на папка неуспешно","Прављење фасцикле није успело","Αποτυχία δημιουργίας φακέλου" ,"falha no mkdir"} },
{ "Image too large (>4 MB)", { 0, "Afbeelding te groot (>4 MB)","Bild zu groß (>4 MB)","Image trop grande (>4 Mo)","Imagen demasiado grande (>4 MB)","Immagine troppo grande (>4 MB)","Изображение слишком большое (>4 МБ)","Зображення завелике (>4 МБ)","Изображението е твърде голямо (>4 MB)","Слика превелика (>4 MB)","Πολύ μεγάλη εικόνα (>4 MB)" ,"Imagem grande demais (>4 MB)"} },
{ "Type a message...", { 0, "Typ een bericht...","Nachricht eingeben...","Saisissez un message...","Escribe un mensaje...","Scrivi un messaggio...","Введите сообщение...","Введіть повідомлення...","Въведете съобщение...","Унесите поруку...","Πληκτρολογήστε μήνυμα..." ,"Digite uma mensagem..."} },
{ "search", { 0, "zoeken","Suche","rechercher","buscar","cerca","поиск","пошук","търсене","претрага","αναζήτηση" ,"buscar"} },
{ "leave empty to generate", { 0, "leeg laten om te genereren","leer lassen zum Erzeugen","laisser vide pour générer","dejar vacío para generar","lascia vuoto per generare","оставьте пустым для генерации","залиште порожнім для генерації","оставете празно за генериране","оставите празно за генерисање","κενό για δημιουργία" ,"deixe vazio para gerar"} },
{ "32 hex characters", { 0, "32 hex-tekens","32 Hex-Zeichen","32 caractères hex","32 caracteres hex","32 caratteri hex","32 hex-символа","32 hex-символи","32 hex знака","32 hex знака","32 hex χαρακτήρες" ,"32 caracteres hex"} },
{ "Enter exactly 32 hex characters.", { 0, "Voer precies 32 hex-tekens in.","Genau 32 Hex-Zeichen eingeben.","Saisissez exactement 32 caractères hex.","Introduce exactamente 32 caracteres hex.","Inserisci esattamente 32 caratteri hex.","Введите ровно 32 hex-символа.","Введіть рівно 32 hex-символи.","Въведете точно 32 hex знака.","Унесите тачно 32 hex знака.","Εισαγάγετε ακριβώς 32 hex χαρακτήρες." ,"Digite exatamente 32 caracteres hex."} },
{ "Secret must be 32 hex chars (or empty).", { 0, "Geheim moet 32 hex-tekens zijn (of leeg).","Geheimnis muss 32 Hex-Zeichen sein (oder leer).","Le secret doit faire 32 hex (ou vide).","El secreto debe tener 32 hex (o vacío).","Il segreto deve essere 32 hex (o vuoto).","Секрет — 32 hex-символа (или пусто).","Секрет — 32 hex-символи (або порожньо).","Тайната трябва да е 32 hex (или празно).","Тајна мора бити 32 hex (или празно).","Το μυστικό πρέπει 32 hex (ή κενό)." ,"O segredo deve ter 32 hex (ou vazio)."} },
{ "Optional — type your network, or leave blank to skip.", { 0, "Optioneel — typ je netwerk, of laat leeg om over te slaan.","Optional — Netzwerk eingeben oder leer lassen.","Facultatif — saisissez votre réseau, ou laissez vide.","Opcional — escribe tu red, o déjalo en blanco.","Opzionale — digita la rete, o lascia vuoto.","Необязательно — введите сеть или оставьте пустым.","Необов'язково — введіть мережу або залиште порожнім.","По избор — въведете мрежата си или оставете празно.","Опционо — унесите мрежу или оставите празно.","Προαιρετικό — πληκτρολογήστε δίκτυο ή αφήστε κενό." ,"Opcional — digite sua rede, ou deixe em branco para pular."} },
{ "Saved", { 0, "Opgeslagen","Gespeichert","Enregistré","Guardado","Salvato","Сохранено","Збережено","Запазено","Сачувано","Αποθηκεύτηκε" ,"Salvo"} },
{ "Advert sent", { 0, "Advert verzonden","Advert gesendet","Annonce envoyée","Anuncio enviado","Advert inviato","Advert отправлен","Advert надіслано","Advert изпратен","Advert послат","Στάλθηκε advert" ,"Advert enviado"} },
{ "Advert failed", { 0, "Advert mislukt","Advert fehlgeschlagen","Échec de l'annonce","Error en el anuncio","Advert non riuscito","Сбой advert","Помилка advert","Advert неуспешен","Advert није успео","Αποτυχία advert" ,"Falha no advert"} },
{ "Flood advert sent", { 0, "Flood-advert verzonden","Flood-Advert gesendet","Annonce flood envoyée","Anuncio flood enviado","Advert flood inviato","Flood advert отправлен","Flood advert надіслано","Flood advert изпратен","Flood advert послат","Στάλθηκε flood advert" ,"Flood advert enviado"} },
{ "Zero-hop advert sent", { 0, "Zero-hop advert verzonden","Zero-Hop-Advert gesendet","Annonce zero-hop envoyée","Anuncio zero-hop enviado","Advert zero-hop inviato","Zero-hop advert отправлен","Zero-hop advert надіслано","Zero-hop advert изпратен","Zero-hop advert послат","Στάλθηκε zero-hop advert" ,"Zero-hop advert enviado"} },
{ "Radio applied", { 0, "Radio toegepast","Funk angewendet","Radio appliquée","Radio aplicada","Radio applicata","Радио применено","Радіо застосовано","Радиото е приложено","Радио примењен","Εφαρμόστηκε ράδιο" ,"Rádio aplicado"} },
{ "Radio + region set", { 0, "Radio + regio ingesteld","Funk + Region gesetzt","Radio + région définies","Radio + región establecidas","Radio + regione impostate","Радио + регион заданы","Радіо + регіон задано","Радио + регион зададени","Радио + регион постављени","Ορίστηκαν ράδιο + περιοχή" ,"Rádio + região definidos"} },
{ "Chat removed", { 0, "Chat verwijderd","Chat entfernt","Discussion supprimée","Chat eliminado","Chat rimossa","Чат удалён","Чат видалено","Чатът е премахнат","Ћаскање уклоњено","Η συνομιλία αφαιρέθηκε" ,"Chat removido"} },
{ "Channel removed", { 0, "Kanaal verwijderd","Kanal entfernt","Canal supprimé","Canal eliminado","Canale rimosso","Канал удалён","Канал видалено","Каналът е премахнат","Канал уклоњен","Το κανάλι αφαιρέθηκε" ,"Canal removido"} },
{ "Sender blocked", { 0, "Afzender geblokkeerd","Absender blockiert","Expéditeur bloqué","Remitente bloqueado","Mittente bloccato","Отправитель заблокирован","Відправника заблоковано","Подателят е блокиран","Пошиљалац блокиран","Ο αποστολέας αποκλείστηκε" ,"Remetente bloqueado"} },
{ "Can't block — not a contact", { 0, "Kan niet blokkeren — geen contact","Blockieren nicht möglich — kein Kontakt","Blocage impossible — pas un contact","No se puede bloquear — no es un contacto","Impossibile bloccare — non è un contatto","Нельзя заблокировать — не контакт","Не можна заблокувати — не контакт","Не може да се блокира — не е контакт","Не могу да блокирам — није контакт","Αδυναμία αποκλεισμού — δεν είναι επαφή" ,"Não dá para bloquear — não é contato"} },
{ "Added to favorites", { 0, "Toegevoegd aan favorieten","Zu Favoriten hinzugefügt","Ajouté aux favoris","Añadido a favoritos","Aggiunto ai preferiti","Добавлено в избранное","Додано до обраного","Добавено в любими","Додато у омиљене","Προστέθηκε στα αγαπημένα" ,"Adicionado aos favoritos"} },
{ "Removed from favorites", { 0, "Verwijderd uit favorieten","Aus Favoriten entfernt","Retiré des favoris","Quitado de favoritos","Rimosso dai preferiti","Удалено из избранного","Видалено з обраного","Премахнато от любими","Уклоњено из омиљених","Αφαιρέθηκε από τα αγαπημένα" ,"Removido dos favoritos"} },
{ "Taste the rainbow!", { 0, "Proef de regenboog!","Schmecke den Regenbogen!","Goûtez l'arc-en-ciel !","¡Saborea el arcoíris!","Assapora l'arcobaleno!","Попробуй радугу!","Скуштуй веселку!","Вкуси дъгата!","Окуси дугу!","Γευτείτε το ουράνιο τόξο!" ,"Prove o arco-íris!"} },
{ "Chat bubbles: plain", { 0, "Chatbubbels: effen","Chat-Blasen: schlicht","Bulles : simples","Burbujas: lisas","Fumetti: semplici","Пузыри: простые","Бульбашки: прості","Балончета: семпли","Облачићи: обични","Φούσκες: απλές" ,"Balões de chat: simples"} },
{ "Accent popups: on", { 0, "Accent-popups: aan","Akzent-Popups: ein","Fenêtres d'accents : activé","Ventanas de acentos: activado","Popup accenti: attivi","Подсказки акцентов: вкл","Підказки акцентів: увімк","Изскачащи акценти: вкл","Искачући акценти: укљ","Αναδυόμενα τόνων: ναι" ,"Pop-ups de acento: ligados"} },
{ "Accent popups: off", { 0, "Accent-popups: uit","Akzent-Popups: aus","Fenêtres d'accents : désactivé","Ventanas de acentos: desactivado","Popup accenti: disattivi","Подсказки акцентов: выкл","Підказки акцентів: вимк","Изскачащи акценти: изкл","Искачући акценти: искљ","Αναδυόμενα τόνων: όχι" ,"Pop-ups de acento: desligados"} },
{ "Failed to save channel.", { 0, "Kan kanaal niet opslaan.","Kanal konnte nicht gespeichert werden.","Échec de l'enregistrement du canal.","Error al guardar el canal.","Impossibile salvare il canale.","Не удалось сохранить канал.","Не вдалося зберегти канал.","Каналът не можа да се запази.","Чување канала није успело.","Αποτυχία αποθήκευσης καναλιού." ,"Falha ao salvar o canal."} },
{ "Distance: km", { 0, "Afstand: km","Entfernung: km","Distance : km","Distancia: km","Distanza: km","Расстояние: км","Відстань: км","Разстояние: км","Растојање: km","Απόσταση: km" ,"Distância: km"} },
{ "Distance: miles", { 0, "Afstand: mijlen","Entfernung: Meilen","Distance : miles","Distancia: millas","Distanza: miglia","Расстояние: мили","Відстань: милі","Разстояние: мили","Растојање: миље","Απόσταση: μίλια" ,"Distância: milhas"} },
{ "Map tiles: microSD", { 0, "Kaarttegels: microSD","Kartenkacheln: microSD","Tuiles : microSD","Teselas: microSD","Riquadri: microSD","Тайлы карты: microSD","Плитки карти: microSD","Плочки: microSD","Плочице: microSD","Πλακίδια: microSD" ,"Tiles do mapa: microSD"} },
{ "Map tiles: server", { 0, "Kaarttegels: server","Kartenkacheln: Server","Tuiles : serveur","Teselas: servidor","Riquadri: server","Тайлы карты: сервер","Плитки карти: сервер","Плочки: сървър","Плочице: сервер","Πλακίδια: διακομιστής" ,"Tiles do mapa: servidor"} },
{ "Mark all chats and channels as read?", { 0, "Alle chats en kanalen als gelezen markeren?","Alle Chats und Kanäle als gelesen markieren?","Marquer tous les chats et canaux comme lus ?","¿Marcar todos los chats y canales como leídos?","Segnare tutte le chat e i canali come letti?","Отметить все чаты и каналы прочитанными?","Позначити всі чати й канали прочитаними?","Да отбележа всички чатове и канали като прочетени?","Означити сва ћаскања и канале као прочитане?","Σήμανση όλων ως αναγνωσμένα;" ,"Marcar todos os chats e canais como lidos?"} },
{ "Import this backup?\nReplaces identity,\nchannels & contacts,\nthen reboots.", { 0, "Deze back-up importeren?\nVervangt identiteit,\nkanalen & contacten,\nherstart daarna.","Dieses Backup importieren?\nErsetzt Identität,\nKanäle & Kontakte,\ndann Neustart.","Importer cette sauvegarde ?\nRemplace l'identité,\nles canaux & contacts,\npuis redémarre.","¿Importar esta copia?\nReemplaza identidad,\ncanales y contactos,\nluego reinicia.","Importare questo backup?\nSostituisce identità,\ncanali e contatti,\npoi riavvia.","Импортировать копию?\nЗаменит личность,\nканалы и контакты,\nзатем перезагрузка.","Імпортувати копію?\nЗамінить ідентичність,\nканали й контакти,\nпотім перезавантаження.","Да внеса този архив?\nЗаменя самоличност,\nканали и контакти,\nпосле рестартира.","Увези ову копију?\nЗамењује идентитет,\nканале и контакте,\nпа рестартује.","Εισαγωγή αντιγράφου;\nΑντικαθιστά ταυτότητα,\nκανάλια & επαφές,\nμετά επανεκκινεί." ,"Importar este backup?\nSubstitui identidade,\ncanais e contatos,\ndepois reinicia."} },
{ "Every node you talk to must match. You can change this later in Settings.", { 0, "Elke node waarmee je praat moet overeenkomen. Je kunt dit later wijzigen in Instellingen.","Jeder Knoten muss übereinstimmen. Sie können dies später in den Einstellungen ändern.","Chaque nœud doit correspondre. Modifiable plus tard dans Paramètres.","Cada nodo debe coincidir. Puedes cambiarlo luego en Ajustes.","Ogni nodo deve corrispondere. Modificabile dopo in Impostazioni.","Все узлы должны совпадать. Можно изменить позже в Настройках.","Усі вузли мають збігатися. Можна змінити пізніше в Налаштуваннях.","Всеки възел трябва да съвпада. Можете да промените по-късно в Настройки.","Сваки чвор мора да се поклапа. Касније у Подешавањима.","Κάθε κόμβος πρέπει να ταιριάζει. Αλλαγή αργότερα στις Ρυθμίσεις." ,"Todo nó com quem você fala deve coincidir. Você pode mudar isto depois em Ajustes."} },
{ "How you'll appear to other nodes. You can change this later in Settings.", { 0, "Hoe je verschijnt voor andere nodes. Je kunt dit later wijzigen in Instellingen.","Wie Sie für andere Knoten erscheinen. Später in den Einstellungen änderbar.","Comment vous apparaîtrez aux autres nœuds. Modifiable dans Paramètres.","Cómo aparecerás ante otros nodos. Puedes cambiarlo en Ajustes.","Come apparirai agli altri nodi. Modificabile in Impostazioni.","Как вы будете видны другим узлам. Можно изменить в Настройках.","Як вас бачитимуть інші вузли. Можна змінити в Налаштуваннях.","Как ще изглеждате за други възли. Промяна в Настройки.","Како ћете изгледати другим чворовима. У Подешавањима.","Πώς θα εμφανίζεστε σε άλλους κόμβους. Αλλαγή στις Ρυθμίσεις." ,"Como você aparecerá para outros nós. Você pode mudar isto depois em Ajustes."} },
{ "e.g. Family", { 0, "bijv. Familie","z. B. Familie","ex. Famille","p. ej. Familia","es. Famiglia","напр. Семья","напр. Сім'я","напр. Семейство","нпр. Породица","π.χ. Οικογένεια" ,"ex. Família"} },
{ "e.g. mesh", { 0, "bijv. mesh","z. B. mesh","ex. mesh","p. ej. mesh","es. mesh","напр. mesh","напр. mesh","напр. mesh","нпр. mesh","π.χ. mesh" ,"ex. mesh"} },
// ---- Sweep pass 8 (final): snprintf status / info lines ----
{ "GPS: fix", { 0, "GPS: fix","GPS: Fix","GPS : point","GPS: fijado","GPS: fix","GPS: есть","GPS: фікс","GPS: фикс","GPS: фикс","GPS: στίγμα" } },
{ "GPS: off", { 0, "GPS: uit","GPS: aus","GPS : désactivé","GPS: apagado","GPS: spento","GPS: выкл","GPS: вимк","GPS: изкл","GPS: искљ","GPS: ανενεργό" } },
{ "GPS: acquiring...", { 0, "GPS: zoeken...","GPS: suche...","GPS : acquisition...","GPS: buscando...","GPS: ricerca...","GPS: поиск...","GPS: пошук...","GPS: търсене...","GPS: тражим...","GPS: αναζήτηση..." } },
{ "GPS unset", { 0, "GPS niet ingesteld","GPS nicht gesetzt","GPS non défini","GPS sin definir","GPS non impostato","GPS не задан","GPS не задано","GPS не е зададен","GPS није постављен","GPS μη ορισμένο" } },
{ "(RTC unset)", { 0, "(RTC niet ingesteld)","(RTC nicht gesetzt)","(RTC non réglée)","(RTC sin ajustar)","(RTC non impostato)","(RTC не задан)","(RTC не задано)","(RTC не е зададен)","(RTC није постављен)","(RTC μη ορισμένο)" } },
{ "Screen timeout: %ds", { 0, "Schermtime-out: %ds","Bildschirm-Timeout: %ds","Délai d'écran : %ds","Tiempo de pantalla: %ds","Timeout schermo: %ds","Тайм-аут экрана: %dс","Тайм-аут екрана: %dс","Изключване на екрана: %dс","Истек екрана: %ds","Λήξη οθόνης: %ds" } },
{ "Screen timeout: never", { 0, "Schermtime-out: nooit","Bildschirm-Timeout: nie","Délai d'écran : jamais","Tiempo de pantalla: nunca","Timeout schermo: mai","Тайм-аут экрана: никогда","Тайм-аут екрана: ніколи","Изключване на екрана: никога","Истек екрана: никад","Λήξη οθόνης: ποτέ" } },
{ "Time offset: %+d h", { 0, "Tijdverschuiving: %+d u","Zeitversatz: %+d h","Décalage horaire : %+d h","Desfase horario: %+d h","Sfasamento orario: %+d h","Смещение времени: %+d ч","Зсув часу: %+d год","Изместване: %+d ч","Помак времена: %+d h","Μετατόπιση ώρας: %+d h" } },
{ "Time %s", { 0, "Tijd %s","Zeit %s","Heure %s","Hora %s","Ora %s","Время %s","Час %s","Време %s","Време %s","Ώρα %s" } },
{ "Pairing code: %06lu", { 0, "Koppelcode: %06lu","Kopplungscode: %06lu","Code d'appairage : %06lu","Código de emparejamiento: %06lu","Codice di accoppiamento: %06lu","Код сопряжения: %06lu","Код сполучення: %06lu","Код за сдвояване: %06lu","Код за упаривање: %06lu","Κωδικός σύζευξης: %06lu" } },
{ "Pairing code: n/a", { 0, "Koppelcode: n.v.t.","Kopplungscode: n. v.","Code d'appairage : n/d","Código de emparejamiento: n/d","Codice di accoppiamento: n/d","Код сопряжения: н/д","Код сполучення: н/д","Код за сдвояване: н/п","Код за упаривање: н/д","Κωδικός σύζευξης: μ/δ" } },
{ "Path hash: %u byte%s", { 0, "Pad-hash: %u byte%s","Pfad-Hash: %u Byte%s","Hash de chemin : %u octet%s","Hash de ruta: %u byte%s","Hash percorso: %u byte%s","Хеш пути: %u байт%s","Хеш шляху: %u байт%s","Хеш на пътя: %u байт%s","Хеш путање: %u бајт%s","Hash διαδρομής: %u byte%s" } },
{ "Calibrated: 100%% = %u.%02u V (long-press to reset)", { 0, "Gekalibreerd: 100%% = %u.%02u V (lang indrukken om te resetten)","Kalibriert: 100%% = %u.%02u V (lang drücken zum Zurücksetzen)","Étalonné : 100%% = %u.%02u V (appui long pour réinitialiser)","Calibrado: 100%% = %u.%02u V (mantén para restablecer)","Calibrato: 100%% = %u.%02u V (tieni premuto per ripristinare)","Калибровано: 100%% = %u.%02u В (долгое нажатие — сброс)","Калібровано: 100%% = %u.%02u В (довге натискання — скидання)","Калибрирано: 100%% = %u.%02u V (задръжте за нулиране)","Калибрисано: 100%% = %u.%02u V (дуго за ресет)","Βαθμονομήθηκε: 100%% = %u.%02u V (παρατεταμένο για επαναφορά)" } },
{ "Centered on %s", { 0, "Gecentreerd op %s","Zentriert auf %s","Centré sur %s","Centrado en %s","Centrato su %s","Центрировано на %s","Центровано на %s","Центрирано на %s","Центрирано на %s","Κεντραρισμένο σε %s" } },
{ "Copied %s", { 0, "%s gekopieerd","%s kopiert","%s copié","%s copiado","%s copiato","%s скопировано","%s скопійовано","%s копирано","%s копирано","Αντιγράφηκε %s" } },
{ "No reply from %s", { 0, "Geen antwoord van %s","Keine Antwort von %s","Pas de réponse de %s","Sin respuesta de %s","Nessuna risposta da %s","Нет ответа от %s","Немає відповіді від %s","Няма отговор от %s","Нема одговора од %s","Καμία απάντηση από %s" } },
{ "Joined %.40s", { 0, "Deelgenomen aan %.40s","%.40s beigetreten","Rejoint %.40s","Unido a %.40s","Unito a %.40s","Вы вошли в %.40s","Приєднано до %.40s","Присъединено към %.40s","Придружено %.40s","Συμμετοχή σε %.40s" } },
{ "Login OK (perms %u)", { 0, "Inloggen OK (rechten %u)","Anmeldung OK (Rechte %u)","Connexion OK (perms %u)","Sesión OK (perms %u)","Accesso OK (permessi %u)","Вход OK (права %u)","Вхід OK (права %u)","Вход OK (права %u)","Пријава OK (дозволе %u)","Σύνδεση OK (δικαι. %u)" } },
{ "Saved to slot %d", { 0, "Opgeslagen in slot %d","In Slot %d gespeichert","Enregistré dans l'emplacement %d","Guardado en ranura %d","Salvato nello slot %d","Сохранено в слот %d","Збережено в слот %d","Запазено в слот %d","Сачувано у слот %d","Αποθηκεύτηκε στη θέση %d" } },
{ "Slot %d cleared", { 0, "Slot %d gewist","Slot %d gelöscht","Emplacement %d effacé","Ranura %d borrada","Slot %d cancellato","Слот %d очищен","Слот %d очищено","Слот %d изчистен","Слот %d обрисан","Η θέση %d εκκαθαρίστηκε" } },
{ "now on channel %s", { 0, "nu op kanaal %s","jetzt auf Kanal %s","maintenant sur le canal %s","ahora en el canal %s","ora sul canale %s","теперь в канале %s","тепер у каналі %s","сега в канал %s","сада на каналу %s","τώρα στο κανάλι %s" } },
{ "now talking to %s", { 0, "praat nu met %s","spricht jetzt mit %s","conversation avec %s","ahora hablando con %s","ora in chat con %s","теперь общаетесь с %s","тепер спілкуєтесь з %s","сега говорите с %s","сада причате са %s","τώρα μιλάτε με %s" } },
{ "Delete chat with \"%s\"?\nMessage history will be cleared.", { 0, "Chat met \"%s\" verwijderen?\nBerichtgeschiedenis wordt gewist.","Chat mit \"%s\" löschen?\nNachrichtenverlauf wird gelöscht.","Supprimer la discussion avec \"%s\" ?\nL'historique sera effacé.","¿Eliminar el chat con \"%s\"?\nSe borrará el historial.","Eliminare la chat con \"%s\"?\nLa cronologia sarà cancellata.","Удалить чат с \"%s\"?\nИстория будет очищена.","Видалити чат із \"%s\"?\nІсторію буде очищено.","Изтриване на чата с \"%s\"?\nИсторията ще се изчисти.","Обрисати ћаскање са \"%s\"?\nИсторија ће бити обрисана.","Διαγραφή συνομιλίας με \"%s\";\nΤο ιστορικό θα διαγραφεί." } },
{ "Remove channel \"%s\"?\nLeaves it on this device only.", { 0, "Kanaal \"%s\" verwijderen?\nAlleen van dit apparaat.","Kanal \"%s\" entfernen?\nNur von diesem Gerät.","Retirer le canal \"%s\" ?\nUniquement de cet appareil.","¿Quitar el canal \"%s\"?\nSolo de este dispositivo.","Rimuovere il canale \"%s\"?\nSolo da questo dispositivo.","Удалить канал \"%s\"?\nТолько с этого устройства.","Видалити канал \"%s\"?\nЛише з цього пристрою.","Премахване на канал \"%s\"?\nСамо от това устройство.","Уклонити канал \"%s\"?\nСамо са овог уређаја.","Αφαίρεση καναλιού \"%s\";\nΜόνο από αυτή τη συσκευή." } },
{ "Firmware beta_%d\nCouldn't reach the update server", { 0, "Firmware beta_%d\nKon de updateserver niet bereiken","Firmware beta_%d\nUpdate-Server nicht erreichbar","Firmware beta_%d\nServeur de mise à jour injoignable","Firmware beta_%d\nNo se pudo contactar el servidor","Firmware beta_%d\nServer di aggiornamento irraggiungibile","Прошивка beta_%d\nСервер обновлений недоступен","Прошивка beta_%d\nСервер оновлень недоступний","Фърмуер beta_%d\nСървърът за обновяване е недостъпен","Фирмвер beta_%d\nСервер ажурирања недоступан","Firmware beta_%d\nΑδυναμία πρόσβασης στον διακομιστή" } },
{ "Internal storage %s / %s", { 0, "Interne opslag %s / %s","Interner Speicher %s / %s","Stockage interne %s / %s","Almacen. interno %s / %s","Archiviazione interna %s / %s","Внутр. память %s / %s","Внутр. пам'ять %s / %s","Вътрешна памет %s / %s","Интерна меморија %s / %s","Εσωτ. χώρος %s / %s" } },
{ "SD card %s (hold: format)", { 0, "SD-kaart %s (vasthouden: formatteren)","SD-Karte %s (halten: formatieren)","Carte SD %s (maintenir : formater)","Tarjeta SD %s (mantener: formatear)","Scheda SD %s (tieni: formatta)","SD-карта %s (удерж.: формат.)","SD-картка %s (утрим.: формат.)","SD карта %s (задръж: формат)","SD картица %s (држи: формат)","Κάρτα SD %s (κράτημα: διαμόρφωση)" } },
{ "SD formatted - %s (MESHCOMOD)", { 0, "SD geformatteerd - %s (MESHCOMOD)","SD formatiert - %s (MESHCOMOD)","SD formatée - %s (MESHCOMOD)","SD formateada - %s (MESHCOMOD)","SD formattata - %s (MESHCOMOD)","SD отформатирована - %s (MESHCOMOD)","SD відформатовано - %s (MESHCOMOD)","SD форматирана - %s (MESHCOMOD)","SD форматирана - %s (MESHCOMOD)","SD διαμορφώθηκε - %s (MESHCOMOD)" } },
{ "SPIFFS %u/%u KB used", { 0, "SPIFFS %u/%u KB gebruikt","SPIFFS %u/%u KB belegt","SPIFFS %u/%u Ko utilisés","SPIFFS %u/%u KB usados","SPIFFS %u/%u KB usati","SPIFFS %u/%u КБ занято","SPIFFS %u/%u КБ використано","SPIFFS %u/%u KB използвани","SPIFFS %u/%u KB заузето","SPIFFS %u/%u KB σε χρήση" } },
{ "contacts (%d):", { 0, "contacten (%d):","Kontakte (%d):","contacts (%d) :","contactos (%d):","contatti (%d):","контакты (%d):","контакти (%d):","контакти (%d):","контакти (%d):","επαφές (%d):" } },
{ "%d on map", { 0, "%d op kaart","%d auf Karte","%d sur la carte","%d en el mapa","%d sulla mappa","%d на карте","%d на карті","%d на картата","%d на мапи","%d στον χάρτη" } },
{ "Data -> SD card on reboot\n(card must be inserted)", { 0, "Gegevens -> SD-kaart bij herstart\n(kaart moet geplaatst zijn)","Daten -> SD-Karte bei Neustart\n(Karte muss eingelegt sein)","Données -> carte SD au redémarrage\n(carte requise)","Datos -> tarjeta SD al reiniciar\n(tarjeta requerida)","Dati -> scheda SD al riavvio\n(scheda richiesta)","Данные -> SD-карта при перезагрузке\n(нужна карта)","Дані -> SD-картка при перезавантаженні\n(потрібна картка)","Данни -> SD карта при рестарт\n(нужна е карта)","Подаци -> SD картица при рестарту\n(потребна картица)","Δεδομένα -> κάρτα SD στην επανεκκίνηση\n(απαιτείται κάρτα)" } },
{ "Data -> internal on reboot", { 0, "Gegevens -> intern bij herstart","Daten -> intern bei Neustart","Données -> interne au redémarrage","Datos -> interno al reiniciar","Dati -> interno al riavvio","Данные -> внутр. при перезагрузке","Дані -> внутр. при перезавантаженні","Данни -> вътрешна при рестарт","Подаци -> интерно при рестарту","Δεδομένα -> εσωτερικά στην επανεκκίνηση" } },
{ "RangeTest sent (direct)", { 0, "RangeTest verzonden (direct)","RangeTest gesendet (direkt)","RangeTest envoyé (direct)","RangeTest enviado (directo)","RangeTest inviato (diretto)","RangeTest отправлен (прямой)","RangeTest надіслано (прямий)","RangeTest изпратен (директен)","RangeTest послат (директно)","Στάλθηκε RangeTest (άμεσο)" } },
{ "RangeTest sent (flood)", { 0, "RangeTest verzonden (flood)","RangeTest gesendet (Flood)","RangeTest envoyé (flood)","RangeTest enviado (flood)","RangeTest inviato (flood)","RangeTest отправлен (flood)","RangeTest надіслано (flood)","RangeTest изпратен (flood)","RangeTest послат (flood)","Στάλθηκε RangeTest (flood)" } },
{ "Telemetry req (direct)…", { 0, "Telemetrie-verzoek (direct)…","Telemetrie-Anfrage (direkt)…","Requête télémétrie (direct)…","Solicitud telemetría (directo)…","Richiesta telemetria (diretto)…","Запрос телеметрии (прямой)…","Запит телеметрії (прямий)…","Заявка за телеметрия (директен)…","Захтев телеметрије (директно)…","Αίτημα τηλεμετρίας (άμεσο)…" } },
{ "Telemetry req (flood)…", { 0, "Telemetrie-verzoek (flood)…","Telemetrie-Anfrage (Flood)…","Requête télémétrie (flood)…","Solicitud telemetría (flood)…","Richiesta telemetria (flood)…","Запрос телеметрии (flood)…","Запит телеметрії (flood)…","Заявка за телеметрия (flood)…","Захтев телеметрије (flood)…","Αίτημα τηλεμετρίας (flood)…" } },
{ "Pinged (direct) — waiting…", { 0, "Ping (direct) — wachten…","Ping (direkt) — warte…","Ping (direct) — attente…","Ping (directo) — esperando…","Ping (diretto) — attesa…","Ping (прямой) — ожидание…","Ping (прямий) — очікування…","Ping (директен) — изчакване…","Ping (директно) — чекање…","Ping (άμεσο) — αναμονή…" } },
{ "Pinged (flood) — waiting…", { 0, "Ping (flood) — wachten…","Ping (Flood) — warte…","Ping (flood) — attente…","Ping (flood) — esperando…","Ping (flood) — attesa…","Ping (flood) — ожидание…","Ping (flood) — очікування…","Ping (flood) — изчакване…","Ping (flood) — чекање…","Ping (flood) — αναμονή…" } },
{ "Unread %d", { 0, "Ongelezen %d","Ungelesen %d","Non lus %d","Sin leer %d","Non letti %d","Непроч. %d","Непроч. %d","Непроч. %d","Непроч. %d","Μη αναγν. %d" } },
{ "Unread %d\nRAM %u%% · PSRAM %u%%", { 0, "Ongelezen %d\nRAM %u%% · PSRAM %u%%","Ungelesen %d\nRAM %u%% · PSRAM %u%%","Non lus %d\nRAM %u%% · PSRAM %u%%","Sin leer %d\nRAM %u%% · PSRAM %u%%","Non letti %d\nRAM %u%% · PSRAM %u%%","Непроч. %d\nRAM %u%% · PSRAM %u%%","Непроч. %d\nRAM %u%% · PSRAM %u%%","Непроч. %d\nRAM %u%% · PSRAM %u%%","Непроч. %d\nRAM %u%% · PSRAM %u%%","Μη αναγν. %d\nRAM %u%% · PSRAM %u%%" } },
{ "Analyzing terrain…\n(retry %d of 3)", { 0, "Terrein analyseren…\n(poging %d van 3)","Gelände wird analysiert…\n(Versuch %d von 3)","Analyse du terrain…\n(essai %d sur 3)","Analizando terreno…\n(intento %d de 3)","Analisi del terreno…\n(tentativo %d di 3)","Анализ рельефа…\n(попытка %d из 3)","Аналіз рельєфу…\n(спроба %d з 3)","Анализ на терена…\n(опит %d от 3)","Анализа терена…\n(покушај %d од 3)","Ανάλυση εδάφους…\n(προσπάθεια %d από 3)" } },
{ "GPS: fix", { 0, "GPS: fix","GPS: Fix","GPS : point","GPS: fijado","GPS: fix","GPS: есть","GPS: фікс","GPS: фикс","GPS: фикс","GPS: στίγμα" ,"GPS: fixado"} },
{ "GPS: off", { 0, "GPS: uit","GPS: aus","GPS : désactivé","GPS: apagado","GPS: spento","GPS: выкл","GPS: вимк","GPS: изкл","GPS: искљ","GPS: ανενεργό" ,"GPS: desligado"} },
{ "GPS: acquiring...", { 0, "GPS: zoeken...","GPS: suche...","GPS : acquisition...","GPS: buscando...","GPS: ricerca...","GPS: поиск...","GPS: пошук...","GPS: търсене...","GPS: тражим...","GPS: αναζήτηση..." ,"GPS: adquirindo..."} },
{ "GPS unset", { 0, "GPS niet ingesteld","GPS nicht gesetzt","GPS non défini","GPS sin definir","GPS non impostato","GPS не задан","GPS не задано","GPS не е зададен","GPS није постављен","GPS μη ορισμένο" ,"GPS não definido"} },
{ "(RTC unset)", { 0, "(RTC niet ingesteld)","(RTC nicht gesetzt)","(RTC non réglée)","(RTC sin ajustar)","(RTC non impostato)","(RTC не задан)","(RTC не задано)","(RTC не е зададен)","(RTC није постављен)","(RTC μη ορισμένο)" ,"(RTC não definido)"} },
{ "Screen timeout: %ds", { 0, "Schermtime-out: %ds","Bildschirm-Timeout: %ds","Délai d'écran : %ds","Tiempo de pantalla: %ds","Timeout schermo: %ds","Тайм-аут экрана: %dс","Тайм-аут екрана: %dс","Изключване на екрана: %dс","Истек екрана: %ds","Λήξη οθόνης: %ds" ,"Tempo de tela: %ds"} },
{ "Screen timeout: never", { 0, "Schermtime-out: nooit","Bildschirm-Timeout: nie","Délai d'écran : jamais","Tiempo de pantalla: nunca","Timeout schermo: mai","Тайм-аут экрана: никогда","Тайм-аут екрана: ніколи","Изключване на екрана: никога","Истек екрана: никад","Λήξη οθόνης: ποτέ" ,"Tempo de tela: nunca"} },
{ "Time offset: %+d h", { 0, "Tijdverschuiving: %+d u","Zeitversatz: %+d h","Décalage horaire : %+d h","Desfase horario: %+d h","Sfasamento orario: %+d h","Смещение времени: %+d ч","Зсув часу: %+d год","Изместване: %+d ч","Помак времена: %+d h","Μετατόπιση ώρας: %+d h" ,"Fuso horário: %+d h"} },
{ "Time %s", { 0, "Tijd %s","Zeit %s","Heure %s","Hora %s","Ora %s","Время %s","Час %s","Време %s","Време %s","Ώρα %s" ,"Hora %s"} },
{ "Pairing code: %06lu", { 0, "Koppelcode: %06lu","Kopplungscode: %06lu","Code d'appairage : %06lu","Código de emparejamiento: %06lu","Codice di accoppiamento: %06lu","Код сопряжения: %06lu","Код сполучення: %06lu","Код за сдвояване: %06lu","Код за упаривање: %06lu","Κωδικός σύζευξης: %06lu" ,"Código de pareamento: %06lu"} },
{ "Pairing code: n/a", { 0, "Koppelcode: n.v.t.","Kopplungscode: n. v.","Code d'appairage : n/d","Código de emparejamiento: n/d","Codice di accoppiamento: n/d","Код сопряжения: н/д","Код сполучення: н/д","Код за сдвояване: н/п","Код за упаривање: н/д","Κωδικός σύζευξης: μ/δ" ,"Código de pareamento: n/d"} },
{ "Path hash: %u byte%s", { 0, "Pad-hash: %u byte%s","Pfad-Hash: %u Byte%s","Hash de chemin : %u octet%s","Hash de ruta: %u byte%s","Hash percorso: %u byte%s","Хеш пути: %u байт%s","Хеш шляху: %u байт%s","Хеш на пътя: %u байт%s","Хеш путање: %u бајт%s","Hash διαδρομής: %u byte%s" ,"Hash de rota: %u byte%s"} },
{ "Calibrated: 100%% = %u.%02u V (long-press to reset)", { 0, "Gekalibreerd: 100%% = %u.%02u V (lang indrukken om te resetten)","Kalibriert: 100%% = %u.%02u V (lang drücken zum Zurücksetzen)","Étalonné : 100%% = %u.%02u V (appui long pour réinitialiser)","Calibrado: 100%% = %u.%02u V (mantén para restablecer)","Calibrato: 100%% = %u.%02u V (tieni premuto per ripristinare)","Калибровано: 100%% = %u.%02u В (долгое нажатие — сброс)","Калібровано: 100%% = %u.%02u В (довге натискання — скидання)","Калибрирано: 100%% = %u.%02u V (задръжте за нулиране)","Калибрисано: 100%% = %u.%02u V (дуго за ресет)","Βαθμονομήθηκε: 100%% = %u.%02u V (παρατεταμένο για επαναφορά)" ,"Calibrado: 100%% = %u.%02u V (segure para redefinir)"} },
{ "Centered on %s", { 0, "Gecentreerd op %s","Zentriert auf %s","Centré sur %s","Centrado en %s","Centrato su %s","Центрировано на %s","Центровано на %s","Центрирано на %s","Центрирано на %s","Κεντραρισμένο σε %s" ,"Centralizado em %s"} },
{ "Copied %s", { 0, "%s gekopieerd","%s kopiert","%s copié","%s copiado","%s copiato","%s скопировано","%s скопійовано","%s копирано","%s копирано","Αντιγράφηκε %s" ,"%s copiado"} },
{ "No reply from %s", { 0, "Geen antwoord van %s","Keine Antwort von %s","Pas de réponse de %s","Sin respuesta de %s","Nessuna risposta da %s","Нет ответа от %s","Немає відповіді від %s","Няма отговор от %s","Нема одговора од %s","Καμία απάντηση από %s" ,"Sem resposta de %s"} },
{ "Joined %.40s", { 0, "Deelgenomen aan %.40s","%.40s beigetreten","Rejoint %.40s","Unido a %.40s","Unito a %.40s","Вы вошли в %.40s","Приєднано до %.40s","Присъединено към %.40s","Придружено %.40s","Συμμετοχή σε %.40s" ,"Entrou em %.40s"} },
{ "Login OK (perms %u)", { 0, "Inloggen OK (rechten %u)","Anmeldung OK (Rechte %u)","Connexion OK (perms %u)","Sesión OK (perms %u)","Accesso OK (permessi %u)","Вход OK (права %u)","Вхід OK (права %u)","Вход OK (права %u)","Пријава OK (дозволе %u)","Σύνδεση OK (δικαι. %u)" ,"Login OK (perms %u)"} },
{ "Saved to slot %d", { 0, "Opgeslagen in slot %d","In Slot %d gespeichert","Enregistré dans l'emplacement %d","Guardado en ranura %d","Salvato nello slot %d","Сохранено в слот %d","Збережено в слот %d","Запазено в слот %d","Сачувано у слот %d","Αποθηκεύτηκε στη θέση %d" ,"Salvo no slot %d"} },
{ "Slot %d cleared", { 0, "Slot %d gewist","Slot %d gelöscht","Emplacement %d effacé","Ranura %d borrada","Slot %d cancellato","Слот %d очищен","Слот %d очищено","Слот %d изчистен","Слот %d обрисан","Η θέση %d εκκαθαρίστηκε" ,"Slot %d limpo"} },
{ "now on channel %s", { 0, "nu op kanaal %s","jetzt auf Kanal %s","maintenant sur le canal %s","ahora en el canal %s","ora sul canale %s","теперь в канале %s","тепер у каналі %s","сега в канал %s","сада на каналу %s","τώρα στο κανάλι %s" ,"agora no canal %s"} },
{ "now talking to %s", { 0, "praat nu met %s","spricht jetzt mit %s","conversation avec %s","ahora hablando con %s","ora in chat con %s","теперь общаетесь с %s","тепер спілкуєтесь з %s","сега говорите с %s","сада причате са %s","τώρα μιλάτε με %s" ,"agora falando com %s"} },
{ "Delete chat with \"%s\"?\nMessage history will be cleared.", { 0, "Chat met \"%s\" verwijderen?\nBerichtgeschiedenis wordt gewist.","Chat mit \"%s\" löschen?\nNachrichtenverlauf wird gelöscht.","Supprimer la discussion avec \"%s\" ?\nL'historique sera effacé.","¿Eliminar el chat con \"%s\"?\nSe borrará el historial.","Eliminare la chat con \"%s\"?\nLa cronologia sarà cancellata.","Удалить чат с \"%s\"?\nИстория будет очищена.","Видалити чат із \"%s\"?\nІсторію буде очищено.","Изтриване на чата с \"%s\"?\nИсторията ще се изчисти.","Обрисати ћаскање са \"%s\"?\nИсторија ће бити обрисана.","Διαγραφή συνομιλίας με \"%s\";\nΤο ιστορικό θα διαγραφεί." ,"Excluir chat com \"%s\"?\nO histórico de mensagens será apagado."} },
{ "Remove channel \"%s\"?\nLeaves it on this device only.", { 0, "Kanaal \"%s\" verwijderen?\nAlleen van dit apparaat.","Kanal \"%s\" entfernen?\nNur von diesem Gerät.","Retirer le canal \"%s\" ?\nUniquement de cet appareil.","¿Quitar el canal \"%s\"?\nSolo de este dispositivo.","Rimuovere il canale \"%s\"?\nSolo da questo dispositivo.","Удалить канал \"%s\"?\nТолько с этого устройства.","Видалити канал \"%s\"?\nЛише з цього пристрою.","Премахване на канал \"%s\"?\nСамо от това устройство.","Уклонити канал \"%s\"?\nСамо са овог уређаја.","Αφαίρεση καναλιού \"%s\";\nΜόνο από αυτή τη συσκευή." ,"Remover canal \"%s\"?\nDeixa-o só neste aparelho."} },
{ "Firmware beta_%d\nCouldn't reach the update server", { 0, "Firmware beta_%d\nKon de updateserver niet bereiken","Firmware beta_%d\nUpdate-Server nicht erreichbar","Firmware beta_%d\nServeur de mise à jour injoignable","Firmware beta_%d\nNo se pudo contactar el servidor","Firmware beta_%d\nServer di aggiornamento irraggiungibile","Прошивка beta_%d\nСервер обновлений недоступен","Прошивка beta_%d\nСервер оновлень недоступний","Фърмуер beta_%d\nСървърът за обновяване е недостъпен","Фирмвер beta_%d\nСервер ажурирања недоступан","Firmware beta_%d\nΑδυναμία πρόσβασης στον διακομιστή" ,"Firmware beta_%d\nNão foi possível acessar o servidor de atualização"} },
{ "Internal storage %s / %s", { 0, "Interne opslag %s / %s","Interner Speicher %s / %s","Stockage interne %s / %s","Almacen. interno %s / %s","Archiviazione interna %s / %s","Внутр. память %s / %s","Внутр. пам'ять %s / %s","Вътрешна памет %s / %s","Интерна меморија %s / %s","Εσωτ. χώρος %s / %s" ,"Armazenamento interno %s / %s"} },
{ "SD card %s (hold: format)", { 0, "SD-kaart %s (vasthouden: formatteren)","SD-Karte %s (halten: formatieren)","Carte SD %s (maintenir : formater)","Tarjeta SD %s (mantener: formatear)","Scheda SD %s (tieni: formatta)","SD-карта %s (удерж.: формат.)","SD-картка %s (утрим.: формат.)","SD карта %s (задръж: формат)","SD картица %s (држи: формат)","Κάρτα SD %s (κράτημα: διαμόρφωση)" ,"Cartão SD %s (segure: formatar)"} },
{ "SD formatted - %s (MESHCOMOD)", { 0, "SD geformatteerd - %s (MESHCOMOD)","SD formatiert - %s (MESHCOMOD)","SD formatée - %s (MESHCOMOD)","SD formateada - %s (MESHCOMOD)","SD formattata - %s (MESHCOMOD)","SD отформатирована - %s (MESHCOMOD)","SD відформатовано - %s (MESHCOMOD)","SD форматирана - %s (MESHCOMOD)","SD форматирана - %s (MESHCOMOD)","SD διαμορφώθηκε - %s (MESHCOMOD)" ,"SD formatado - %s (MESHCOMOD)"} },
{ "SPIFFS %u/%u KB used", { 0, "SPIFFS %u/%u KB gebruikt","SPIFFS %u/%u KB belegt","SPIFFS %u/%u Ko utilisés","SPIFFS %u/%u KB usados","SPIFFS %u/%u KB usati","SPIFFS %u/%u КБ занято","SPIFFS %u/%u КБ використано","SPIFFS %u/%u KB използвани","SPIFFS %u/%u KB заузето","SPIFFS %u/%u KB σε χρήση" ,"SPIFFS %u/%u KB usados"} },
{ "contacts (%d):", { 0, "contacten (%d):","Kontakte (%d):","contacts (%d) :","contactos (%d):","contatti (%d):","контакты (%d):","контакти (%d):","контакти (%d):","контакти (%d):","επαφές (%d):" ,"contatos (%d):"} },
{ "%d on map", { 0, "%d op kaart","%d auf Karte","%d sur la carte","%d en el mapa","%d sulla mappa","%d на карте","%d на карті","%d на картата","%d на мапи","%d στον χάρτη" ,"%d no mapa"} },
{ "Data -> SD card on reboot\n(card must be inserted)", { 0, "Gegevens -> SD-kaart bij herstart\n(kaart moet geplaatst zijn)","Daten -> SD-Karte bei Neustart\n(Karte muss eingelegt sein)","Données -> carte SD au redémarrage\n(carte requise)","Datos -> tarjeta SD al reiniciar\n(tarjeta requerida)","Dati -> scheda SD al riavvio\n(scheda richiesta)","Данные -> SD-карта при перезагрузке\n(нужна карта)","Дані -> SD-картка при перезавантаженні\n(потрібна картка)","Данни -> SD карта при рестарт\n(нужна е карта)","Подаци -> SD картица при рестарту\n(потребна картица)","Δεδομένα -> κάρτα SD στην επανεκκίνηση\n(απαιτείται κάρτα)" ,"Dados -> cartão SD ao reiniciar\n(cartão deve estar inserido)"} },
{ "Data -> internal on reboot", { 0, "Gegevens -> intern bij herstart","Daten -> intern bei Neustart","Données -> interne au redémarrage","Datos -> interno al reiniciar","Dati -> interno al riavvio","Данные -> внутр. при перезагрузке","Дані -> внутр. при перезавантаженні","Данни -> вътрешна при рестарт","Подаци -> интерно при рестарту","Δεδομένα -> εσωτερικά στην επανεκκίνηση" ,"Dados -> interno ao reiniciar"} },
{ "RangeTest sent (direct)", { 0, "RangeTest verzonden (direct)","RangeTest gesendet (direkt)","RangeTest envoyé (direct)","RangeTest enviado (directo)","RangeTest inviato (diretto)","RangeTest отправлен (прямой)","RangeTest надіслано (прямий)","RangeTest изпратен (директен)","RangeTest послат (директно)","Στάλθηκε RangeTest (άμεσο)" ,"RangeTest enviado (direto)"} },
{ "RangeTest sent (flood)", { 0, "RangeTest verzonden (flood)","RangeTest gesendet (Flood)","RangeTest envoyé (flood)","RangeTest enviado (flood)","RangeTest inviato (flood)","RangeTest отправлен (flood)","RangeTest надіслано (flood)","RangeTest изпратен (flood)","RangeTest послат (flood)","Στάλθηκε RangeTest (flood)" ,"RangeTest enviado (flood)"} },
{ "Telemetry req (direct)…", { 0, "Telemetrie-verzoek (direct)…","Telemetrie-Anfrage (direkt)…","Requête télémétrie (direct)…","Solicitud telemetría (directo)…","Richiesta telemetria (diretto)…","Запрос телеметрии (прямой)…","Запит телеметрії (прямий)…","Заявка за телеметрия (директен)…","Захтев телеметрије (директно)…","Αίτημα τηλεμετρίας (άμεσο)…" ,"Req. de telemetria (direto)…"} },
{ "Telemetry req (flood)…", { 0, "Telemetrie-verzoek (flood)…","Telemetrie-Anfrage (Flood)…","Requête télémétrie (flood)…","Solicitud telemetría (flood)…","Richiesta telemetria (flood)…","Запрос телеметрии (flood)…","Запит телеметрії (flood)…","Заявка за телеметрия (flood)…","Захтев телеметрије (flood)…","Αίτημα τηλεμετρίας (flood)…" ,"Req. de telemetria (flood)…"} },
{ "Pinged (direct) — waiting…", { 0, "Ping (direct) — wachten…","Ping (direkt) — warte…","Ping (direct) — attente…","Ping (directo) — esperando…","Ping (diretto) — attesa…","Ping (прямой) — ожидание…","Ping (прямий) — очікування…","Ping (директен) — изчакване…","Ping (директно) — чекање…","Ping (άμεσο) — αναμονή…" ,"Ping (direto) — aguardando…"} },
{ "Pinged (flood) — waiting…", { 0, "Ping (flood) — wachten…","Ping (Flood) — warte…","Ping (flood) — attente…","Ping (flood) — esperando…","Ping (flood) — attesa…","Ping (flood) — ожидание…","Ping (flood) — очікування…","Ping (flood) — изчакване…","Ping (flood) — чекање…","Ping (flood) — αναμονή…" ,"Ping (flood) — aguardando…"} },
{ "Unread %d", { 0, "Ongelezen %d","Ungelesen %d","Non lus %d","Sin leer %d","Non letti %d","Непроч. %d","Непроч. %d","Непроч. %d","Непроч. %d","Μη αναγν. %d" ,"Não lidas %d"} },
{ "Unread %d\nRAM %u%% · PSRAM %u%%", { 0, "Ongelezen %d\nRAM %u%% · PSRAM %u%%","Ungelesen %d\nRAM %u%% · PSRAM %u%%","Non lus %d\nRAM %u%% · PSRAM %u%%","Sin leer %d\nRAM %u%% · PSRAM %u%%","Non letti %d\nRAM %u%% · PSRAM %u%%","Непроч. %d\nRAM %u%% · PSRAM %u%%","Непроч. %d\nRAM %u%% · PSRAM %u%%","Непроч. %d\nRAM %u%% · PSRAM %u%%","Непроч. %d\nRAM %u%% · PSRAM %u%%","Μη αναγν. %d\nRAM %u%% · PSRAM %u%%" ,"Não lidas %d\nRAM %u%% · PSRAM %u%%"} },
{ "Analyzing terrain…\n(retry %d of 3)", { 0, "Terrein analyseren…\n(poging %d van 3)","Gelände wird analysiert…\n(Versuch %d von 3)","Analyse du terrain…\n(essai %d sur 3)","Analizando terreno…\n(intento %d de 3)","Analisi del terreno…\n(tentativo %d di 3)","Анализ рельефа…\n(попытка %d из 3)","Аналіз рельєфу…\n(спроба %d з 3)","Анализ на терена…\n(опит %d от 3)","Анализа терена…\n(покушај %d од 3)","Ανάλυση εδάφους…\n(προσπάθεια %d από 3)" ,"Analisando o terreno…\n(tentativa %d de 3)"} },
// ---- Backups page (Settings -> Backups) ----
{ "Backups", { 0, "Back-ups","Backups","Sauvegardes","Copias","Backup","Резервные копии","Резервні копії","Резервни копия","Резервне копије","Αντίγραφα" } },
{ "Saved backups", { 0, "Opgeslagen back-ups","Gespeicherte Backups","Sauvegardes enregistrées","Copias guardadas","Backup salvati","Сохранённые копии","Збережені копії","Запазени копия","Сачуване копије","Αποθηκευμένα αντίγραφα" } },
{ "Export new backup", { 0, "Nieuwe back-up","Neues Backup","Nouvelle sauvegarde","Nueva copia","Nuovo backup","Новая копия","Нова копія","Ново копие","Нова копија","Νέο αντίγραφο" } },
{ "Danger zone", { 0, "Gevarenzone","Gefahrenzone","Zone de danger","Zona de peligro","Zona pericolosa","Опасная зона","Небезпечна зона","Опасна зона","Опасна зона","Ζώνη κινδύνου" } },
{ "Factory reset", { 0, "Fabrieksreset","Werksreset","Réinit. d'usine","Rest. de fábrica","Ripristino fabbrica","Сброс настроек","Скидання налашт.","Фабрични настройки","Фабричко ресет.","Επαναφορά εργοστ." } },
{ "Erase all", { 0, "Alles wissen","Alles löschen","Tout effacer","Borrar todo","Cancella tutto","Стереть всё","Стерти все","Изтрий всичко","Обриши све","Διαγραφή όλων" } },
{ "Erasing everything…", { 0, "Alles wissen…","Alles wird gelöscht…","Effacement total…","Borrando todo…","Cancellazione…","Стирание всего…","Стирання всього…","Изтриване на всичко…","Брисање свега…","Διαγραφή όλων…" } },
{ "Delete failed", { 0, "Verwijderen mislukt","Löschen fehlgeschlagen","Échec suppression","Error al borrar","Eliminazione fallita","Не удалось удалить","Не вдалося видалити","Грешка при изтриване","Брисање није успело","Αποτυχία διαγραφής" } },
{ "No backups yet. Tap Export new backup above to create one.", { 0, "Nog geen back-ups. Tik hierboven op Nieuwe back-up om er een te maken.","Noch keine Backups. Tippe oben auf Neues Backup, um eines zu erstellen.","Aucune sauvegarde. Touchez Nouvelle sauvegarde ci-dessus pour en créer une.","Sin copias aún. Toca Nueva copia arriba para crear una.","Nessun backup. Tocca Nuovo backup sopra per crearne uno.","Копий пока нет. Нажмите «Новая копия» выше, чтобы создать.","Копій ще немає. Натисніть «Нова копія» вгорі, щоб створити.","Все още няма копия. Натиснете «Ново копие» горе, за да създадете.","Још нема копија. Додирните «Нова копија» изнад да направите.","Δεν υπάρχουν αντίγραφα. Πατήστε «Νέο αντίγραφο» πάνω για δημιουργία." } },
{ "Erases identity, contacts, channels, messages, Wi-Fi and all settings, then reboots.", { 0, "Wist identiteit, contacten, kanalen, berichten, wifi en alle instellingen, en herstart dan.","Löscht Identität, Kontakte, Kanäle, Nachrichten, WLAN und alle Einstellungen, dann Neustart.","Efface identité, contacts, canaux, messages, Wi-Fi et tous les réglages, puis redémarre.","Borra identidad, contactos, canales, mensajes, Wi-Fi y todos los ajustes, luego reinicia.","Cancella identità, contatti, canali, messaggi, Wi-Fi e tutte le impostazioni, poi riavvia.","Стирает личность, контакты, каналы, сообщения, Wi-Fi и все настройки, затем перезагрузка.","Стирає особистість, контакти, канали, повідомлення, Wi-Fi і всі налаштування, потім перезавантаження.","Изтрива идентичност, контакти, канали, съобщения, Wi-Fi и всички настройки, после рестарт.","Брише идентитет, контакте, канале, поруке, Wi-Fi и сва подешавања, па се рестартује.","Διαγράφει ταυτότητα, επαφές, κανάλια, μηνύματα, Wi-Fi και όλες τις ρυθμίσεις, μετά επανεκκίνηση." } },
{ "Delete this backup file?\nThis cannot be undone.", { 0, "Dit back-upbestand verwijderen?\nDit kan niet ongedaan worden gemaakt.","Diese Backup-Datei löschen?\nDies kann nicht rückgängig gemacht werden.","Supprimer ce fichier de sauvegarde ?\nCeci est irréversible.","¿Eliminar este archivo de copia?\nNo se puede deshacer.","Eliminare questo file di backup?\nNon è reversibile.","Удалить этот файл копии?\nОтменить нельзя.","Видалити цей файл копії?\nСкасувати неможливо.","Изтриване на този файл с копие?\nНе може да се отмени.","Обрисати ову датотеку копије?\nНе може се поништити.","Διαγραφή αυτού του αρχείου;\nΔεν αναιρείται." } },
{ "Factory reset?\n\nErases identity, contacts,\nchannels, messages, Wi-Fi\nand every setting. This\ncannot be undone.", { 0, "Fabrieksreset?\n\nWist identiteit, contacten, kanalen, berichten, wifi en elke instelling. Onomkeerbaar.","Werksreset?\n\nLöscht Identität, Kontakte, Kanäle, Nachrichten, WLAN und alle Einstellungen. Unwiderruflich.","Réinitialisation d'usine ?\n\nEfface identité, contacts, canaux, messages, Wi-Fi et tous les réglages. Irréversible.","¿Restablecer de fábrica?\n\nBorra identidad, contactos, canales, mensajes, Wi-Fi y todos los ajustes. Irreversible.","Ripristino di fabbrica?\n\nCancella identità, contatti, canali, messaggi, Wi-Fi e ogni impostazione. Irreversibile.","Сброс к заводским?\n\nСтирает личность, контакты, каналы, сообщения, Wi-Fi и все настройки. Необратимо.","Скинути до заводських?\n\nСтирає особистість, контакти, канали, повідомлення, Wi-Fi і всі налаштування. Незворотно.","Фабричен ресет?\n\nИзтрива идентичност, контакти, канали, съобщения, Wi-Fi и всички настройки. Необратимо.","Фабричко ресетовање?\n\nБрише идентитет, контакте, канале, поруке, Wi-Fi и сва подешавања. Неповратно.","Επαναφορά εργοστασίου;\n\nΔιαγράφει ταυτότητα, επαφές, κανάλια, μηνύματα, Wi-Fi και κάθε ρύθμιση. Μη αναστρέψιμο." } },
{ "Backups", { 0, "Back-ups","Backups","Sauvegardes","Copias","Backup","Резервные копии","Резервні копії","Резервни копия","Резервне копије","Αντίγραφα" ,"Backups"} },
{ "Saved backups", { 0, "Opgeslagen back-ups","Gespeicherte Backups","Sauvegardes enregistrées","Copias guardadas","Backup salvati","Сохранённые копии","Збережені копії","Запазени копия","Сачуване копије","Αποθηκευμένα αντίγραφα" ,"Backups salvos"} },
{ "Export new backup", { 0, "Nieuwe back-up","Neues Backup","Nouvelle sauvegarde","Nueva copia","Nuovo backup","Новая копия","Нова копія","Ново копие","Нова копија","Νέο αντίγραφο" ,"Exportar novo backup"} },
{ "Danger zone", { 0, "Gevarenzone","Gefahrenzone","Zone de danger","Zona de peligro","Zona pericolosa","Опасная зона","Небезпечна зона","Опасна зона","Опасна зона","Ζώνη κινδύνου" ,"Zona de perigo"} },
{ "Factory reset", { 0, "Fabrieksreset","Werksreset","Réinit. d'usine","Rest. de fábrica","Ripristino fabbrica","Сброс настроек","Скидання налашт.","Фабрични настройки","Фабричко ресет.","Επαναφορά εργοστ." ,"Restauração de fábrica"} },
{ "Erase all", { 0, "Alles wissen","Alles löschen","Tout effacer","Borrar todo","Cancella tutto","Стереть всё","Стерти все","Изтрий всичко","Обриши све","Διαγραφή όλων" ,"Apagar tudo"} },
{ "Erasing everything…", { 0, "Alles wissen…","Alles wird gelöscht…","Effacement total…","Borrando todo…","Cancellazione…","Стирание всего…","Стирання всього…","Изтриване на всичко…","Брисање свега…","Διαγραφή όλων…" ,"Apagando tudo…"} },
{ "Delete failed", { 0, "Verwijderen mislukt","Löschen fehlgeschlagen","Échec suppression","Error al borrar","Eliminazione fallita","Не удалось удалить","Не вдалося видалити","Грешка при изтриване","Брисање није успело","Αποτυχία διαγραφής" ,"Falha ao excluir"} },
{ "No backups yet. Tap Export new backup above to create one.", { 0, "Nog geen back-ups. Tik hierboven op Nieuwe back-up om er een te maken.","Noch keine Backups. Tippe oben auf Neues Backup, um eines zu erstellen.","Aucune sauvegarde. Touchez Nouvelle sauvegarde ci-dessus pour en créer une.","Sin copias aún. Toca Nueva copia arriba para crear una.","Nessun backup. Tocca Nuovo backup sopra per crearne uno.","Копий пока нет. Нажмите «Новая копия» выше, чтобы создать.","Копій ще немає. Натисніть «Нова копія» вгорі, щоб створити.","Все още няма копия. Натиснете «Ново копие» горе, за да създадете.","Још нема копија. Додирните «Нова копија» изнад да направите.","Δεν υπάρχουν αντίγραφα. Πατήστε «Νέο αντίγραφο» πάνω για δημιουργία." ,"Ainda sem backups. Toque em Exportar novo backup acima para criar um."} },
{ "Erases identity, contacts, channels, messages, Wi-Fi and all settings, then reboots.", { 0, "Wist identiteit, contacten, kanalen, berichten, wifi en alle instellingen, en herstart dan.","Löscht Identität, Kontakte, Kanäle, Nachrichten, WLAN und alle Einstellungen, dann Neustart.","Efface identité, contacts, canaux, messages, Wi-Fi et tous les réglages, puis redémarre.","Borra identidad, contactos, canales, mensajes, Wi-Fi y todos los ajustes, luego reinicia.","Cancella identità, contatti, canali, messaggi, Wi-Fi e tutte le impostazioni, poi riavvia.","Стирает личность, контакты, каналы, сообщения, Wi-Fi и все настройки, затем перезагрузка.","Стирає особистість, контакти, канали, повідомлення, Wi-Fi і всі налаштування, потім перезавантаження.","Изтрива идентичност, контакти, канали, съобщения, Wi-Fi и всички настройки, после рестарт.","Брише идентитет, контакте, канале, поруке, Wi-Fi и сва подешавања, па се рестартује.","Διαγράφει ταυτότητα, επαφές, κανάλια, μηνύματα, Wi-Fi και όλες τις ρυθμίσεις, μετά επανεκκίνηση." ,"Apaga identidade, contatos, canais, mensagens, Wi-Fi e todos os ajustes, depois reinicia."} },
{ "Delete this backup file?\nThis cannot be undone.", { 0, "Dit back-upbestand verwijderen?\nDit kan niet ongedaan worden gemaakt.","Diese Backup-Datei löschen?\nDies kann nicht rückgängig gemacht werden.","Supprimer ce fichier de sauvegarde ?\nCeci est irréversible.","¿Eliminar este archivo de copia?\nNo se puede deshacer.","Eliminare questo file di backup?\nNon è reversibile.","Удалить этот файл копии?\nОтменить нельзя.","Видалити цей файл копії?\nСкасувати неможливо.","Изтриване на този файл с копие?\nНе може да се отмени.","Обрисати ову датотеку копије?\nНе може се поништити.","Διαγραφή αυτού του αρχείου;\nΔεν αναιρείται." ,"Excluir este arquivo de backup?\nIsto não pode ser desfeito."} },
{ "Factory reset?\n\nErases identity, contacts,\nchannels, messages, Wi-Fi\nand every setting. This\ncannot be undone.", { 0, "Fabrieksreset?\n\nWist identiteit, contacten, kanalen, berichten, wifi en elke instelling. Onomkeerbaar.","Werksreset?\n\nLöscht Identität, Kontakte, Kanäle, Nachrichten, WLAN und alle Einstellungen. Unwiderruflich.","Réinitialisation d'usine ?\n\nEfface identité, contacts, canaux, messages, Wi-Fi et tous les réglages. Irréversible.","¿Restablecer de fábrica?\n\nBorra identidad, contactos, canales, mensajes, Wi-Fi y todos los ajustes. Irreversible.","Ripristino di fabbrica?\n\nCancella identità, contatti, canali, messaggi, Wi-Fi e ogni impostazione. Irreversibile.","Сброс к заводским?\n\nСтирает личность, контакты, каналы, сообщения, Wi-Fi и все настройки. Необратимо.","Скинути до заводських?\n\nСтирає особистість, контакти, канали, повідомлення, Wi-Fi і всі налаштування. Незворотно.","Фабричен ресет?\n\nИзтрива идентичност, контакти, канали, съобщения, Wi-Fi и всички настройки. Необратимо.","Фабричко ресетовање?\n\nБрише идентитет, контакте, канале, поруке, Wi-Fi и сва подешавања. Неповратно.","Επαναφορά εργοστασίου;\n\nΔιαγράφει ταυτότητα, επαφές, κανάλια, μηνύματα, Wi-Fi και κάθε ρύθμιση. Μη αναστρέψιμο." ,"Restauração de fábrica?\n\nApaga identidade, contatos,\ncanais, mensagens, Wi-Fi\ne todos os ajustes. Isto\nnão pode ser desfeito."} },
};
static const int kI18nCount = (int)(sizeof(kI18n) / sizeof(kI18n[0]));
+1 -1
View File
@@ -9,7 +9,7 @@
// Keep this enum in sync with kUiLangNames + the table column order in i18n.cpp.
enum UiLang : uint8_t {
LANG_EN = 0, LANG_NL, LANG_DE, LANG_FR, LANG_ES, LANG_IT,
LANG_RU, LANG_UK, LANG_BG, LANG_SR, LANG_EL, LANG_COUNT
LANG_RU, LANG_UK, LANG_BG, LANG_SR, LANG_EL, LANG_PT_BR, LANG_COUNT
};
// Native names for the language picker (e.g. "Nederlands", "Русский", "Ελληνικά").
+6
View File
@@ -36,4 +36,10 @@ public:
}
float getAdcMultiplier() const override { return adc_mult; }
const char* getManufacturerName() const override;
// V4.3 (KCT8103L FEM) high-gain receiver LNA (~17 dB). It is BYPASSED by default; this turns it
// on/off and re-drives the FEM into RX so the change applies to the live receive immediately.
// femLnaControllable() is true only on boards where the LNA is switchable (V4.3); a no-op elsewhere.
void setFemLnaEnable(bool en) { loRaFEMControl.setLNAEnable(en); loRaFEMControl.setRxModeEnable(); }
bool femLnaControllable() { return loRaFEMControl.isLnaCanControl(); }
};
+1
View File
@@ -23,6 +23,7 @@ extern "C" void set_boot_phase(int phase);
extern HeltecV4Board board;
extern WRAPPER_CLASS radio_driver;
extern RADIO_CLASS radio; // raw SX1262 — driven directly by the Spectrum analyzer sweep
extern AutoDiscoverRTCClock rtc_clock;
extern EnvironmentSensorManager sensors;
+1
View File
@@ -16,6 +16,7 @@
extern TDeckBoard board;
extern WRAPPER_CLASS radio_driver;
extern RADIO_CLASS radio; // raw SX1262 — driven directly by the Spectrum analyzer sweep
extern AutoDiscoverRTCClock rtc_clock;
extern EnvironmentSensorManager sensors;