mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-15 15:14:26 +10:00
Compare commits
309 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba4fd3eff5 | |||
| beb3e1996d | |||
| 3f4e6f4e13 | |||
| e1cc285c8a | |||
| f4b32e8a8a | |||
| 01bf95c98e | |||
| fa044dd204 | |||
| 72fea3fc32 | |||
| f0bd61144c | |||
| 61c897630c | |||
| a270e2e6d1 | |||
| 247db6a36d | |||
| 78d08afb47 | |||
| c77264cc81 | |||
| d6ed8c5f13 | |||
| 209fee48ca | |||
| 8892823337 | |||
| e738664f89 | |||
| e37616fa15 | |||
| 2763d83fe4 | |||
| 77018dc358 | |||
| 21c58d4e13 | |||
| 3af97ff6dd | |||
| 703d5a1ec4 | |||
| e801a497f8 | |||
| e92a66ff28 | |||
| 6900e5c3db | |||
| 966a8d0d2c | |||
| 3ec3b05fb8 | |||
| 14a93e9bf5 | |||
| c229b0369e | |||
| 9f332e93be | |||
| b472ea8c70 | |||
| a67c6d81c3 | |||
| 91ae4dab90 | |||
| 08ac60a408 | |||
| d4da34fcf7 | |||
| 74840d3baf | |||
| 4a72fbd1ad | |||
| dbe0a5411b | |||
| dc3325ec46 | |||
| a92e57bb64 | |||
| e21f3106d0 | |||
| 0dcb5f05f0 | |||
| f501d11ec6 | |||
| dfcf13a97b | |||
| ccd23c4b81 | |||
| 00636c9084 | |||
| accec1681b | |||
| 67238468ce | |||
| bc5b12f1ef | |||
| c09af98bef | |||
| ae32e76563 | |||
| 5572c9ee75 | |||
| f6cc000788 | |||
| 75b0d198bc | |||
| 1947cd9f3e | |||
| f1d93bd5e8 | |||
| f63d50f0da | |||
| eb597b6c68 | |||
| efe21c4e87 | |||
| 38fece3313 | |||
| 3af3cce606 | |||
| 026ec6f7de | |||
| eb50249b93 | |||
| ca6058eccd | |||
| 99c0ab7e22 | |||
| 2950a9a687 | |||
| 1b3de54873 | |||
| 20a9ef3c2b | |||
| a741e12ad1 | |||
| e54f30d6fb | |||
| e1d23ad2c7 | |||
| f07993b367 | |||
| 0e5f1a45c4 | |||
| f10aeaeba8 | |||
| 00e4f52d75 | |||
| 3ea2e4763e | |||
| 94d9afe8b1 | |||
| 7db3a12723 | |||
| fcf10b4a73 | |||
| 7f353490cf | |||
| 46683e0ec2 | |||
| 4e368d562d | |||
| 38f6e42796 | |||
| f56c28a27d | |||
| 92d3009eb4 | |||
| 7a4ac9ae9b | |||
| f8d00caae0 | |||
| e03d80b71f | |||
| b7d0db8d1c | |||
| 6ae3f612ae | |||
| 40d3941aab | |||
| e53c493e78 | |||
| 54e0dae172 | |||
| 066aba7c5d | |||
| 5e446207c6 | |||
| 609d0c8dbc | |||
| 820bac0db0 | |||
| d3c7d8e43a | |||
| 0c1e163b88 | |||
| d0d6a34fb5 | |||
| 16ce1359d7 | |||
| 9fe4a3710d | |||
| 8611adab1f | |||
| 7d457cb863 | |||
| 297516fc80 | |||
| 6b6a881c7a | |||
| 8ef8a38495 | |||
| ddcda4ba5a | |||
| 5cfe45b953 | |||
| b572314ae9 | |||
| e97fb9bd24 | |||
| 1c9c089a53 | |||
| cb3b5a84eb | |||
| a4bbeffddc | |||
| 37ec8f2f05 | |||
| 39cd6d5514 | |||
| 44eb4fad58 | |||
| 1a209cbcfc | |||
| 33a8f34463 | |||
| ce8e8f0d5b | |||
| aa2d0f1927 | |||
| 0757c8e53a | |||
| add4731d05 | |||
| 7dc162d968 | |||
| 8ba4bbfbc5 | |||
| cac6abfef1 | |||
| 5354acb1d3 | |||
| fae416fb34 | |||
| 69433b6d89 | |||
| ea3b9609fc | |||
| 20a9939314 | |||
| c7b7deb0f6 | |||
| 82e04e8090 | |||
| f299608296 | |||
| 7dcec5b4ee | |||
| e4684b585a | |||
| 8386f262e1 | |||
| 45cd8a56a3 | |||
| 754f8a6c62 | |||
| c4f54efd77 | |||
| 637e08d22c | |||
| 32dc0fca22 | |||
| b5aa294fc1 | |||
| 26516baf67 | |||
| 4879b136f8 | |||
| bdd7fc0cdd | |||
| 5ea044af10 | |||
| 9d20be1c06 | |||
| 9436c2d45a | |||
| 17e55e96bb | |||
| e4cfbb57b4 | |||
| d9f9ff58b4 | |||
| a059f1be45 | |||
| 9e46f8b44c | |||
| a934781009 | |||
| 5fe6738f25 | |||
| c1bcf261d7 | |||
| b570539a2d | |||
| 89a14c2719 | |||
| 4ad01ed43c | |||
| ffaa4033ae | |||
| 1a4fd1b477 | |||
| e1555ce380 | |||
| c7933d363b | |||
| 08ffb978cf | |||
| c5ec60638c | |||
| 75ec3b6116 | |||
| 45c9823c6f | |||
| 45658a7612 | |||
| a14833494e | |||
| 457b44de3a | |||
| 36d4a10396 | |||
| 77566b0fe1 | |||
| 7633327f45 | |||
| 6b4b2d7ce6 | |||
| 10b63e0df2 | |||
| ba6d751346 | |||
| 96d222a580 | |||
| 01ad8471cc | |||
| 2b826757cb | |||
| 9bf649e2c6 | |||
| c7a2bf9a95 | |||
| 82adbd761b | |||
| 9a8bdf00dc | |||
| 8b30342113 | |||
| 817c60a155 | |||
| f08e86cf97 | |||
| a6bb9490a1 | |||
| e4e8bfa4ef | |||
| d1e45fc2ba | |||
| 32fa96431e | |||
| 1e9508d401 | |||
| 36697c6e61 | |||
| c9145c99d3 | |||
| 6b6d9caeeb | |||
| d0e3767db6 | |||
| f9cb0c80a5 | |||
| a26d14bd46 | |||
| 411cd3f8d2 | |||
| 38f4de80b6 | |||
| 7de07c023f | |||
| c272c60f9a | |||
| eca78453d6 | |||
| 3754cf14ea | |||
| 834850fb51 | |||
| e7e2bb91b8 | |||
| 4c492f69ef | |||
| 50f2a8b439 | |||
| 2c8a15538e | |||
| 68eeefa04e | |||
| ebbc367fec | |||
| 2da8995d0b | |||
| 1c376b0056 | |||
| da70d5fc08 | |||
| f63bc4b787 | |||
| 9b1f1e1994 | |||
| 5f475fce4d | |||
| 0228c38621 | |||
| fc7283f076 | |||
| 7eff1df6e2 | |||
| 58252b8a40 | |||
| 630606acdc | |||
| bd030153c1 | |||
| 5140ff383d | |||
| dc57f9b9c0 | |||
| 53cd3f4461 | |||
| 35e296f1cd | |||
| 532401cc94 | |||
| 5321974cbb | |||
| 7c16dde989 | |||
| 9a75c912af | |||
| 767dc1164e | |||
| 14f3429eb5 | |||
| e49e80d330 | |||
| d07372c7e0 | |||
| 990f2bd33d | |||
| 29660d520e | |||
| dbefb0b5f4 | |||
| 4f609f160f | |||
| e313bea3fc | |||
| 77be2b8e6f | |||
| c81c3efe7c | |||
| cac0cc15eb | |||
| 1392c2d00f | |||
| cb63b48b78 | |||
| 4ad4a93a20 | |||
| 4962a48e64 | |||
| b88e5e647a | |||
| 87d11c2e6b | |||
| 7b3c099736 | |||
| 11cb14a925 | |||
| d2df2b0bed | |||
| 723bf7293c | |||
| 53caec3e14 | |||
| 3c440ca3d4 | |||
| 8797d8ffde | |||
| faba120823 | |||
| be690c8194 | |||
| 0ef2194fb0 | |||
| 3664ae34cd | |||
| 64d75dde45 | |||
| 9199aab7f7 | |||
| 60e8ee0130 | |||
| 6dfb7a4b69 | |||
| 28a423e0a8 | |||
| 3593cfa843 | |||
| dc85e7a41c | |||
| 9265daaf16 | |||
| 4b744184c2 | |||
| 64698e0be6 | |||
| 3dd9037be3 | |||
| 566e3aadf8 | |||
| 06a906f4f7 | |||
| 054a84031e | |||
| fffcff3b74 | |||
| b336aedbc5 | |||
| 2ee2358ecc | |||
| 86e9b7fe01 | |||
| 24fa78741b | |||
| 79a45c527b | |||
| 8b280b37be | |||
| fa4da979af | |||
| 91608ff09e | |||
| 71f59d23df | |||
| e90742be25 | |||
| db935a7454 | |||
| 1ad5db27ca | |||
| 81758adc61 | |||
| 2f770bbd53 | |||
| 9db79e9d40 | |||
| 1913a5aa11 | |||
| 929c1c3d28 | |||
| 7a2bb20bf7 | |||
| 1b94442ab6 | |||
| 3ae14781f0 | |||
| ecc496f2af | |||
| 87b25655d0 | |||
| c47a4cb622 | |||
| a30fc439f3 | |||
| afcc4db405 | |||
| 87bcb6a6a3 | |||
| 68bb031bb6 | |||
| c4f5c7b171 | |||
| 2bce14224d | |||
| fd305fd55b | |||
| d0dd805244 | |||
| 8668564464 |
+6
-1
@@ -33,6 +33,9 @@ migrate_working_dir/
|
|||||||
pubspec.lock
|
pubspec.lock
|
||||||
/build/
|
/build/
|
||||||
/coverage/
|
/coverage/
|
||||||
|
# fvm project files
|
||||||
|
.fvm/
|
||||||
|
.fvmrc
|
||||||
|
|
||||||
# Symbolication related
|
# Symbolication related
|
||||||
app.*.symbols
|
app.*.symbols
|
||||||
@@ -58,6 +61,7 @@ secrets.dart
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.AppleDouble
|
.AppleDouble
|
||||||
.LSOverride
|
.LSOverride
|
||||||
|
macos/Flutter/GeneratedPluginRegistrant.swift
|
||||||
|
|
||||||
# iOS
|
# iOS
|
||||||
**/ios/Pods/
|
**/ios/Pods/
|
||||||
@@ -83,6 +87,7 @@ keystore.properties
|
|||||||
# IDE
|
# IDE
|
||||||
.vscode/launch.json
|
.vscode/launch.json
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
|
.contextstream/
|
||||||
|
|
||||||
# Cloudflare Wrangler
|
# Cloudflare Wrangler
|
||||||
.wrangler
|
.wrangler
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
6.2.4
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
## BLE Frames & Protocol Notes
|
## BLE Frames & Protocol Notes
|
||||||
- Nordic UART Service (NUS) UUIDs: Service `6e400001-b5a3-f393-e0a9-e50e24dcca9e`, RX `6e400002-b5a3-f393-e0a9-e50e24dcca9e`, TX `6e400003-b5a3-f393-e0a9-e50e24dcca9e`.
|
- Nordic UART Service (NUS) UUIDs: Service `6e400001-b5a3-f393-e0a9-e50e24dcca9e`, RX `6e400002-b5a3-f393-e0a9-e50e24dcca9e`, TX `6e400003-b5a3-f393-e0a9-e50e24dcca9e`.
|
||||||
- Discovery: scans for device name prefix `MeshCore-` and filters by `platformName`/`advertisementData.advName`.
|
- Discovery: scans for device names matching known prefixes and filters by `platformName`/`advertisementData.advName`.
|
||||||
- Frames are capped at `maxFrameSize = 172` bytes; byte 0 is the command/response/push code. I/O is `MeshCoreConnector.sendFrame` and `MeshCoreConnector.receivedFrames`.
|
- Frames are capped at `maxFrameSize = 172` bytes; byte 0 is the command/response/push code. I/O is `MeshCoreConnector.sendFrame` and `MeshCoreConnector.receivedFrames`.
|
||||||
- Command codes (to device): `cmdAppStart`=1, `cmdSendTxtMsg`=2, `cmdSendChannelTxtMsg`=3, `cmdGetContacts`=4, `cmdGetDeviceTime`=5, `cmdSetDeviceTime`=6, `cmdSendSelfAdvert`=7, `cmdSetAdvertName`=8, `cmdAddUpdateContact`=9, `cmdSyncNextMessage`=10, `cmdSetRadioParams`=11, `cmdSetRadioTxPower`=12, `cmdResetPath`=13, `cmdSetAdvertLatLon`=14, `cmdRemoveContact`=15, `cmdShareContact`=16, `cmdExportContact`=17, `cmdImportContact`=18, `cmdReboot`=19, `cmdSendLogin`=26, `cmdGetChannel`=31, `cmdSetChannel`=32, `cmdGetRadioSettings`=57.
|
- Command codes (to device): `cmdAppStart`=1, `cmdSendTxtMsg`=2, `cmdSendChannelTxtMsg`=3, `cmdGetContacts`=4, `cmdGetDeviceTime`=5, `cmdSetDeviceTime`=6, `cmdSendSelfAdvert`=7, `cmdSetAdvertName`=8, `cmdAddUpdateContact`=9, `cmdSyncNextMessage`=10, `cmdSetRadioParams`=11, `cmdSetRadioTxPower`=12, `cmdResetPath`=13, `cmdSetAdvertLatLon`=14, `cmdRemoveContact`=15, `cmdShareContact`=16, `cmdExportContact`=17, `cmdImportContact`=18, `cmdReboot`=19, `cmdSendLogin`=26, `cmdGetChannel`=31, `cmdSetChannel`=32, `cmdGetRadioSettings`=57.
|
||||||
- Response codes (from device): `respCodeOk`=0, `respCodeErr`=1, `respCodeContactsStart`=2, `respCodeContact`=3, `respCodeEndOfContacts`=4, `respCodeSelfInfo`=5, `respCodeSent`=6, `respCodeContactMsgRecv`=7, `respCodeChannelMsgRecv`=8, `respCodeCurrTime`=9, `respCodeNoMoreMessages`=10, `respCodeContactMsgRecvV3`=16, `respCodeChannelMsgRecvV3`=17, `respCodeChannelInfo`=18, `respCodeRadioSettings`=25.
|
- Response codes (from device): `respCodeOk`=0, `respCodeErr`=1, `respCodeContactsStart`=2, `respCodeContact`=3, `respCodeEndOfContacts`=4, `respCodeSelfInfo`=5, `respCodeSent`=6, `respCodeContactMsgRecv`=7, `respCodeChannelMsgRecv`=8, `respCodeCurrTime`=9, `respCodeNoMoreMessages`=10, `respCodeContactMsgRecvV3`=16, `respCodeChannelMsgRecvV3`=17, `respCodeChannelInfo`=18, `respCodeRadioSettings`=25.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# MeshCore Open - Flutter Client
|
# MeshCore Open - Flutter Client
|
||||||
|
|
||||||
Open-source Flutter client for MeshCore LoRa mesh networking devices.
|
Open-source Flutter client for MeshCore LoRa mesh networking devices. Connects to MeshCore-compatible radios over **BLE, TCP, or USB serial** and provides direct/channel chat, contact and channel management, on-map node tracking, repeater administration, and on-device message translation.
|
||||||
|
|
||||||
## Build Commands
|
## Build Commands
|
||||||
|
|
||||||
@@ -17,6 +17,9 @@ Open-source Flutter client for MeshCore LoRa mesh networking devices.
|
|||||||
# Build iOS
|
# Build iOS
|
||||||
~/flutter/bin/flutter build ios
|
~/flutter/bin/flutter build ios
|
||||||
|
|
||||||
|
# Build versioned web release (uses build_pipe)
|
||||||
|
~/flutter/bin/dart run build_pipe
|
||||||
|
|
||||||
# Run static analysis
|
# Run static analysis
|
||||||
~/flutter/bin/flutter analyze
|
~/flutter/bin/flutter analyze
|
||||||
|
|
||||||
@@ -28,43 +31,132 @@ Open-source Flutter client for MeshCore LoRa mesh networking devices.
|
|||||||
|
|
||||||
```
|
```
|
||||||
lib/
|
lib/
|
||||||
├── main.dart # App entry point, MaterialApp setup with Provider
|
├── main.dart # Entry point: MultiProvider wiring, locale + theme, initial route
|
||||||
├── connector/
|
├── connector/ # Unified BLE/TCP/USB transport layer
|
||||||
│ └── meshcore_connector.dart # BLE communication layer (MeshCoreConnector)
|
│ ├── meshcore_connector.dart # Central state holder + ChangeNotifier (all transports)
|
||||||
├── screens/
|
│ ├── meshcore_connector_tcp.dart # TCP transport helper
|
||||||
│ ├── scanner_screen.dart # BLE device scanning (home screen)
|
│ ├── meshcore_connector_usb.dart # USB serial transport helper
|
||||||
│ ├── device_screen.dart # Connected device hub with navigation
|
│ ├── meshcore_protocol.dart # Frame size + version constants
|
||||||
│ ├── chat_screen.dart # Chat interface (placeholder)
|
│ └── meshcore_uuids.dart # Nordic UART UUIDs + scan name prefixes
|
||||||
│ ├── contacts_screen.dart # Contacts list (placeholder)
|
├── models/ # Plain data classes (Contact, Channel, Message, Community, …)
|
||||||
│ └── settings_screen.dart # Device info and app settings
|
├── services/ # ChangeNotifier services + IO services (retry, translation, ML, …)
|
||||||
└── widgets/
|
├── storage/ # SharedPreferences-backed stores, scoped per device key
|
||||||
└── device_tile.dart # Device list item with signal strength
|
├── helpers/ # Pure utilities (Smaz compression, GIF parsing, scroll helpers)
|
||||||
|
├── utils/ # Platform / IO / UX utilities (logger, GPX export, dialogs)
|
||||||
|
├── theme/ # MeshPalette (defined, not yet wired in main.dart)
|
||||||
|
├── l10n/ # ARB localization for 18 locales
|
||||||
|
├── icons/ # Custom icon widgets
|
||||||
|
├── widgets/ # Reusable widgets (AppBar, BatteryUi, QR, jump-to-bottom, …)
|
||||||
|
└── screens/ # ~26 screens — see Screens section below
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Screens
|
||||||
|
|
||||||
|
All screens are fully implemented (no remaining placeholders).
|
||||||
|
|
||||||
|
### Connection / Scanning
|
||||||
|
| Screen | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `scanner_screen.dart` | BLE device scan and connect — main entry point |
|
||||||
|
| `tcp_screen.dart` | Connect to a MeshCore device over TCP/IP |
|
||||||
|
| `usb_screen.dart` | Connect to a MeshCore device over USB serial |
|
||||||
|
| `discovery_screen.dart` | Browse all discovered (non-contact) mesh nodes |
|
||||||
|
| `chrome_required_screen.dart` | Web gate for non-Chrome browsers (BLE unavailable) |
|
||||||
|
|
||||||
|
### Chat / Messaging
|
||||||
|
| Screen | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `chat_screen.dart` | Direct (private) messaging with a contact |
|
||||||
|
| `channel_chat_screen.dart` | Group messaging inside a named channel |
|
||||||
|
| `channels_screen.dart` | List and manage channels (add/edit/delete) |
|
||||||
|
| `channel_message_path_screen.dart` | Hop-by-hop route a channel message took, with map overlay |
|
||||||
|
|
||||||
|
### Contacts / Neighbors
|
||||||
|
| Screen | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `contacts_screen.dart` | Full contacts list with previews and management |
|
||||||
|
| `neighbors_screen.dart` | Nodes directly heard by the connected radio (one-hop) |
|
||||||
|
|
||||||
|
### Repeater Management
|
||||||
|
| Screen | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `repeater_hub_screen.dart` | Top-level repeater hub; navigates to sub-screens |
|
||||||
|
| `repeater_status_screen.dart` | Live status of a managed repeater node |
|
||||||
|
| `repeater_cli_screen.dart` | Raw command-line interface to a repeater |
|
||||||
|
| `repeater_settings_screen.dart` | Full radio/node settings editor for a repeater |
|
||||||
|
|
||||||
|
### Map / Location
|
||||||
|
| Screen | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `map_screen.dart` | Main map view of contacts/nodes with live GPS positions |
|
||||||
|
| `line_of_sight_map_screen.dart` | Terrain LOS analysis between configurable endpoints |
|
||||||
|
| `path_trace_map.dart` | Animates the hop path a direct message traveled |
|
||||||
|
| `map_cache_screen.dart` | Download/clear offline map tile cache |
|
||||||
|
| `community_qr_scanner_screen.dart` | Scan QR to join a mesh community/channel |
|
||||||
|
|
||||||
|
### Settings / Debug / Diagnostics
|
||||||
|
| Screen | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `settings_screen.dart` | Connected device settings: radio params, identity, GPS |
|
||||||
|
| `app_settings_screen.dart` | App preferences: theme, units, map source, notifications |
|
||||||
|
| `app_debug_log_screen.dart` | In-app log viewer (app-layer messages) |
|
||||||
|
| `ble_debug_log_screen.dart` | In-app log viewer (raw BLE frame traffic) |
|
||||||
|
| `companion_radio_stats_screen.dart` | RF stats (RSSI, SNR, packet counts) for paired radio |
|
||||||
|
| `telemetry_screen.dart` | Battery / sensor / environmental telemetry for a contact |
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### State Management
|
### State Management
|
||||||
- **Provider** with `ChangeNotifier` pattern
|
|
||||||
- `MeshCoreConnector` is the central state holder for BLE connection
|
`Provider` with `ChangeNotifier`. `main.dart` wires a `MultiProvider` with the following:
|
||||||
- Screens use `Consumer<MeshCoreConnector>` for reactive UI updates
|
|
||||||
|
| Provider | Role |
|
||||||
|
|---|---|
|
||||||
|
| `MeshCoreConnector` | Active transport (BLE/TCP/USB), connection state, frame I/O |
|
||||||
|
| `MessageRetryService` | ACK tracking and retry scheduling with backoff |
|
||||||
|
| `PathHistoryService` | Per-contact routing history (LRU cache, 50 contacts) |
|
||||||
|
| `AppSettingsService` | App preferences (theme, units, locale, notifications) |
|
||||||
|
| `BleDebugLogService` | Raw BLE frame log buffer |
|
||||||
|
| `AppDebugLogService` | Structured app log buffer |
|
||||||
|
| `ChatTextScaleService` | Pinch-to-zoom text scale for chat screens |
|
||||||
|
| `TranslationService` | On-device LLM translation (llamadart) |
|
||||||
|
| `UiViewStateService` | Contacts/channels sort/filter/search state |
|
||||||
|
| `TimeoutPredictionService` | ML linear regression for ACK timeout prediction |
|
||||||
|
| `StorageService` | Path history + delivery observation persistence |
|
||||||
|
| `MapTileCacheService` | OSM tile pre-cache |
|
||||||
|
|
||||||
|
Screens consume these via `Consumer<T>` (or `context.watch<T>()` / `context.read<T>()`) for reactive UI.
|
||||||
|
|
||||||
|
### Storage / Persistence
|
||||||
|
|
||||||
|
All stores in `lib/storage/` use `PrefsManager` (a `SharedPreferences` singleton initialized in `main()`). Most stores **scope keys by the first 10 hex chars of the connected device's public key**, so per-radio data is isolated.
|
||||||
|
|
||||||
|
| Store | Persists |
|
||||||
|
|---|---|
|
||||||
|
| `message_store`, `channel_message_store` | Direct + channel messages |
|
||||||
|
| `contact_store`, `contact_discovery_store` | Known + discovered contacts |
|
||||||
|
| `channel_store`, `channel_order_store`, `channel_settings_store` | Channels, display order, per-channel Smaz toggle |
|
||||||
|
| `community_store` | Communities (32-byte shared secrets) |
|
||||||
|
| `contact_group_store`, `contact_settings_store` | Groups, per-contact Smaz toggle |
|
||||||
|
| `unread_store` | Per-contact unread counts (debounced writes) |
|
||||||
|
|
||||||
|
GGUF translation models are stored as files (not SharedPreferences) via `translation_file_store`.
|
||||||
|
|
||||||
### Theming
|
### Theming
|
||||||
- Material 3 design (`useMaterial3: true`)
|
- Material 3 design (`useMaterial3: true`)
|
||||||
- System-based dark/light mode (`ThemeMode.system`)
|
- System-based dark/light mode (`ThemeMode.system`)
|
||||||
- Blue color scheme seed
|
- Blue color scheme seed
|
||||||
|
- `lib/theme/mesh_theme.dart` defines a warm-dark `MeshPalette` (phosphor-green accents) but is **not currently wired** in `main.dart` — available for a future redesign
|
||||||
|
|
||||||
## BLE Protocol
|
### Localization
|
||||||
|
|
||||||
### Nordic UART Service (NUS)
|
18 locales supported via Flutter's standard ARB pipeline (`lib/l10n/`): en, de, es, fr, it, pt, ru, uk, bg, hu, ja, ko, nl, pl, sk, sl, sv, zh. Language override comes from `AppSettingsService.settings.languageOverride`. Use the `context.l10n` extension (`lib/l10n/l10n.dart`) for translated strings; contact-type names live in `contact_localization.dart`.
|
||||||
- **Service UUID**: `6e400001-b5a3-f393-e0a9-e50e24dcca9e`
|
|
||||||
- **RX Characteristic**: `6e400002-b5a3-f393-e0a9-e50e24dcca9e` (Write to device)
|
|
||||||
- **TX Characteristic**: `6e400003-b5a3-f393-e0a9-e50e24dcca9e` (Notify from device)
|
|
||||||
|
|
||||||
### Device Discovery
|
## Transports
|
||||||
- Scans for devices with name prefix `MeshCore-`
|
|
||||||
- Filters by `platformName` or `advertisementData.advName`
|
|
||||||
|
|
||||||
### Connection States
|
`MeshCoreConnector` unifies all three transports under one `ChangeNotifier`. There is **no shared base class** — selection is via the `MeshCoreTransportType { bluetooth, usb, tcp }` enum, and BLE/TCP/USB share the same connection-state enum, send/receive API, and frame protocol.
|
||||||
|
|
||||||
|
### Connection State
|
||||||
```dart
|
```dart
|
||||||
enum MeshCoreConnectionState {
|
enum MeshCoreConnectionState {
|
||||||
disconnected,
|
disconnected,
|
||||||
@@ -75,28 +167,136 @@ enum MeshCoreConnectionState {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Frame I/O
|
### Frame I/O (all transports)
|
||||||
- **Send**: `MeshCoreConnector.sendFrame(Uint8List data)`
|
- **Send**: `MeshCoreConnector.sendFrame(Uint8List data, {String? channelSendQueueId, bool expectsGenericAck})`
|
||||||
- **Receive**: `MeshCoreConnector.receivedFrames` stream of `Uint8List`
|
- **Receive**: `Stream<Uint8List> get receivedFrames`
|
||||||
|
- **Protocol constants** (`meshcore_protocol.dart`): `maxFrameSize = 172`, `maxTextPayloadBytes = 160`, `appProtocolVersion = 4`
|
||||||
|
|
||||||
|
### BLE — Nordic UART Service (NUS)
|
||||||
|
- **Service UUID**: `6e400001-b5a3-f393-e0a9-e50e24dcca9e`
|
||||||
|
- **RX Characteristic** (write to device): `6e400002-b5a3-f393-e0a9-e50e24dcca9e`
|
||||||
|
- **TX Characteristic** (notify from device): `6e400003-b5a3-f393-e0a9-e50e24dcca9e`
|
||||||
|
- **Discovery**: scans for devices whose name starts with `MeshCore-`, `Whisper-`, `WisCore-`, `Seeed`, `Lilygo`, `HT-`, or `LowMesh_MC_` (filters on both `platformName` and `advertisementData.advName`)
|
||||||
|
- **Linux**: `linux_ble_pairing_service.dart` falls back to `bluetoothctl` when BlueZ agent prompts fail
|
||||||
|
|
||||||
|
### TCP
|
||||||
|
- Manual host/port entry, persisted via `AppSettingsService` (`tcpServerAddress`, `tcpServerPort`)
|
||||||
|
- UI hint: `192.168.40.10` / port `5000`
|
||||||
|
- Disabled on web (`PlatformInfo.isWeb`)
|
||||||
|
- API: `MeshCoreConnector.connectTcp(host: ..., port: ...)`
|
||||||
|
|
||||||
|
### USB Serial (flserial)
|
||||||
|
- Default baud rate: `115200`
|
||||||
|
- Port enumeration: `MeshCoreConnector.listUsbPorts()`
|
||||||
|
- COBS-framed packets via `usb_serial_frame_codec.dart`
|
||||||
|
- macOS device-name resolution via `ioreg` (`utils/macos_usb_device_names.dart`)
|
||||||
|
- API: `MeshCoreConnector.connectUsb(portName: ..., baudRate: 115200)`
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
|
App version: `8.0.0+11` — Dart SDK constraint: `^3.9.2`
|
||||||
|
|
||||||
|
**Connectivity**
|
||||||
|
|
||||||
| Package | Version | Purpose |
|
| Package | Version | Purpose |
|
||||||
|---------|---------|---------|
|
|---------|---------|---------|
|
||||||
| flutter_blue_plus | ^2.1.0 | BLE communication |
|
| flutter_blue_plus | ^2.1.0 | BLE scanning, connecting, and UART data transfer |
|
||||||
| provider | ^6.1.5+1 | State management |
|
| flutter_blue_plus_platform_interface | ^8.2.1 | Platform-interface layer required by flutter_blue_plus |
|
||||||
| cupertino_icons | ^1.0.8 | iOS-style icons |
|
| flserial | git (MeshEnvy fork) | USB serial transport for wired device connections (TODO: upstream pending) |
|
||||||
|
|
||||||
|
**State / Storage**
|
||||||
|
|
||||||
|
| Package | Version | Purpose |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| provider | ^6.1.5+1 | ChangeNotifier-based state management across screens |
|
||||||
|
| shared_preferences | ^2.2.2 | Persistent key-value storage for user settings |
|
||||||
|
| path_provider | ^2.1.5 | Locates platform-appropriate directories for file I/O |
|
||||||
|
|
||||||
|
**Crypto**
|
||||||
|
|
||||||
|
| Package | Version | Purpose |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| crypto | ^3.0.3 | SHA/HMAC hashing used in message authentication |
|
||||||
|
| pointycastle | ^4.0.0 | AES encryption/decryption for channel and direct messages |
|
||||||
|
| uuid | ^4.3.3 | Generates UUIDs for message and contact identity |
|
||||||
|
|
||||||
|
**Maps & Location**
|
||||||
|
|
||||||
|
| Package | Version | Purpose |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| flutter_map | ^8.2.2 | Interactive tile map for node positions and path traces |
|
||||||
|
| latlong2 | ^0.9.1 | LatLng coordinate type used throughout map and GPS code |
|
||||||
|
| gpx | ^2.3.0 | Export node paths as GPX track files |
|
||||||
|
|
||||||
|
**UI**
|
||||||
|
|
||||||
|
| Package | Version | Purpose |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| material_symbols_icons | ^4.2906.0 | Extended Material Symbols icon set (line-of-sight, etc.) |
|
||||||
|
| flutter_svg | ^2.0.10+1 | Renders SVG assets (custom icons such as LoS indicator) |
|
||||||
|
| cached_network_image | ^3.4.1 | Caches map tile images downloaded over the network |
|
||||||
|
| flutter_cache_manager | ^3.4.1 | Underlying cache manager used by cached_network_image |
|
||||||
|
| flutter_linkify | ^6.0.0 | Auto-detects and makes URLs tappable in chat messages |
|
||||||
|
| mobile_scanner | ^7.1.4 | QR/barcode scanning for contact and channel import |
|
||||||
|
| qr_flutter | ^4.1.0 | Generates QR codes for sharing contacts and channels |
|
||||||
|
| cupertino_icons | ^1.0.8 | iOS-style icon font (bundled for completeness) |
|
||||||
|
| characters | ^1.4.0 | Unicode-aware string operations for message text handling |
|
||||||
|
|
||||||
|
**Notifications / Background**
|
||||||
|
|
||||||
|
| Package | Version | Purpose |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| flutter_local_notifications | ^20.1.0 | Shows local push notifications for incoming messages |
|
||||||
|
| flutter_foreground_task | ^9.2.0 | Keeps the app alive in background to maintain BLE/USB connection |
|
||||||
|
|
||||||
|
**ML / AI**
|
||||||
|
|
||||||
|
| Package | Version | Purpose |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| ml_algo | ^16.0.0 | OLS regression used in `timeout_prediction_service.dart` to predict message ACK timeouts |
|
||||||
|
| ml_dataframe | ^1.0.0 | DataFrame input format required by ml_algo |
|
||||||
|
| llamadart | >=0.6.8 <0.7.0 | On-device LLM inference used in `translation_service.dart` for message translation |
|
||||||
|
|
||||||
|
**Misc**
|
||||||
|
|
||||||
|
| Package | Version | Purpose |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| http | ^1.2.0 | Fetches tile URLs and any remote API calls |
|
||||||
|
| url_launcher | ^6.3.0 | Opens URLs in the system browser from linkified chat text |
|
||||||
|
| share_plus | ^12.0.1 | Shares files (e.g. exported GPX tracks) via the system share sheet |
|
||||||
|
| package_info_plus | ^9.0.0 | Reads app version/build number displayed in settings |
|
||||||
|
| web | ^1.1.1 | Web-platform APIs for USB serial and browser detection on Flutter Web |
|
||||||
|
| intl | any | Internationalization and locale formatting (required by flutter_localizations) |
|
||||||
|
| build_pipe | ^0.3.1 | CI/CD build pipeline configuration (web release builds with versioned assets) |
|
||||||
|
|
||||||
## Platform Configuration
|
## Platform Configuration
|
||||||
|
|
||||||
### Android (`android/app/src/main/AndroidManifest.xml`)
|
### Android (`android/app/src/main/AndroidManifest.xml`)
|
||||||
- `BLUETOOTH`, `BLUETOOTH_ADMIN` (API 30 and below)
|
- `INTERNET` (map tiles, translation model downloads)
|
||||||
- `BLUETOOTH_SCAN`, `BLUETOOTH_CONNECT`, `BLUETOOTH_ADVERTISE` (API 31+)
|
- `BLUETOOTH`, `BLUETOOTH_ADMIN` (API ≤ 30)
|
||||||
- `ACCESS_FINE_LOCATION`, `ACCESS_COARSE_LOCATION` (for BLE scanning)
|
- `BLUETOOTH_SCAN` (with `neverForLocation`), `BLUETOOTH_CONNECT`, `BLUETOOTH_ADVERTISE` (API 31+)
|
||||||
|
- `ACCESS_FINE_LOCATION`, `ACCESS_COARSE_LOCATION` (BLE scanning on API ≤ 30)
|
||||||
|
- `POST_NOTIFICATIONS` (API 33+)
|
||||||
|
- `FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_CONNECTED_DEVICE` (background BLE/USB connection)
|
||||||
|
- `WAKE_LOCK`
|
||||||
|
- `CAMERA` (QR scanning, declared as optional feature)
|
||||||
|
- USB host hardware feature (optional)
|
||||||
|
|
||||||
|
`flutter_foreground_task` registers a `ForegroundService` with `foregroundServiceType="connectedDevice"` and `stopWithTask="false"`.
|
||||||
|
|
||||||
|
**Build config (`android/app/build.gradle.kts`)**: `applicationId = com.meshcore.meshcore_open`, NDK `29.0.14206865`, Java 8 core-library desugaring (`desugar_jdk_libs:2.1.4`), release signing via `key.properties` (debug fallback).
|
||||||
|
|
||||||
### iOS (`ios/Runner/Info.plist`)
|
### iOS (`ios/Runner/Info.plist`)
|
||||||
- `NSBluetoothAlwaysUsageDescription`
|
- `NSBluetoothAlwaysUsageDescription`, `NSBluetoothPeripheralUsageDescription`
|
||||||
- `NSBluetoothPeripheralUsageDescription`
|
- `NSCameraUsageDescription` (QR scanning to join communities)
|
||||||
|
- Background modes: `bluetooth-central`
|
||||||
|
- `LSApplicationQueriesSchemes`: `http`, `https`
|
||||||
|
|
||||||
|
### Web (`web/`)
|
||||||
|
PWA scaffold present but boilerplate (`manifest.json` and `index.html` are unmodified Flutter defaults). BLE is unsupported in browsers; TCP and Web Serial USB may work in Chrome only. `ChromeRequiredScreen` gates non-Chrome web users. Versioned releases are produced via `build_pipe` (`?v=<pubspec version>` cache busting, no service worker).
|
||||||
|
|
||||||
|
### Desktop
|
||||||
|
`linux/`, `windows/`, and `macos/` directories are present as Flutter scaffolds. No app-specific native config has been added; BLE on desktop has not been validated.
|
||||||
|
|
||||||
## Coding Conventions
|
## Coding Conventions
|
||||||
|
|
||||||
@@ -123,14 +323,14 @@ enum MeshCoreConnectionState {
|
|||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| `lib/connector/meshcore_connector.dart` | All BLE logic - scanning, connecting, data transfer |
|
| `lib/main.dart` | App configuration, MultiProvider setup, theme, locale, initial route |
|
||||||
| `lib/screens/scanner_screen.dart` | Entry point UI, device list |
|
| `lib/connector/meshcore_connector.dart` | Unified BLE/TCP/USB transport state holder |
|
||||||
| `lib/main.dart` | App configuration, theme, Provider setup |
|
| `lib/connector/meshcore_protocol.dart` | Frame size limits and protocol version |
|
||||||
| `pubspec.yaml` | Dependencies and project metadata |
|
| `lib/connector/meshcore_uuids.dart` | NUS UUIDs and BLE scan name prefixes |
|
||||||
|
| `lib/services/app_settings_service.dart` | App-wide settings (`AppSettings` JSON in SharedPreferences) |
|
||||||
## Placeholder Screens
|
| `lib/services/storage_service.dart` | Path history + delivery observation persistence |
|
||||||
|
| `lib/services/message_retry_service.dart` | ACK tracking + retry scheduling |
|
||||||
The following screens are implemented as placeholders and need full implementation:
|
| `lib/services/translation_service.dart` | On-device LLM translation (llamadart) |
|
||||||
- `chat_screen.dart` - Mesh chat functionality
|
| `lib/storage/prefs_manager.dart` | SharedPreferences singleton initialized in `main()` |
|
||||||
- `contacts_screen.dart` - Contact management
|
| `lib/screens/scanner_screen.dart` | Home screen — BLE scan and connect |
|
||||||
- `settings_screen.dart` - Radio settings, node identity, location (partially implemented)
|
| `pubspec.yaml` | Dependencies and project metadata (current version `8.0.0+11`) |
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# How to contribute to Meshcore Open
|
||||||
|
|
||||||
|
Before submitting any pull requests (PR), please review the following information.
|
||||||
|
|
||||||
|
Unsolicited PRs without previous discussion or open issues may be
|
||||||
|
rejected. As may changes that are too broad (i.e. 100 files changed) or that
|
||||||
|
cover too many separate changes. If the changes are clearly AI generated they
|
||||||
|
may also be rejected. [See more](#ai-use)
|
||||||
|
|
||||||
|
## First Step Checklist
|
||||||
|
|
||||||
|
### **Did you find a bug?**
|
||||||
|
|
||||||
|
* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/zjs81/meshcore-open/issues).
|
||||||
|
|
||||||
|
* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/zjs81/meshcore-open/issues/new).
|
||||||
|
Be sure to include a **title and clear description**, as much relevant
|
||||||
|
information as possible, and a **code sample** or an **executable test case**
|
||||||
|
demonstrating the expected behavior that is not occurring. You can also include
|
||||||
|
screenshots or video.
|
||||||
|
|
||||||
|
* DO NOT start work and submit a PR at this time, please discuss the issue and
|
||||||
|
your implementation plan first.
|
||||||
|
|
||||||
|
### **Did you fix whitespace, format code, or make a purely cosmetic patch?**
|
||||||
|
|
||||||
|
Changes that are cosmetic in nature and do not add anything substantial to the
|
||||||
|
stability, functionality, or testability of the application will generally not
|
||||||
|
be accepted.
|
||||||
|
|
||||||
|
### **Do you intend to add a new feature or change an existing one?**
|
||||||
|
|
||||||
|
* Suggest your change in a new issue as a feature request.
|
||||||
|
|
||||||
|
* DO NOT start work and submit a PR at this time, please discuss the change and
|
||||||
|
your implementation plan first.
|
||||||
|
|
||||||
|
* After it is generally decided that the feature or change fits the goals of the
|
||||||
|
project you can start work or open a PR if you have already started.
|
||||||
|
|
||||||
|
## Submitting your patch
|
||||||
|
|
||||||
|
* All changes should be based on the `dev` branch. When creating your PR please
|
||||||
|
be sure to change the target to merge into dev, and when starting work on a new
|
||||||
|
branch be sure to start on latest `dev`.
|
||||||
|
|
||||||
|
* Ensure the PR description clearly describes the problem and solution. Include
|
||||||
|
the relevant issue number if applicable.
|
||||||
|
|
||||||
|
* The PR should contain **one commit** only, the commit message should have a
|
||||||
|
clear title followed by a new line and then brief description if needed. PR with
|
||||||
|
multiple commits will be squashed into one before merging if required. See
|
||||||
|
[Git Mastery](https://git-mastery.org/lessons/commitMessage/) for more
|
||||||
|
information on good commit messages.
|
||||||
|
|
||||||
|
* **Before committing changes** on your branch, be sure to run both
|
||||||
|
`dart format .` and `flutter analyze`. The continuous development checks will
|
||||||
|
fail if issues here are not addressed before hand.
|
||||||
|
|
||||||
|
## AI-use
|
||||||
|
|
||||||
|
Everyone loves some help, AI agents are a tool in many of our belts. The project
|
||||||
|
is not anti-AI.
|
||||||
|
|
||||||
|
There are some limits to acceptable use however. Generally:
|
||||||
|
|
||||||
|
* All code generated by AI should be thoroughly reviewed by the contributor.
|
||||||
|
* The changes should be tightly controlled to not change anything out of scope
|
||||||
|
for the patch, bug fix, etc.
|
||||||
|
* The contributor should have a good understanding of what the code does and how
|
||||||
|
the application works in order to effectively be able to manage the agent.
|
||||||
@@ -6,6 +6,8 @@ Open-source Flutter client for MeshCore LoRa mesh networking devices.
|
|||||||
|
|
||||||
MeshCore Open is a cross-platform mobile application for communicating with MeshCore LoRa mesh network devices via Bluetooth Low Energy (BLE). The app enables long-range, off-grid communication through peer-to-peer messaging, public channels, and mesh networking capabilities.
|
MeshCore Open is a cross-platform mobile application for communicating with MeshCore LoRa mesh network devices via Bluetooth Low Energy (BLE). The app enables long-range, off-grid communication through peer-to-peer messaging, public channels, and mesh networking capabilities.
|
||||||
|
|
||||||
|
**Website:** [meshcoreopen.org](https://meshcoreopen.org/)
|
||||||
|
|
||||||
<a href="http://apps.obtainium.imranr.dev/redirect.html?r=obtainium://add/https://github.com/zjs81/meshcore-open">
|
<a href="http://apps.obtainium.imranr.dev/redirect.html?r=obtainium://add/https://github.com/zjs81/meshcore-open">
|
||||||
<img src="assets/badges/badge_obtainium.png" height="80" align="center" alt="Get it on Obtainium"/>
|
<img src="assets/badges/badge_obtainium.png" height="80" align="center" alt="Get it on Obtainium"/>
|
||||||
</a>
|
</a>
|
||||||
@@ -51,7 +53,7 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
|
|||||||
|
|
||||||
### Device Management
|
### Device Management
|
||||||
|
|
||||||
- **BLE Connection**: Scan and connect to MeshCore devices via Bluetooth
|
- **BLE, USB, TCP Connection**: Scan and connect to MeshCore devices via Bluetooth, USB or TCP
|
||||||
- **Device Settings**: Configure radio parameters, power settings, and network options
|
- **Device Settings**: Configure radio parameters, power settings, and network options
|
||||||
- **Battery Monitoring**: Real-time battery status with chemistry-specific voltage curves
|
- **Battery Monitoring**: Real-time battery status with chemistry-specific voltage curves
|
||||||
- **Firmware Updates**: Over-the-air firmware updates via BLE (coming soon)
|
- **Firmware Updates**: Over-the-air firmware updates via BLE (coming soon)
|
||||||
@@ -75,10 +77,16 @@ MeshCore Open is a cross-platform mobile application for communicating with Mesh
|
|||||||
|
|
||||||
### Platform Support
|
### Platform Support
|
||||||
|
|
||||||
- ✅ **Android**: Full support (API 21+)
|
| Feature | Android (API 21+) | iOS (12+) | Linux | Windows | macOS | Web |
|
||||||
- ✅ **iOS**: Full support (iOS 12+)
|
|--------------------|:-----------------:|:---------:|:-----:|:-------:|:-----:|:---------------------------------:|
|
||||||
- 🚧 **Desktop**: Limited support (macOS/Linux/Windows)
|
| BLE companion | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
- 🚧 **Web**: Under construction (Chrome)
|
| USB companion | ✅ | 🚧 | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| TCP companion | ✅ | 🚧 | ✅ | ✅ | ✅ | ❌<br>(requires websocket bridge) |
|
||||||
|
| Core Functionality | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| Mesh Network | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| Map & Location | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| Device Management | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| Repeater Hub | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
@@ -144,7 +152,8 @@ lib/
|
|||||||
├── main.dart # App entry point
|
├── main.dart # App entry point
|
||||||
├── connector/
|
├── connector/
|
||||||
│ ├── meshcore_connector.dart # BLE communication & state management
|
│ ├── meshcore_connector.dart # BLE communication & state management
|
||||||
│ └── meshcore_protocol.dart # Protocol definitions & frame parsing
|
│ ├── meshcore_protocol.dart # Protocol definitions & frame parsing
|
||||||
|
│ └── meshcore_uuids.dart # Device names and IDs (add prefixes here!)
|
||||||
├── screens/
|
├── screens/
|
||||||
│ ├── scanner_screen.dart # Device scanning (home screen)
|
│ ├── scanner_screen.dart # Device scanning (home screen)
|
||||||
│ ├── contacts_screen.dart # Contact list
|
│ ├── contacts_screen.dart # Contact list
|
||||||
@@ -178,7 +187,16 @@ lib/
|
|||||||
|
|
||||||
### Device Discovery
|
### Device Discovery
|
||||||
|
|
||||||
Devices are discovered by scanning for BLE advertisements with the name prefix `MeshCore-`
|
Devices are discovered by scanning for BLE advertisements with known MeshCore device name prefixes. These are currently:
|
||||||
|
- `MeshCore-`
|
||||||
|
- `Whisper-`
|
||||||
|
- `WisCore-`
|
||||||
|
- `HT-`
|
||||||
|
- `LowMesh_MC_`
|
||||||
|
- `NRF52`
|
||||||
|
|
||||||
|
New device prefixes can be added in `lib/connector/meshcore_uuids.dart`.
|
||||||
|
|
||||||
|
|
||||||
### Message Format
|
### Message Format
|
||||||
|
|
||||||
@@ -189,6 +207,7 @@ Messages are transmitted as binary frames using a custom protocol optimized for
|
|||||||
### App Settings
|
### App Settings
|
||||||
|
|
||||||
- **Theme**: System default, light, or dark mode
|
- **Theme**: System default, light, or dark mode
|
||||||
|
- **Language**: Use one of 15 languages (English, Chinese, French, Spanish, Portuguese, German, Dutch, Polish, Swedish, Italian, Slovak, Slovene, Bulgarian, Russian, Ukrainian)
|
||||||
- **Notifications**: Configurable for messages, channels, and node advertisements
|
- **Notifications**: Configurable for messages, channels, and node advertisements
|
||||||
- **Battery Chemistry**: Support for NMC, LiFePO4, and LiPo battery types
|
- **Battery Chemistry**: Support for NMC, LiFePO4, and LiPo battery types
|
||||||
- **Message Retry**: Automatic retry with configurable path clearing
|
- **Message Retry**: Automatic retry with configurable path clearing
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ if (keystorePropertiesFile.exists()) {
|
|||||||
android {
|
android {
|
||||||
namespace = "com.meshcore.meshcore_open"
|
namespace = "com.meshcore.meshcore_open"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = "29.0.14206865"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
|||||||
@@ -21,7 +21,12 @@ The MeshCore BLE protocol implements a binary frame-based communication system u
|
|||||||
|
|
||||||
### Connection Flow
|
### Connection Flow
|
||||||
|
|
||||||
1. **Scan** for devices with name prefix `MeshCore-`
|
1. **Scan** for devices with known name prefixes (defined in `MeshCoreUuids.deviceNamePrefixes`):
|
||||||
|
- `MeshCore-`
|
||||||
|
- `Whisper-`
|
||||||
|
- `WisCore-`
|
||||||
|
- `HT-`
|
||||||
|
- `LowMesh_MC_`
|
||||||
2. **Connect** with 15-second timeout
|
2. **Connect** with 15-second timeout
|
||||||
3. **Request MTU** of 185 bytes (falls back to default if unsupported)
|
3. **Request MTU** of 185 bytes (falls back to default if unsupported)
|
||||||
4. **Discover services** and locate NUS characteristics
|
4. **Discover services** and locate NUS characteristics
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# MeshCore Open - Feature Documentation
|
||||||
|
|
||||||
|
MeshCore Open is an open-source Flutter client for MeshCore LoRa mesh networking devices. This documentation covers every user-facing feature, how to access it, and what it does.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Scanner & Connection](scanner-and-connection.md) - BLE scanning, USB serial, and TCP connection
|
||||||
|
2. [Navigation](navigation.md) - App flow, device screen, and quick-switch navigation
|
||||||
|
3. [Contacts](contacts.md) - Contact management, groups, discovery, and sharing
|
||||||
|
4. [Chat & Messaging](chat-and-messaging.md) - Direct messages, message status, reactions, and retries
|
||||||
|
5. [Channels](channels.md) - Broadcast channels, communities, and channel chat
|
||||||
|
6. [Map & Location](map-and-location.md) - Node map, path tracing, line-of-sight, and offline caching
|
||||||
|
7. [Settings](settings.md) - Device settings, app settings, radio configuration, and exports
|
||||||
|
8. [Notifications](notifications.md) - System notifications, unread badges, and notification preferences
|
||||||
|
9. [Repeater Management](repeater-management.md) - Repeater hub, status, CLI, telemetry, and neighbors
|
||||||
|
10. [Additional Features](additional-features.md) - GIF picker, localization, debug logs, SMAZ compression, and more
|
||||||
|
11. [BLE Protocol & Data Layer](ble-protocol.md) - Technical reference for the communication protocol and data architecture
|
||||||
|
|
||||||
|
## App Overview
|
||||||
|
|
||||||
|
MeshCore Open connects to MeshCore LoRa mesh radios over BLE, USB, or TCP. Once connected, users can:
|
||||||
|
|
||||||
|
- **Chat** with other mesh nodes via encrypted direct messages
|
||||||
|
- **Broadcast** on shared channels (public, hashtag, private, or community-scoped)
|
||||||
|
- **View nodes on a map** with GPS locations, predicted positions, and path traces
|
||||||
|
- **Manage repeaters** with CLI access, telemetry, neighbor info, and settings
|
||||||
|
- **Share contacts** via `meshcore://` URIs and QR codes
|
||||||
|
- **Configure radio settings** including frequency, power, bandwidth, and spreading factor
|
||||||
|
- **Cache offline maps** for use without internet connectivity
|
||||||
|
- **Analyze line-of-sight** between nodes with terrain elevation profiles
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
# Additional Features
|
||||||
|
|
||||||
|
## GIF Picker (Giphy Integration)
|
||||||
|
|
||||||
|
### How to Access
|
||||||
|
In any chat screen (direct or channel), tap the GIF button in the message input bar.
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
A bottom sheet with a search field and a grid of GIF thumbnails.
|
||||||
|
|
||||||
|
### Key Interactions
|
||||||
|
- On open, loads trending GIFs (G-rated, 25 results)
|
||||||
|
- Type to search and press the keyboard submit button (search triggers on submit, not on each keystroke). Clearing the search field reloads trending GIFs
|
||||||
|
- On network/API errors, a "Retry" button is shown in-place
|
||||||
|
- Tap a GIF to select it — the chat input shows an inline preview with an X button to dismiss
|
||||||
|
- Send the message to transmit the GIF reference (`g:<giphy-id>`)
|
||||||
|
- Recipients see the GIF rendered inline via Giphy CDN
|
||||||
|
- "Powered by Giphy" attribution is always shown at the bottom of the picker
|
||||||
|
- The bottom sheet occupies 70% of screen height
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Localization / Multi-Language Support
|
||||||
|
|
||||||
|
### How to Access
|
||||||
|
App Settings → Appearance → Language
|
||||||
|
|
||||||
|
### Supported Languages (18)
|
||||||
|
English, French, Spanish, German, Polish, Slovenian, Portuguese, Italian, Chinese, Swedish, Dutch, Slovak, Bulgarian, Russian, Ukrainian, Hungarian, Japanese, Korean
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
- All UI strings go through Flutter's ARB localization system
|
||||||
|
- Language can follow the system locale or be explicitly overridden
|
||||||
|
- Changes take effect immediately
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Discovered Contacts Screen
|
||||||
|
|
||||||
|
### How to Access
|
||||||
|
From Contacts screen → overflow menu → "Discovered Contacts"
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
A list of nodes heard passively over the air but not yet added as contacts. Each shows:
|
||||||
|
- Color-coded avatar (by type)
|
||||||
|
- Name
|
||||||
|
- Short public key
|
||||||
|
- Last-seen time
|
||||||
|
|
||||||
|
### Key Interactions
|
||||||
|
- Search bar with debounced filtering
|
||||||
|
- Sort by last seen or name; filter by type
|
||||||
|
- **Tap**: Import the contact (adds to your contact list)
|
||||||
|
- **Long-press**: Add Contact, Copy `meshcore://` URI to clipboard, or Delete
|
||||||
|
- Overflow menu → "Delete All" (with confirmation)
|
||||||
|
- Already-known contacts and your own node are filtered out
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SMAZ Compression
|
||||||
|
|
||||||
|
### What It Is
|
||||||
|
An optional per-contact and per-channel text compression feature using the SMAZ algorithm (optimized for short English text).
|
||||||
|
|
||||||
|
### How to Enable
|
||||||
|
- **Per contact**: Chat screen → info button → toggle "SMAZ compression"
|
||||||
|
- **Per channel**: Long-press channel → Edit → toggle "SMAZ compression"
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
- When enabled, compression is applied using a "compress only if smaller" strategy — the message is only transmitted compressed if the encoded result is actually shorter than the original. Otherwise, the original text is sent uncompressed
|
||||||
|
- Compressed messages are transmitted with a `s:` prefix followed by base64-encoded data
|
||||||
|
- Recipients using MeshCore Open will decompress automatically. **Recipients using other software** that is not SMAZ-aware will see garbled `s:...` text
|
||||||
|
- The codec operates on ASCII. Non-ASCII / non-English text generally does not benefit from compression and may even expand. Best suited for short English messages
|
||||||
|
- Disabled by default
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Community QR Scanner
|
||||||
|
|
||||||
|
### How to Access
|
||||||
|
From Channels screen → "+" FAB → "Scan Community QR"
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
A live QR scanner view with instruction text overlay.
|
||||||
|
|
||||||
|
### Key Interactions
|
||||||
|
- Scan a community QR code shared by another member
|
||||||
|
- On valid scan: confirmation dialog showing community name and ID
|
||||||
|
- Option to "Add public channel to device" on join
|
||||||
|
- If already a member: shows an "Already a member" dialog
|
||||||
|
- Invalid QR: shows an orange error snackbar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Channel Message Path Viewing
|
||||||
|
|
||||||
|
### How to Access
|
||||||
|
In a channel chat, tap a message bubble (mobile) or use the "Path" action (desktop).
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
- Summary card: sender, time, repeat count, path type, observed hops
|
||||||
|
- "Other Observed Paths" section (if multiple paths detected)
|
||||||
|
- "Repeater Hops" section listing each hop with hex prefix, resolved name, and GPS coordinates
|
||||||
|
|
||||||
|
### Actions
|
||||||
|
- **Radar icon**: Opens path trace map for live trace
|
||||||
|
- **Map icon**: Opens a map with hop markers and polyline
|
||||||
|
- **Path dropdown**: Switch between observed path variants (if multiple)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Debug Logging
|
||||||
|
|
||||||
|
### BLE Debug Log
|
||||||
|
**Access**: Settings → BLE Debug Log
|
||||||
|
|
||||||
|
Two views:
|
||||||
|
- **Frames**: Each BLE frame with direction, description, hex preview, timestamp. Long-press to copy hex.
|
||||||
|
- **Raw Log RX**: Decoded LoRa packets with route type, payload type, path bytes, and summary.
|
||||||
|
|
||||||
|
### App Debug Log
|
||||||
|
**Access**: Settings → App Debug Log (must be enabled first in App Settings → Debug)
|
||||||
|
|
||||||
|
Structured log entries with level (Info/Warning/Error), tag, message, and timestamp.
|
||||||
|
|
||||||
|
Both logs support copy-all and clear operations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Chrome Required Screen
|
||||||
|
|
||||||
|
### When It Appears
|
||||||
|
Automatically shown on web platforms when a non-Chromium browser is detected.
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
A full-screen informational page explaining that Web Bluetooth requires a Chromium-based browser. No interactive elements — purely informational.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Path History Service
|
||||||
|
|
||||||
|
### What It Does (Background Service)
|
||||||
|
Maintains an in-memory LRU cache of up to 50 contacts, each with up to 100 route history entries, tracking:
|
||||||
|
- Hop count and trip time
|
||||||
|
- Success/failure counts and route weights
|
||||||
|
- Flood vs. direct discovery
|
||||||
|
|
||||||
|
### Path Scoring
|
||||||
|
Paths are scored using a weighted formula: reliability (45%), route weight (20%), latency (25%), and freshness (10%). These weights are internal and not user-configurable. Paths whose weight drops to zero or below are automatically deleted. Flood deliveries that receive an ACK give a weight boost (+0.5) to the specific return path.
|
||||||
|
|
||||||
|
Used internally for:
|
||||||
|
- **Auto route rotation**: Cycles through known paths using configurable weights on retries, with a diversity window to avoid re-using recently tried paths
|
||||||
|
- **Path selection**: Picks the best-scored path for each retry attempt
|
||||||
|
- **Flood statistics**: Tracks flood vs. direct discovery ratios
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Message Retry Service
|
||||||
|
|
||||||
|
### What It Does (Background Service)
|
||||||
|
Handles reliable delivery of outgoing direct messages:
|
||||||
|
1. Assigns a UUID and sends immediately. Only one message per contact can be in-flight at a time (avoids overflowing the firmware's 8-entry ACK table); subsequent messages are queued
|
||||||
|
2. Listens for ACK frames matched via SHA-256 hash of `[timestamp][attempt][text][sender_pubkey]`
|
||||||
|
3. On timeout, retries with exponential backoff: `1000 × 2^retryCount` ms (1s, 2s, 4s, 8s...)
|
||||||
|
4. Each retry may use a different path (via path history diversity window)
|
||||||
|
5. After max retries: marks failed but keeps a **30-second grace window** during which a late ACK can still resolve the message to "delivered". Optionally clears the contact's path
|
||||||
|
6. Reports RTT and path data for quality learning
|
||||||
|
7. Maintains an ACK hash history (last 50 entries) to handle duplicate ACKs
|
||||||
|
|
||||||
|
### Configurable Settings (App Settings → Messaging)
|
||||||
|
- Max retries (2–10, default 5)
|
||||||
|
- Clear path on max retry (on/off)
|
||||||
|
- Auto route rotation with weight parameters
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Timeout Prediction (ML)
|
||||||
|
|
||||||
|
### What It Does (Background Service)
|
||||||
|
An ML-based service that predicts expected delivery timeouts:
|
||||||
|
- Collects delivery observations (path length, message size, time since last RX, delivery time) in a sliding window of up to 100 observations (oldest evicted first)
|
||||||
|
- Requires **10 minimum observations** before first training. After that, retrains every 5 new observations
|
||||||
|
- Applies a **1.5x safety margin** to raw predictions (the actual timeout issued is 1.5× the model's predicted delivery time)
|
||||||
|
- Features with zero variance are automatically excluded from training
|
||||||
|
- Blends per-contact statistics with ML predictions
|
||||||
|
- Falls back to `3000 + 3000 × pathLength` ms when insufficient data
|
||||||
|
- Observations are persisted to storage via a 2-second debounced timer (observations within 2s of app termination may be lost)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## On-Device Message Translation
|
||||||
|
|
||||||
|
### What It Is
|
||||||
|
An optional on-device translation service powered by an embedded LLM (llamadart, running GGUF models). Translation runs entirely on-device — no data leaves the app.
|
||||||
|
|
||||||
|
### How to Access
|
||||||
|
Tap the translate button on any received message. On first use, the GGUF model file is downloaded and cached locally.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
- Model files are managed by `TranslationFileStore`; download progress is shown in-place
|
||||||
|
- Translation runs via `TranslationService` using the llamadart CPU backend (arm64 and x64 on Android)
|
||||||
|
- Translated text is shown in `TranslatedMessageContent` as an inline overlay on the original message bubble
|
||||||
|
- Each translation is cached; re-tapping shows the cached result without re-running inference
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Emoji Reactions
|
||||||
|
|
||||||
|
### How to Access
|
||||||
|
Long-press a message bubble in any direct or channel chat, then select a reaction emoji.
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
An emoji picker inline with common reactions. Selected reactions appear below the message bubble with a count.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
- Implemented via `emoji_picker.dart` and `reaction_helper.dart`
|
||||||
|
- Reactions are transmitted as a special message type visible to all participants with MeshCore Open
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Linkification
|
||||||
|
|
||||||
|
### What It Does
|
||||||
|
URLs and `meshcore://` URIs in received messages are automatically detected and rendered as tappable links.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
- Powered by the `flutter_linkify` package via `link_handler.dart`
|
||||||
|
- Tapping a URL opens the system browser; tapping a `meshcore://` URI imports the contact
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GPX Export
|
||||||
|
|
||||||
|
### How to Access
|
||||||
|
Settings → Export section (three options: Export Repeaters, Export Contacts, Export All).
|
||||||
|
|
||||||
|
### What It Does
|
||||||
|
Exports contacts with GPS coordinates to a `.gpx` file via the OS share sheet. Not available on web.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pinch-to-Zoom Chat Text
|
||||||
|
|
||||||
|
### What It Does
|
||||||
|
Users can pinch to scale all chat text up or down within a session.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
- Implemented via `ChatTextScaleService` and `ChatZoomWrapper`
|
||||||
|
- Scale range: 0.8× to 1.8×
|
||||||
|
- The chosen scale persists across the session via the service
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Background Service (Android)
|
||||||
|
|
||||||
|
### What It Does
|
||||||
|
On Android, a foreground service (`background_service.dart`) keeps the BLE connection and message handling alive when the app is in the background. On other platforms this is a no-op.
|
||||||
|
|
||||||
|
### User Impact
|
||||||
|
- A persistent notification appears while the service is running
|
||||||
|
- Messages are received and retry logic continues even when the app is not in the foreground
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
# BLE Protocol & Data Layer
|
||||||
|
|
||||||
|
This is a technical reference for the communication protocol and data architecture.
|
||||||
|
|
||||||
|
## Transport Layer
|
||||||
|
|
||||||
|
The app supports three transports, all sharing the same command/response protocol:
|
||||||
|
|
||||||
|
| Transport | Method | Implementation |
|
||||||
|
|---|---|---|
|
||||||
|
| Bluetooth LE | Nordic UART Service (NUS) GATT | `flutter_blue_plus` |
|
||||||
|
| USB Serial | Packet-framed serial | `MeshCoreUsbManager` |
|
||||||
|
| TCP | Packet-framed socket | `MeshCoreTcpConnector` |
|
||||||
|
|
||||||
|
### BLE (Nordic UART Service)
|
||||||
|
|
||||||
|
- **Service UUID**: `6e400001-b5a3-f393-e0a9-e50e24dcca9e`
|
||||||
|
- **RX Characteristic** (write to device): `6e400002-b5a3-f393-e0a9-e50e24dcca9e`
|
||||||
|
- **TX Characteristic** (notify from device): `6e400003-b5a3-f393-e0a9-e50e24dcca9e`
|
||||||
|
|
||||||
|
Raw `Uint8List` payloads are written directly to the RX characteristic. Writes use "write without response" if supported, falling back to "write with response".
|
||||||
|
|
||||||
|
### USB and TCP Framing
|
||||||
|
|
||||||
|
Both use a lightweight packet framing codec:
|
||||||
|
|
||||||
|
```
|
||||||
|
TX (host → device): [0x3C][len_lo][len_hi][payload...]
|
||||||
|
RX (device → host): [0x3E][len_lo][len_hi][payload...]
|
||||||
|
```
|
||||||
|
|
||||||
|
- Frame start: `0x3C` (`<`) for outgoing, `0x3E` (`>`) for incoming
|
||||||
|
- Length: 2-byte little-endian, payload only
|
||||||
|
- Max payload: 172 bytes
|
||||||
|
- TCP: `tcpNoDelay: true` (Nagle disabled), writes serialized to prevent interleaving
|
||||||
|
|
||||||
|
## Connection State Machine
|
||||||
|
|
||||||
|
```
|
||||||
|
enum MeshCoreConnectionState {
|
||||||
|
disconnected,
|
||||||
|
scanning,
|
||||||
|
connecting,
|
||||||
|
connected,
|
||||||
|
disconnecting,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## BLE Connection Lifecycle
|
||||||
|
|
||||||
|
1. **Scan** with known name prefixes (defined in `MeshCoreUuids.deviceNamePrefixes`):
|
||||||
|
- `MeshCore-`
|
||||||
|
- `Whisper-`
|
||||||
|
- `WisCore-`
|
||||||
|
- `Seeed`
|
||||||
|
- `Lilygo`
|
||||||
|
- `HT-`
|
||||||
|
- `LowMesh_MC_`
|
||||||
|
2. **Connect** with 15-second timeout (6 seconds on Linux)
|
||||||
|
3. **Request MTU** 185 bytes (non-web only)
|
||||||
|
4. **Discover services** and locate NUS
|
||||||
|
5. **Enable TX notifications** (up to 3 attempts on native)
|
||||||
|
6. **Subscribe** to TX characteristic for incoming frames
|
||||||
|
7. **Initial sync**: device info query, time sync, channel sync
|
||||||
|
|
||||||
|
## Auto-Reconnect (BLE Only)
|
||||||
|
|
||||||
|
On unexpected disconnection, auto-reconnect with exponential backoff:
|
||||||
|
- Delays: 1s, 2s, 4s, 8s, 16s, 30s, 30s...
|
||||||
|
- Resets on successful connection
|
||||||
|
- Disabled for manual disconnects
|
||||||
|
- Not available for USB or TCP
|
||||||
|
|
||||||
|
## Protocol Constants
|
||||||
|
|
||||||
|
| Constant | Value | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| Max frame size | 172 bytes | BLE/USB/TCP payload limit |
|
||||||
|
| Public key size | 32 bytes | Ed25519 public key |
|
||||||
|
| Max path size | 64 bytes | Maximum path data |
|
||||||
|
| Max name size | 32 bytes | Maximum node name |
|
||||||
|
| Max text payload | 160 bytes | Firmware `MAX_TEXT_LEN` |
|
||||||
|
| App protocol version | 4 | Sent in device query |
|
||||||
|
| Contact frame size | 148 bytes | Fixed-size contact record |
|
||||||
|
|
||||||
|
## Command Codes (App → Device)
|
||||||
|
|
||||||
|
| Code | Name | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| 1 | CMD_APP_START | Announce app connection |
|
||||||
|
| 2 | CMD_SEND_TXT_MSG | Send direct text message |
|
||||||
|
| 3 | CMD_SEND_CHANNEL_TXT_MSG | Send channel text message |
|
||||||
|
| 4 | CMD_GET_CONTACTS | Request contact list |
|
||||||
|
| 5 | CMD_GET_DEVICE_TIME | Query device clock |
|
||||||
|
| 6 | CMD_SET_DEVICE_TIME | Set device clock |
|
||||||
|
| 7 | CMD_SEND_SELF_ADVERT | Broadcast own advertisement |
|
||||||
|
| 8 | CMD_SET_ADVERT_NAME | Set node name |
|
||||||
|
| 9 | CMD_ADD_UPDATE_CONTACT | Add or update a contact |
|
||||||
|
| 10 | CMD_SYNC_NEXT_MESSAGE | Request next queued message |
|
||||||
|
| 11 | CMD_SET_RADIO_PARAMS | Set radio parameters |
|
||||||
|
| 12 | CMD_SET_RADIO_TX_POWER | Set TX power |
|
||||||
|
| 13 | CMD_RESET_PATH | Reset contact path |
|
||||||
|
| 14 | CMD_SET_ADVERT_LATLON | Set advertised location |
|
||||||
|
| 15 | CMD_REMOVE_CONTACT | Remove a contact |
|
||||||
|
| 16 | CMD_SHARE_CONTACT | Share contact to mesh |
|
||||||
|
| 17 | CMD_EXPORT_CONTACT | Export contact as bytes |
|
||||||
|
| 18 | CMD_IMPORT_CONTACT | Import contact from bytes |
|
||||||
|
| 19 | CMD_REBOOT | Reboot device |
|
||||||
|
| 20 | CMD_GET_BATT_AND_STORAGE | Query battery and storage |
|
||||||
|
| 22 | CMD_DEVICE_QUERY | Query device info |
|
||||||
|
| 26 | CMD_SEND_LOGIN | Login to repeater/room |
|
||||||
|
| 27 | CMD_SEND_STATUS_REQ | Request repeater status |
|
||||||
|
| 30 | CMD_GET_CONTACT_BY_KEY | Get contact by public key |
|
||||||
|
| 31 | CMD_GET_CHANNEL | Get channel definition |
|
||||||
|
| 32 | CMD_SET_CHANNEL | Set channel name and PSK |
|
||||||
|
| 36 | CMD_SEND_TRACE_PATH | Request path trace |
|
||||||
|
| 38 | CMD_SET_OTHER_PARAMS | Set misc parameters |
|
||||||
|
| 39 | CMD_GET_TELEMETRY_REQ | Request sensor telemetry |
|
||||||
|
| 40 | CMD_GET_CUSTOM_VAR | Get custom variables |
|
||||||
|
| 41 | CMD_SET_CUSTOM_VAR | Set a custom variable |
|
||||||
|
| 50 | CMD_SEND_BINARY_REQ | Send binary request |
|
||||||
|
| 57 | CMD_SEND_ANON_REQ | Send anonymous request |
|
||||||
|
| 58 | CMD_SET_AUTO_ADD_CONFIG | Set auto-add configuration |
|
||||||
|
| 59 | CMD_GET_AUTO_ADD_CONFIG | Get auto-add configuration |
|
||||||
|
|
||||||
|
## Response / Push Codes (Device → App)
|
||||||
|
|
||||||
|
| Code | Name | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| 0 | RESP_CODE_OK | Generic success |
|
||||||
|
| 1 | RESP_CODE_ERR | Generic error |
|
||||||
|
| 2 | RESP_CODE_CONTACTS_START | Contact list begins |
|
||||||
|
| 3 | RESP_CODE_CONTACT | Single contact data |
|
||||||
|
| 4 | RESP_CODE_END_OF_CONTACTS | Contact list complete |
|
||||||
|
| 5 | RESP_CODE_SELF_INFO | Device self-info response |
|
||||||
|
| 6 | RESP_CODE_SENT | Message transmitted; carries `[1]=is_flood, [2–5]=ack_hash, [6–9]=estimated_timeout_ms` |
|
||||||
|
| 7 | RESP_CODE_CONTACT_MSG_RECV | Incoming direct message (v2) |
|
||||||
|
| 8 | RESP_CODE_CHANNEL_MSG_RECV | Incoming channel message (v2) |
|
||||||
|
| 10 | RESP_CODE_NO_MORE_MESSAGES | No more queued messages |
|
||||||
|
| 11 | RESP_CODE_EXPORT_CONTACT | Exported contact data |
|
||||||
|
| 9 | RESP_CODE_CURR_TIME | Current device time |
|
||||||
|
| 12 | RESP_CODE_BATT_AND_STORAGE | Battery mV (uint16 LE) + storage used/total (uint32 LE each) |
|
||||||
|
| 13 | RESP_CODE_DEVICE_INFO | Firmware info |
|
||||||
|
| 16 | RESP_CODE_CONTACT_MSG_RECV_V3 | Incoming direct message (v3) |
|
||||||
|
| 17 | RESP_CODE_CHANNEL_MSG_RECV_V3 | Incoming channel message (v3) |
|
||||||
|
| 18 | RESP_CODE_CHANNEL_INFO | Channel definition |
|
||||||
|
| 21 | RESP_CODE_CUSTOM_VARS | Custom variables |
|
||||||
|
| 25 | RESP_CODE_AUTO_ADD_CONFIG | Auto-add flags |
|
||||||
|
| 0x80 | PUSH_CODE_ADVERT | Known contact re-seen |
|
||||||
|
| 0x81 | PUSH_CODE_PATH_UPDATED | Better path found; carries the 32-byte public key of the updated contact |
|
||||||
|
| 0x82 | PUSH_CODE_SEND_CONFIRMED | Delivery ACK from remote; carries ACK hash (4 bytes) + trip time (4 bytes) |
|
||||||
|
| 0x83 | PUSH_CODE_MSG_WAITING | Offline messages queued |
|
||||||
|
| 0x85 | PUSH_CODE_LOGIN_SUCCESS | Repeater/room login succeeded |
|
||||||
|
| 0x86 | PUSH_CODE_LOGIN_FAIL | Repeater/room login failed |
|
||||||
|
| 0x87 | PUSH_CODE_STATUS_RESPONSE | Repeater status response |
|
||||||
|
| 0x88 | PUSH_CODE_LOG_RX_DATA | Radio RX data with SNR (int8, units 1/4 dB), RSSI, and raw radio packet |
|
||||||
|
| 0x89 | PUSH_CODE_TRACE_DATA | Path trace result |
|
||||||
|
| 0x8A | PUSH_CODE_NEW_ADVERT | New node discovered |
|
||||||
|
| 0x8B | PUSH_CODE_TELEMETRY_RESPONSE | Sensor telemetry data |
|
||||||
|
| 0x8C | PUSH_CODE_BINARY_RESPONSE | Binary data response |
|
||||||
|
|
||||||
|
## Data Models
|
||||||
|
|
||||||
|
### Contact
|
||||||
|
32-byte public key (primary identity), name, type (chat/repeater/room/sensor), flags, path data, GPS coordinates, last-seen timestamp. Parsed from 148-byte firmware frames with this layout:
|
||||||
|
|
||||||
|
```
|
||||||
|
[0] = resp_code
|
||||||
|
[1–32] = public key (32 bytes)
|
||||||
|
[33] = type (1=chat, 2=repeater, 3=room, 4=sensor)
|
||||||
|
[34] = flags (bit 0 = favorite)
|
||||||
|
[35] = path_length
|
||||||
|
[36–99] = path (64 bytes)
|
||||||
|
[100–131] = name (32 bytes, null-padded)
|
||||||
|
[132–135] = timestamp (uint32 LE)
|
||||||
|
[136–139] = latitude (int32 LE, × 1e-6 degrees)
|
||||||
|
[140–143] = longitude (int32 LE, × 1e-6 degrees)
|
||||||
|
[144–147] = last_modified (uint32 LE)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Message (Direct)
|
||||||
|
Sender key, text, timestamp, outgoing flag, status (pending/sent/delivered/failed), message ID (UUID), retry count, ACK hash, trip time, path data, reactions.
|
||||||
|
|
||||||
|
### Channel Message
|
||||||
|
Sender name, text, timestamp, status (pending/sent/failed), repeater hops, path variants, channel index, reactions, reply threading fields.
|
||||||
|
|
||||||
|
### Channel
|
||||||
|
Index (0–7), name, 16-byte PSK, unread count. PSK derivation methods for hashtag (SHA-256) and community (HMAC-SHA256) channels.
|
||||||
|
|
||||||
|
### Community
|
||||||
|
UUID, name, 32-byte secret, hashtag channel list. Shared via QR code.
|
||||||
|
|
||||||
|
## Persistence
|
||||||
|
|
||||||
|
All data is stored via `SharedPreferences` (JSON-serialized). No SQLite or other database.
|
||||||
|
|
||||||
|
| Data | Storage Key Pattern | Scope |
|
||||||
|
|---|---|---|
|
||||||
|
| Contacts | `contacts<pubKey10>` | Per device identity |
|
||||||
|
| Messages | `messages_<pubKey10><contactKey>` | Per device + contact |
|
||||||
|
| Channel Messages | `channel_messages_<pubKey10><index>` | Per device + channel |
|
||||||
|
| Channels | `channels<pubKey10>` | Per device identity |
|
||||||
|
| Channel Order | `channel_order_<pubKey10>` | Per device identity |
|
||||||
|
| Contact Groups | `contact_groups<pubKey10>` | Per device identity |
|
||||||
|
| Communities | `communities_v1<pubKey10>` | Per device identity |
|
||||||
|
| Unread Counts | `contact_unread_count<pubKey10>` | Per device identity |
|
||||||
|
| Discovered Contacts | `discovered_contacts` | Global |
|
||||||
|
| App Settings | `app_settings` | Global |
|
||||||
|
| Path History | `path_history_<contactKey>` | Per contact |
|
||||||
|
|
||||||
|
## Auto-Add Configuration Bitmask
|
||||||
|
|
||||||
|
Used by `CMD_SET_AUTO_ADD_CONFIG` (58) and `RESP_CODE_AUTO_ADD_CONFIG` (25):
|
||||||
|
|
||||||
|
| Bit | Flag | Description |
|
||||||
|
|-----|------|-------------|
|
||||||
|
| 0 | 0x01 | Overwrite oldest contact when list is full |
|
||||||
|
| 1 | 0x02 | Auto-add chat users |
|
||||||
|
| 2 | 0x04 | Auto-add repeaters |
|
||||||
|
| 3 | 0x08 | Auto-add room servers |
|
||||||
|
| 4 | 0x10 | Auto-add sensors |
|
||||||
|
|
||||||
|
## Radio Packet Payload Types
|
||||||
|
|
||||||
|
Seen inside `PUSH_CODE_LOG_RX_DATA` raw packets:
|
||||||
|
|
||||||
|
| Code | Type |
|
||||||
|
|------|------|
|
||||||
|
| 0x00 | REQ (request) |
|
||||||
|
| 0x01 | RESPONSE |
|
||||||
|
| 0x02 | TXTMSG (text message) |
|
||||||
|
| 0x03 | ACK |
|
||||||
|
| 0x04 | ADVERT |
|
||||||
|
| 0x05 | GRPTXT (group/channel text) |
|
||||||
|
| 0x06 | GRPDATA (group data) |
|
||||||
|
| 0x07 | ANONREQ (anonymous request) |
|
||||||
|
| 0x08 | PATH |
|
||||||
|
| 0x09 | TRACE |
|
||||||
|
| 0x0A | MULTIPART |
|
||||||
|
| 0x0B | CONTROL |
|
||||||
|
| 0x0F | RAW_CUSTOM |
|
||||||
|
|
||||||
|
## State Management
|
||||||
|
|
||||||
|
Uses Flutter `Provider` with `ChangeNotifier`. The central state holder is `MeshCoreConnector`, which owns all in-memory collections and fires debounced (50ms) `notifyListeners()` to update the UI. In-memory conversations are windowed to 200 messages per contact; older messages remain on disk and are loaded on demand.
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
|
||||||
|
1. Raw frames arrive over BLE/USB/TCP
|
||||||
|
2. First byte is parsed as response/push code
|
||||||
|
3. Appropriate model factory (`fromFrame()`) parses the data
|
||||||
|
4. In-memory collections are updated
|
||||||
|
5. Storage stores are persisted (async)
|
||||||
|
6. `notifyListeners()` triggers UI rebuilds
|
||||||
|
7. Screens read current state via getters
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
# Channels
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Channels are broadcast group-chat spaces secured by a 16-byte pre-shared key (PSK). Any device with the same channel index and PSK will receive and decrypt channel messages. Unlike direct messages, channel messages are broadcast to the entire mesh.
|
||||||
|
|
||||||
|
Up to 8 channels (indices 0–7) can be active simultaneously on one device.
|
||||||
|
|
||||||
|
## How to Access
|
||||||
|
|
||||||
|
QuickSwitchBar tab 1 (middle) from any main screen.
|
||||||
|
|
||||||
|
## Channel Types
|
||||||
|
|
||||||
|
| Type | Icon | Color | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Public | Globe | Green | Fixed well-known PSK; any device can join |
|
||||||
|
| Hashtag | Hash tag | Blue | PSK derived from the hashtag name via SHA-256; discoverable by convention |
|
||||||
|
| Private | Lock | Blue | Random PSK; requires out-of-band sharing of the 32-hex key |
|
||||||
|
| Community | Groups/Tag | Purple | PSK derived via HMAC-SHA256 from a community's shared secret |
|
||||||
|
|
||||||
|
## Channels List Screen
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
|
||||||
|
- **Search bar** with live text filtering (300ms debounce)
|
||||||
|
- **Sort/filter button**
|
||||||
|
- **Scrollable list of channel cards**, each showing:
|
||||||
|
- Type icon with color coding (purple badge overlay for community channels)
|
||||||
|
- Channel name (or "Channel N" if unnamed)
|
||||||
|
- Unread badge (if messages are unread)
|
||||||
|
- Drag handle (when manual sort is active)
|
||||||
|
- **"+" FAB** to add a new channel
|
||||||
|
- **Overflow menu**: Disconnect, Manage Communities (only shown when at least one community exists), Settings
|
||||||
|
|
||||||
|
If no channels exist, an empty state with an "Add Public Channel" shortcut is shown. If a search produces no results, a separate "no results" empty state with a search-off icon is shown.
|
||||||
|
|
||||||
|
Pull-to-refresh (swipe down) forces a re-fetch of channels from the device firmware.
|
||||||
|
|
||||||
|
### Sorting Options
|
||||||
|
|
||||||
|
- **Manual** (default): Drag-and-drop reordering, persisted (drag handles are hidden when a search query is active)
|
||||||
|
- **A–Z**: Alphabetical
|
||||||
|
- **Latest messages**: Most recent first
|
||||||
|
- **Unread**: Most unread first
|
||||||
|
|
||||||
|
## Adding a Channel
|
||||||
|
|
||||||
|
Tap the "+" FAB to open a dialog with six options:
|
||||||
|
|
||||||
|
1. **Create Private Channel** — Enter a name (max 31 characters); a random PSK is generated
|
||||||
|
2. **Join Private Channel** — Enter a name and a 32-hex PSK (non-hex characters like spaces and dashes are silently stripped, so pasted keys with formatting are accepted)
|
||||||
|
3. **Join Public Channel** — One tap; uses the well-known public PSK (only shown if no public channel exists)
|
||||||
|
4. **Join Hashtag Channel** — Enter a hashtag name; PSK is derived from the name. If communities exist, choose between regular hashtag (SHA-256) or community hashtag (HMAC)
|
||||||
|
5. **Scan Community QR** — Opens QR scanner to join a community
|
||||||
|
6. **Create Community** — Enter a name; generates a random 32-byte secret; optionally adds a community public channel; shows QR code for sharing
|
||||||
|
|
||||||
|
## Channel Actions (Long-Press / Right-Click)
|
||||||
|
|
||||||
|
| Action | Description |
|
||||||
|
|---|---|
|
||||||
|
| Edit | Change name, PSK (with a dice icon to generate a random PSK), or SMAZ compression toggle (compresses outgoing messages to allow longer text within the byte limit) |
|
||||||
|
| Mute / Unmute | Toggle push notification suppression for this channel |
|
||||||
|
| Delete | Remove the channel from the device (confirmation required) |
|
||||||
|
|
||||||
|
## Channel Chat
|
||||||
|
|
||||||
|
Tap a channel card to open the channel chat screen.
|
||||||
|
|
||||||
|
### App Bar
|
||||||
|
|
||||||
|
- Type icon: globe for public channels, tag (#) for all other channel types
|
||||||
|
- Channel name
|
||||||
|
- Subtitle: "{Public|Private} • {N} unread" (e.g., "Public • 3 unread")
|
||||||
|
|
||||||
|
### Message Display
|
||||||
|
|
||||||
|
- Reverse-scrolling list (newest at bottom)
|
||||||
|
- **Incoming messages**: Colored avatar with sender's initial (or first emoji if name starts with one; color is deterministic from sender name hash), sender name in primary color, message bubble
|
||||||
|
- **Outgoing messages**: Primary container color bubble with a small status icon: pending (clock), sent (checkmark), or failed (red error circle)
|
||||||
|
- Automatic older-message loading on scroll-to-top
|
||||||
|
- Jump-to-bottom button when scrolled up
|
||||||
|
- **Pinch-to-zoom**: Two-finger zoom (0.8x–1.8x) and double-tap to reset text size
|
||||||
|
- **Message tracing mode** (when enabled in App Settings): Each bubble additionally shows path prefix bytes (`via XX,YY,...`), a timestamp, and a repeat count icon
|
||||||
|
|
||||||
|
### Message Types in Chat
|
||||||
|
|
||||||
|
- **Plain text** with linkified URLs
|
||||||
|
- **GIFs** (`g:{gifId}`) rendered inline via Giphy CDN
|
||||||
|
- **Location pins** (`m:{lat},{lon}|{label}|`) shown as tappable location cards
|
||||||
|
- **Reactions** displayed as emoji pills below target messages
|
||||||
|
|
||||||
|
### Replies (Channel Chat Only)
|
||||||
|
|
||||||
|
- **Mobile**: Swipe an **incoming** message left to trigger reply (with haptic feedback). You cannot swipe your own outgoing messages. Swipe reply is not available on desktop.
|
||||||
|
- **All platforms**: Long-press → "Reply"
|
||||||
|
- Reply banner appears above the input bar with the quoted message (tap X to cancel)
|
||||||
|
- Sent replies are prefixed `@[{senderName}] {text}`
|
||||||
|
- Received replies show a bordered quote block inside the bubble; tapping scrolls to the original. Reply previews render GIF thumbnails and location pin icons, not just text.
|
||||||
|
|
||||||
|
### Message Path Viewing
|
||||||
|
|
||||||
|
- **Mobile**: Tap a message bubble to view its routing path
|
||||||
|
- **Desktop**: Long-press/right-click → "Path" (tapping the bubble does nothing on desktop)
|
||||||
|
- Opens the Channel Message Path Screen (see [Additional Features](additional-features.md))
|
||||||
|
|
||||||
|
### Context Actions (Long-Press / Right-Click)
|
||||||
|
|
||||||
|
| Action | Availability | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| Reply | All messages | Triggers reply mode |
|
||||||
|
| Path | Desktop only | Opens message path view |
|
||||||
|
| Add Reaction | Incoming messages only | Opens emoji picker (cannot react to your own messages) |
|
||||||
|
| Copy | All messages | Copies text to clipboard |
|
||||||
|
| Mark as Unread | Incoming messages only | Marks this message and all subsequent incoming messages as unread |
|
||||||
|
| Delete | All messages | Removes locally (not from mesh) |
|
||||||
|
|
||||||
|
## Communities
|
||||||
|
|
||||||
|
Communities are a layer above channels that provide a private namespace.
|
||||||
|
|
||||||
|
### What is a Community?
|
||||||
|
|
||||||
|
A community has a name and a 32-byte random secret. Channel PSKs are derived from this secret:
|
||||||
|
- **Public channel**: `HMAC-SHA256(secret, "channel:v1:__public__")[:16]`
|
||||||
|
- **Hashtag channel**: `HMAC-SHA256(secret, "channel:v1:{hashtag}")[:16]`
|
||||||
|
|
||||||
|
Outsiders who don't know the secret cannot discover or join community channels.
|
||||||
|
|
||||||
|
### Sharing a Community
|
||||||
|
|
||||||
|
Communities are shared via QR codes containing a JSON payload:
|
||||||
|
```json
|
||||||
|
{"v": 1, "type": "meshcore_community", "name": "...", "k": "<base64url-secret>"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Managing Communities
|
||||||
|
|
||||||
|
From the channels screen overflow menu → "Manage Communities". Opens a draggable scrollable sheet (resizable 30–90% of screen height):
|
||||||
|
|
||||||
|
- Each community shows its name and a short community ID (first 8 hex characters)
|
||||||
|
- **Tap a community** to directly show its QR code for sharing
|
||||||
|
- **Popup menu** per community:
|
||||||
|
- **Show QR** — displays the QR code for sharing with new members
|
||||||
|
- **Delete** — removes the community locally and deletes all associated device channels (confirmation dialog warns how many channels will be removed)
|
||||||
|
|
||||||
|
## How Channels Differ from Direct Messages
|
||||||
|
|
||||||
|
| Aspect | Channels | Direct Messages |
|
||||||
|
|---|---|---|
|
||||||
|
| Addressing | Broadcast to all nodes with matching PSK | Point-to-point to a specific contact |
|
||||||
|
| Encryption | Shared PSK (symmetric) | Contact's public key (asymmetric) |
|
||||||
|
| Sender identity | Plain text prefix in payload | Verified via public key |
|
||||||
|
| Replies | Supported (swipe or long-press) | Not supported |
|
||||||
|
| Retry mechanism | No automatic retry | Exponential backoff with path rotation |
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
# Chat & Messaging
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The app supports two chat modes:
|
||||||
|
- **Direct messages**: Encrypted point-to-point messages to individual contacts
|
||||||
|
- **Channel messages**: Broadcast messages to shared channels (see [Channels](channels.md))
|
||||||
|
|
||||||
|
This page covers direct messaging. For channel chat, see the Channels documentation.
|
||||||
|
|
||||||
|
## How to Access
|
||||||
|
|
||||||
|
From the Contacts screen, tap any Chat-type contact to open the ChatScreen.
|
||||||
|
|
||||||
|
## Chat Screen Layout
|
||||||
|
|
||||||
|
### App Bar
|
||||||
|
|
||||||
|
- **Title**: Contact name
|
||||||
|
- **Subtitle**: Current routing path label (e.g., "2 hops", "flood (auto)", "direct (forced)") and unread count. Tapping the subtitle shows the full path details.
|
||||||
|
- **Action buttons**:
|
||||||
|
- **Routing mode** (waves icon): Switch between Auto, Direct, and Flood routing
|
||||||
|
- **Path management** (timeline icon): View recent paths with hop count, round-trip time, age, and success count. Paths are color-coded by direct repeater (green/yellow/red/blue for ranked repeaters, grey for unknown). Tap a path to activate it (the device verifies and confirms via snackbar), long-press to view full path details, set custom paths, or force flood mode. A warning banner appears when history reaches 100 entries.
|
||||||
|
- **Info** (info icon): Contact info dialog showing type, path, GPS coordinates, public key, and SMAZ compression toggle
|
||||||
|
|
||||||
|
### Message List
|
||||||
|
|
||||||
|
- Scrollable list with newest messages at the bottom
|
||||||
|
- **Outgoing messages**: Right-aligned, primary color background. **Failed messages** change to a red-toned error container background
|
||||||
|
- **Incoming messages**: Left-aligned, grey background with a colored avatar (initial letter or first emoji of sender name; color is deterministic from a hash of the sender name)
|
||||||
|
- Bubble width capped at 65% of screen width
|
||||||
|
- Hyperlinks rendered as tappable green underlined text
|
||||||
|
- **Pinch-to-zoom**: Two-finger zoom (0.8x–1.8x) and double-tap to reset
|
||||||
|
- **Jump to bottom**: Floating button appears when scrolled away from the bottom
|
||||||
|
- **Lazy loading**: Scrolling to top loads older messages from storage
|
||||||
|
|
||||||
|
### Input Bar
|
||||||
|
|
||||||
|
- **GIF button** (left): Opens GIF picker bottom sheet
|
||||||
|
- **Translation button** (optional, between GIF and text field): Shown only when translation is enabled in App Settings. Tap to configure outgoing-message translation language and on/off toggle.
|
||||||
|
- **Text field** (center): Auto-capitalization, enforces UTF-8 byte limit in real-time
|
||||||
|
- **Send button** (right): Submits the message
|
||||||
|
- On desktop: Enter/Numpad Enter also submits
|
||||||
|
- When a GIF is selected, the text field shows an inline GIF preview with a dismiss button
|
||||||
|
|
||||||
|
## Message Types
|
||||||
|
|
||||||
|
| Type | Wire Format | Display |
|
||||||
|
|---|---|---|
|
||||||
|
| Plain text | Raw UTF-8 string | Inline text with link detection |
|
||||||
|
| GIF | `g:<giphy-id>` | Inline GIF image from Giphy CDN |
|
||||||
|
| Location pin | `m:<lat>,<lon>\|<label>\|...` | Location icon + label; tap to open map |
|
||||||
|
| Reaction | `r:<hash>:<emoji-index>` | Applied to target message as emoji pill |
|
||||||
|
|
||||||
|
## Message Status
|
||||||
|
|
||||||
|
Outgoing messages display a status indicator:
|
||||||
|
|
||||||
|
| Status | Icon | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| Pending | Grey double-check | Queued, waiting for device to transmit (visually identical to Sent) |
|
||||||
|
| Sent | Grey double-check | Device confirmed transmission (visually identical to Pending) |
|
||||||
|
| Delivered | Green double-check | Remote node acknowledged receipt |
|
||||||
|
| Failed | Red X | All retries exhausted |
|
||||||
|
|
||||||
|
### Message Tracing Mode
|
||||||
|
|
||||||
|
When enabled in App Settings, additional metadata appears inside each bubble:
|
||||||
|
- Timestamp (HH:MM)
|
||||||
|
- Retry count (e.g., "Retry 2 of 4") — only shown for outgoing messages where at least one retry has occurred
|
||||||
|
- Status icon (outgoing only)
|
||||||
|
- Round-trip time in seconds (if delivered)
|
||||||
|
|
||||||
|
## Message Length Limits
|
||||||
|
|
||||||
|
- **Direct messages**: 156 bytes (UTF-8) — enforced in real-time by the input formatter
|
||||||
|
- **Channel messages**: 160 minus sender name length minus 2 bytes for the `"<name>: "` prefix
|
||||||
|
- Over-length paste shows a snackbar error
|
||||||
|
|
||||||
|
## Send Queue
|
||||||
|
|
||||||
|
Only one message per contact can be in-flight at a time (to avoid overflowing the firmware's 8-entry ACK table). If you send multiple messages rapidly, they are queued and sent sequentially — each waits for the previous one to be delivered, fail, or exhaust retries before transmitting.
|
||||||
|
|
||||||
|
## Retry Mechanism
|
||||||
|
|
||||||
|
When a direct message is sent:
|
||||||
|
|
||||||
|
1. The app computes an expected ACK hash: `SHA256([timestamp][attempt][text][selfPubKey])[0:4]` — matching the firmware's hash calculation. If SMAZ compression is enabled, the compressed text (not the original) is hashed
|
||||||
|
2. On device acknowledgment (`RESP_CODE_SENT`), the message transitions to "sent" and a timeout timer starts
|
||||||
|
3. **Timeout duration**: Preferably from the ML timeout prediction service; otherwise calculated from LoRa airtime physics: `500 + (airtime × 6 + 250) × (pathLength + 1)` ms for direct paths, `500 + 16 × airtime` ms for flood (airtime is estimated from the radio's current spreading factor, bandwidth, and coding rate)
|
||||||
|
4. On timeout, the message is retried with **exponential backoff**: `1000 × 2^retryCount` ms (1s, 2s, 4s, 8s, 16s...)
|
||||||
|
5. **Max retries**: Configurable (default 5, range 2–10)
|
||||||
|
6. After max retries, the message is marked "failed" — but a **30-second grace window** remains during which a late ACK can still resolve the message to "delivered"
|
||||||
|
7. If **Clear Path on Max Retry** is enabled (App Settings), the contact's stored routing path is automatically cleared when max retries are exhausted
|
||||||
|
8. **Auto route rotation**: When enabled (and no manual path override is set), the retry service uses a diversity window to avoid re-using recently tried paths, cycling through known routes on each attempt
|
||||||
|
|
||||||
|
### Manual Retry
|
||||||
|
|
||||||
|
Long-press a failed message → "Retry" to re-send using the current routing settings.
|
||||||
|
|
||||||
|
## Reactions
|
||||||
|
|
||||||
|
Add emoji reactions to incoming messages (not your own):
|
||||||
|
|
||||||
|
1. Long-press (or right-click on desktop) a message
|
||||||
|
2. Select "Add reaction" from the context menu
|
||||||
|
3. Choose from quick emojis (thumbs up, heart, laugh, party, clap, fire) or browse the full emoji picker
|
||||||
|
4. Reactions appear as pills below the message bubble with emoji and count
|
||||||
|
5. Pending reactions show at 50% opacity with a spinner
|
||||||
|
6. Failed reactions show a red retry icon (tap to retry)
|
||||||
|
|
||||||
|
## Context Actions (Long-Press / Right-Click)
|
||||||
|
|
||||||
|
| Action | Availability | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| Add reaction | Incoming messages only | Opens emoji picker |
|
||||||
|
| View path | Mobile: tap bubble directly; Desktop: long-press/right-click menu | Shows message routing path |
|
||||||
|
| Copy | All messages | Copies text to clipboard |
|
||||||
|
| Mark as Unread | Incoming messages only | Marks this message and all subsequent incoming messages as unread |
|
||||||
|
| Delete | All messages | Removes locally (not from mesh) |
|
||||||
|
| Retry | Failed outgoing messages | Re-sends the message |
|
||||||
|
| Open chat with sender | Room server chats | Opens 1:1 chat with the message sender |
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
# Contacts
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Contacts screen is the primary hub for managing mesh nodes your radio has a relationship with. A "contact" is any node whose cryptographic advertisement has been received — it can be a chat user, repeater, room server, or sensor.
|
||||||
|
|
||||||
|
## How to Access
|
||||||
|
|
||||||
|
- Automatically shown after connecting to a device
|
||||||
|
- QuickSwitchBar tab 0 (leftmost) from Channels or Map screens
|
||||||
|
- Back navigation from Chat or Settings screens
|
||||||
|
|
||||||
|
## Contact Types
|
||||||
|
|
||||||
|
| Type | Avatar Color | Icon | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Chat | Blue | Chat bubble | Another user's mesh radio |
|
||||||
|
| Repeater | Orange | Cell tower | A mesh repeater/relay node |
|
||||||
|
| Room | Purple | Group | A room server for group chat |
|
||||||
|
| Sensor | Green | Sensors | A sensor device |
|
||||||
|
|
||||||
|
## Contact List
|
||||||
|
|
||||||
|
Each contact is displayed as a list tile showing:
|
||||||
|
|
||||||
|
- **Avatar**: Color-coded circle with type icon (or first emoji of the contact's name if it starts with one)
|
||||||
|
- **Name**: Contact name (single line)
|
||||||
|
- **Path label**: "Direct", "N hops", or "Flood" (with forced variants if a path override is active)
|
||||||
|
- **Public key**: Shortened hex format `<XXXXXXXX...XXXXXXXX>`
|
||||||
|
- **Unread badge**: Red pill with count (if unread messages exist)
|
||||||
|
- **Last seen**: Relative timestamp ("Now", "5 mins ago", "2 hours ago", "3 days ago"). For chat contacts, this shows whichever is more recent: the last advertisement time or the last message time
|
||||||
|
- **Favorite star**: Amber star icon if favorited
|
||||||
|
- **Location pin**: Grey pin icon if the contact has GPS coordinates
|
||||||
|
|
||||||
|
Pull-to-refresh re-fetches the full contact list from the device.
|
||||||
|
|
||||||
|
## Search and Filter
|
||||||
|
|
||||||
|
A toolbar at the top provides:
|
||||||
|
|
||||||
|
**Search**: Matches contact name (case-insensitive) or public key hex prefix. Debounced at 300ms.
|
||||||
|
|
||||||
|
**Sort options**:
|
||||||
|
- Latest Messages (by most recent message)
|
||||||
|
- Heard Recently (by last seen / last message)
|
||||||
|
- A–Z (alphabetical)
|
||||||
|
|
||||||
|
**Filter options**:
|
||||||
|
- All, Favorites, Users, Repeaters, Room Servers, Unread Only
|
||||||
|
|
||||||
|
## Contact Groups
|
||||||
|
|
||||||
|
Groups are a client-side organizational feature for grouping contacts.
|
||||||
|
|
||||||
|
- **Create a group**: Tap the group dropdown → "+" icon → enter name → select members → Save
|
||||||
|
- **Edit a group**: Group dropdown → pencil icon next to the group
|
||||||
|
- **Delete a group**: Group dropdown → trash icon next to the group
|
||||||
|
- **Filter by group**: Select a group from the dropdown to show only its members
|
||||||
|
|
||||||
|
Groups are stored per radio identity (scoped by public key).
|
||||||
|
|
||||||
|
**Validation rules**: Group names cannot be empty, cannot be "all" (reserved, case-insensitive), and must be unique (case-insensitive). The group creation dialog includes a built-in search field to filter contacts when selecting members. Creating a new group automatically selects it as the active filter.
|
||||||
|
|
||||||
|
## Tap Actions
|
||||||
|
|
||||||
|
| Contact Type | Action on Tap |
|
||||||
|
|---|---|
|
||||||
|
| Chat / Sensor | Opens ChatScreen for direct messaging |
|
||||||
|
| Repeater | Shows password login dialog → opens RepeaterHubScreen |
|
||||||
|
| Room | Shows password login dialog → opens ChatScreen for room chat |
|
||||||
|
|
||||||
|
## Long-Press / Right-Click Menu
|
||||||
|
|
||||||
|
| Action | Availability | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| Ping | Repeaters (always) | Opens PathTraceMapScreen targeting the repeater |
|
||||||
|
| Path Trace | Rooms (always); Chat/Sensor if `pathLength > 0` | Opens PathTraceMapScreen. For rooms, label shows "Ping" when no path bytes are known, "Path Trace" when path bytes are available |
|
||||||
|
| Manage Repeater | Repeaters only | Login dialog → RepeaterHubScreen |
|
||||||
|
| Room Login | Rooms only | Login dialog → ChatScreen |
|
||||||
|
| Room Management | Rooms only | Login dialog → RepeaterHubScreen (management mode) |
|
||||||
|
| Open Chat | Chat/Sensor | Same as single tap |
|
||||||
|
| Add/Remove Favorite | All types | Toggles the favorite flag |
|
||||||
|
| Share Contact | All types | Copies `meshcore://<hex>` URI to clipboard |
|
||||||
|
| Share Contact Zero-Hop | All types | Broadcasts the contact's advertisement one hop |
|
||||||
|
| Delete Contact | All types | Confirmation dialog → removes from device and clears messages |
|
||||||
|
|
||||||
|
## App Bar Menus
|
||||||
|
|
||||||
|
The Contacts screen has **two separate popup menus** in the app bar:
|
||||||
|
|
||||||
|
**Antenna icon menu** (contact sharing):
|
||||||
|
- Zero-Hop Advert — broadcasts your advertisement to immediately adjacent nodes
|
||||||
|
- Flood Advert — broadcasts across the full mesh network
|
||||||
|
- Copy Advert to Clipboard — copies your `meshcore://<hex>` URI for sharing externally
|
||||||
|
- Add Contact from Clipboard — reads a `meshcore://<hex>` URI from clipboard and imports it
|
||||||
|
|
||||||
|
**Three-dot overflow menu**:
|
||||||
|
- Disconnect — disconnects from the device
|
||||||
|
- Discovered Contacts — opens the DiscoveryScreen
|
||||||
|
- Settings — opens the Settings screen
|
||||||
|
|
||||||
|
## Adding Contacts
|
||||||
|
|
||||||
|
### Automatic (Passive)
|
||||||
|
When the radio hears an advertisement, the contact appears automatically if auto-add is enabled for that type (configurable in Settings → Contact Settings).
|
||||||
|
|
||||||
|
### Import from Clipboard
|
||||||
|
Antenna menu → "Add Contact from Clipboard". Reads a `meshcore://<hex>` URI from clipboard and imports it to the device.
|
||||||
|
|
||||||
|
### Import from Discovered Contacts
|
||||||
|
Overflow menu → "Discovered Contacts". Shows nodes heard passively that haven't been added yet. Tap to immediately import (no confirmation dialog), or long-press for more options (Add, Copy URI, Delete). The Discovery screen has its own search bar, type filters (Users, Repeaters, Rooms, Favorites), and sort options (Last Seen, A-Z). An overflow "Delete All" option clears all discovered contacts.
|
||||||
|
|
||||||
|
## Contact Sharing Format
|
||||||
|
|
||||||
|
Contacts are shared using the `meshcore://` URI scheme:
|
||||||
|
```
|
||||||
|
meshcore://<hex-encoded-advertisement-packet>
|
||||||
|
```
|
||||||
|
This contains the node's public key and metadata. Paste it into another MeshCore app to import.
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
# Map & Location
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Map feature is a full-featured node-location visualization and radio-planning tool built on OpenStreetMap tiles. It is one of the three primary views accessible from the QuickSwitchBar.
|
||||||
|
|
||||||
|
## How to Access
|
||||||
|
|
||||||
|
- **QuickSwitchBar tab 2** (rightmost) from Contacts or Channels
|
||||||
|
- **Deep-link from a chat message**: Tapping a shared location pin in a chat opens the map centered on that pin
|
||||||
|
- **Settings → Offline Map Cache**: Opens the tile cache management screen
|
||||||
|
|
||||||
|
## What the Map Displays
|
||||||
|
|
||||||
|
### Self Location (Teal Circle)
|
||||||
|
Your own node's position, obtained from the device firmware. Displayed as a teal `person_pin_circle` icon. Only appears if the device has GPS data or a manually-set location.
|
||||||
|
|
||||||
|
### Contact / Node Markers (Color-Coded)
|
||||||
|
All contacts with known GPS coordinates are plotted:
|
||||||
|
|
||||||
|
| Type | Color | Icon |
|
||||||
|
|---|---|---|
|
||||||
|
| Chat user | Blue | Person |
|
||||||
|
| Repeater | Green | Router |
|
||||||
|
| Room | Purple | Meeting room |
|
||||||
|
| Sensor | Orange | Sensors |
|
||||||
|
|
||||||
|
Node name labels appear automatically at zoom level 14 and above.
|
||||||
|
|
||||||
|
### Shared Map Pins (Flag Icons)
|
||||||
|
Location pins shared in chat messages are displayed as flags:
|
||||||
|
- **Blue flag**: From a direct message
|
||||||
|
- **Purple flag**: From a private channel
|
||||||
|
- **Orange flag**: From a public channel
|
||||||
|
|
||||||
|
Tap a pin to see its info. Options to "Hide" (session only) or "Remove" (persistent).
|
||||||
|
|
||||||
|
### Predicted / Guessed Locations (Semi-Transparent)
|
||||||
|
|
||||||
|
Many contacts on the mesh don't have GPS hardware, so the map has no explicit coordinates for them. Instead of leaving these contacts invisible, the app **infers an approximate position** by analyzing the repeater path the contact's messages travel through. These inferred positions are displayed as semi-transparent markers with a `not_listed_location` icon, visually distinct from confirmed-location markers.
|
||||||
|
|
||||||
|
#### Why guessed locations exist
|
||||||
|
|
||||||
|
In a mesh network, every message hops through one or more repeaters on its way to the destination. Each repeater in the path is identified by the first byte of its public key. If any of those repeaters have a known GPS location (because they advertise it), then a contact that routes through those repeaters must be somewhere within radio range of them. By combining the positions of multiple repeaters a contact is known to use, the app can triangulate a rough area where the contact is likely located.
|
||||||
|
|
||||||
|
#### How the algorithm works
|
||||||
|
|
||||||
|
1. **Build a repeater index**: The app collects all known contacts of type Repeater that have a valid GPS position and indexes them by the first byte of their public key.
|
||||||
|
|
||||||
|
2. **Collect anchor points**: For each contact that lacks GPS, the app looks at the **last-hop byte** of the contact's current path and also searches the `PathHistoryService` for recent paths. Each last-hop byte that matches a located repeater becomes an "anchor point" — a GPS coordinate the contact is likely near.
|
||||||
|
|
||||||
|
3. **Resolve ambiguity**: If multiple repeaters share the same first public-key byte (a hash collision), that byte is discarded as ambiguous. Only unambiguous one-to-one matches are kept.
|
||||||
|
|
||||||
|
4. **Filter geometric inconsistencies**: Two anchor points separated by more than `2 × maxRangeKm` (the estimated LoRa radio range, computed from the current frequency, bandwidth, spreading factor, and TX power using a free-space path loss model) cannot both be in range of the same node. Outlier anchors are removed to keep only a geometrically consistent set.
|
||||||
|
|
||||||
|
5. **Compute the estimated position**:
|
||||||
|
- **Single anchor**: The contact is placed on a small circle (330m radius) around the repeater. The angle on the circle is deterministic — derived from an FNV-1a hash of the contact's public key — so the same contact always appears at the same offset, preventing markers from stacking on top of each other.
|
||||||
|
- **Two or more anchors**: The position is the average (centroid) of all anchor coordinates, with a smaller offset radius (80–120m) applied for visual separation.
|
||||||
|
|
||||||
|
6. **Assign confidence level**:
|
||||||
|
- **High confidence** (2+ anchors): Displayed at 55% opacity.
|
||||||
|
- **Low confidence** (1 anchor): Displayed at 30% opacity.
|
||||||
|
|
||||||
|
7. **Cache the result**: The computation is cached using a key derived from the contact's paths, anchor positions, path-history version, and radio parameters. The cache is only invalidated when any of these inputs change, avoiding recomputation on every UI rebuild.
|
||||||
|
|
||||||
|
#### How to read guessed locations on the map
|
||||||
|
|
||||||
|
- **Semi-transparent marker** with a `not_listed_location` icon: This is a guessed position, not a confirmed GPS fix.
|
||||||
|
- **More opaque** (55%): Higher confidence — the contact was seen through 2 or more repeaters with known positions.
|
||||||
|
- **More transparent** (30%): Lower confidence — based on a single repeater anchor only.
|
||||||
|
- Coordinates shown in the marker info dialog are prefixed with `~` to indicate they are estimated.
|
||||||
|
- Guessed locations can be toggled on/off in the map filter dialog (FAB → "Guessed locations" toggle).
|
||||||
|
|
||||||
|
## Map Interactions
|
||||||
|
|
||||||
|
### Zoom and Pan
|
||||||
|
Standard pinch-to-zoom (range 2–18). Initial camera position is calculated from the statistical spread of all plotted points.
|
||||||
|
|
||||||
|
### Tap on a Node Marker
|
||||||
|
Opens a dialog showing: type, path (hop chain), coordinates, last-seen time, and public key. Action buttons vary by type:
|
||||||
|
- **Chat nodes**: "Open Chat"
|
||||||
|
- **Repeaters**: "Manage Repeater"
|
||||||
|
- **Rooms**: "Join Room"
|
||||||
|
|
||||||
|
### Long-Press on Empty Map Area
|
||||||
|
Shows a bottom sheet with:
|
||||||
|
- **Share marker here**: Prompts for a label, then pick a DM contact or channel to send the location to. Wire format: `m:<lat>,<lon>|<label>|poi`
|
||||||
|
- **Set as my location**: Updates your device's advertised location
|
||||||
|
|
||||||
|
### Filter Dialog (FAB)
|
||||||
|
Toggle visibility of: chat nodes, repeaters, other nodes, guessed locations, discovery contacts, overlapping markers (stacked markers at similar coordinates), and shared map pins (flag markers).
|
||||||
|
Additional filters:
|
||||||
|
- **Key prefix filter**: Show only contacts whose public key starts with a given prefix
|
||||||
|
- **Last-seen time slider**: Exponential scale from near-zero to 6 months, with "all time" at the top end
|
||||||
|
|
||||||
|
### Legend Card (Top-Right)
|
||||||
|
Shows node count and pin count. Tappable to expand a legend of all marker types.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Path Trace Map
|
||||||
|
|
||||||
|
### How to Access
|
||||||
|
- From the main map's radar icon
|
||||||
|
- From a contact's long-press menu → "Path Trace / Ping"
|
||||||
|
- From a message's path view → radar icon
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
A map with a polyline showing the route from your node through repeater hops to the target:
|
||||||
|
- **Green circles**: Hops with known GPS coordinates
|
||||||
|
- **Orange circles** (`~HH`): Inferred positions (no GPS but deducible from contacts)
|
||||||
|
- **Red endpoint**: Target contact with known GPS
|
||||||
|
- **Purple semi-transparent endpoint**: Target with guessed position
|
||||||
|
|
||||||
|
A legend card at the bottom lists each hop pair with SNR quality icons and total path distance.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
Sends a trace request frame over the mesh. The repeater network traces the path hop-by-hop and returns per-hop SNR data. For hops without GPS, positions are inferred by averaging GPS coordinates of contacts sharing that last-hop byte.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Line-of-Sight (LOS) Analysis
|
||||||
|
|
||||||
|
### How to Access
|
||||||
|
From the main map, tap the terrain/antenna icon.
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
A full-screen map with a collapsible control panel containing:
|
||||||
|
- **Elevation profile chart**: Terrain fill (green), LOS beam line (white), radio horizon line (yellow)
|
||||||
|
- **Status**: Clear (green) or blocked (red) with distance and minimum clearance
|
||||||
|
- **Options panel**: Node toggles, endpoint dropdowns, antenna height sliders (0–400 ft), Run LOS button
|
||||||
|
|
||||||
|
### Key Interactions
|
||||||
|
- **Long-press the map** to add custom endpoints (orange pushpin markers, renameable/deleteable)
|
||||||
|
- **Tap a marker** to select it as Point A or B; LOS runs automatically when both are set
|
||||||
|
- **Antenna heights** are adjustable for both endpoints
|
||||||
|
- **Map line** between endpoints is colored green (clear) or red (blocked)
|
||||||
|
- Terrain elevation is fetched from the Open-Meteo API (21–81 sample points, cached 24 hours)
|
||||||
|
- K-factor is adjusted per radio frequency from a baseline of 4/3 at 915 MHz
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Offline Map Cache
|
||||||
|
|
||||||
|
### How to Access
|
||||||
|
Settings → App Settings → Map Display → Offline Map Cache
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
- Map with a blue polygon overlay showing previously selected cache bounds
|
||||||
|
- Bounding box coordinates card
|
||||||
|
- **Cache Area** controls: "Use Current View" and Clear buttons
|
||||||
|
- **Zoom Range** slider (3–18) with estimated tile count
|
||||||
|
- **Download progress** bar (when downloading)
|
||||||
|
- **Download Tiles** and **Clear Cache** buttons
|
||||||
|
|
||||||
|
### Key Interactions
|
||||||
|
1. Pan/zoom the map to the desired area
|
||||||
|
2. Tap "Use Current View" to capture the viewport as cache bounds
|
||||||
|
3. Adjust the zoom range slider
|
||||||
|
4. Tap "Download Tiles" (confirmation dialog shows estimated count)
|
||||||
|
5. Tiles are downloaded with up to 8 concurrent connections
|
||||||
|
6. Once cached, tiles are served from disk without internet (365-day stale period)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GPX Export
|
||||||
|
|
||||||
|
### How to Access
|
||||||
|
Settings → Export section
|
||||||
|
|
||||||
|
### What It Does
|
||||||
|
Exports contacts with GPS coordinates to a `.gpx` file via the OS share sheet. Three export options:
|
||||||
|
- **Export Repeaters**: Repeater and Room contacts with locations
|
||||||
|
- **Export Contacts**: Chat contacts with locations
|
||||||
|
- **Export All**: All contacts with locations
|
||||||
|
|
||||||
|
Each waypoint includes: name, lat/lon, type label, and public key hex.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Location Data Sources
|
||||||
|
|
||||||
|
The phone's own GPS is **never used**. All location data comes from the mesh:
|
||||||
|
|
||||||
|
1. **Device self-location**: Read from firmware device-info response. Set manually in Settings → Location, or updated automatically if the device has a GPS module.
|
||||||
|
2. **Remote node locations**: Extracted from advertisement packets received over the mesh. Encoded as integer lat/lon × 1,000,000.
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# Navigation
|
||||||
|
|
||||||
|
## App Flow
|
||||||
|
|
||||||
|
The app follows this general flow:
|
||||||
|
|
||||||
|
```
|
||||||
|
Launch → Scanner Screen → [Connect via BLE/USB/TCP] → Contacts Screen
|
||||||
|
```
|
||||||
|
|
||||||
|
After connecting, the three main screens (Contacts, Channels, Map) are accessible via a persistent bottom navigation bar called the **QuickSwitchBar**.
|
||||||
|
|
||||||
|
## Quick Switch Bar
|
||||||
|
|
||||||
|
The QuickSwitchBar is a Material 3 `NavigationBar` with a frosted-glass visual treatment (blur backdrop, transparent theme, rounded corners). It appears at the bottom of all three main screens.
|
||||||
|
|
||||||
|
| Index | Icon | Label | Screen |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 0 | People | Contacts | ContactsScreen |
|
||||||
|
| 1 | Tag | Channels | ChannelsScreen |
|
||||||
|
| 2 | Map | Map | MapScreen |
|
||||||
|
|
||||||
|
Tapping a tab replaces the current screen with a subtle fade + slight horizontal nudge transition (220ms forward, 200ms reverse). The back button is suppressed on all three main screens — navigation between them is flat, not stacked. All icons use outline variants (`people_outline`, `tag`, `map_outlined`) following Material 3 conventions.
|
||||||
|
|
||||||
|
## Disconnection
|
||||||
|
|
||||||
|
- The disconnect button (available in the Settings screen and other main screens) shows a confirmation dialog before disconnecting
|
||||||
|
- If the device disconnects unexpectedly, the app automatically navigates back to the Scanner screen (fires after the current frame completes via a post-frame callback)
|
||||||
|
- This auto-navigation behavior (`DisconnectNavigationMixin`) is shared across all main screens
|
||||||
|
|
||||||
|
## Theme and Locale
|
||||||
|
|
||||||
|
- **Theme mode** is user-configurable in App Settings (System / Light / Dark) — not locked to system
|
||||||
|
- **Language** can be overridden to one of 18 supported languages, or follow the system locale
|
||||||
|
- On web, if a non-Chromium browser is detected, the app shows a `ChromeRequiredScreen` instead of the Scanner (Web Bluetooth requires Chromium)
|
||||||
|
|
||||||
|
## Full Navigation Graph
|
||||||
|
|
||||||
|
```
|
||||||
|
ScannerScreen (root, always on stack)
|
||||||
|
├─ [BLE connect] → push → ContactsScreen
|
||||||
|
├─ [TCP FAB] → push → TcpScreen
|
||||||
|
│ └─ [TCP connected] → pushReplacement → ContactsScreen
|
||||||
|
└─ [USB FAB] → push → UsbScreen
|
||||||
|
└─ [USB connected] → pushReplacement → ContactsScreen
|
||||||
|
|
||||||
|
ContactsScreen (selected=0)
|
||||||
|
├─ [quick-switch 1] → pushReplacement → ChannelsScreen
|
||||||
|
├─ [quick-switch 2] → pushReplacement → MapScreen
|
||||||
|
├─ [tap contact] → push → ChatScreen
|
||||||
|
├─ [overflow > Settings] → push → SettingsScreen
|
||||||
|
└─ [overflow > Discovered] → push → DiscoveryScreen
|
||||||
|
|
||||||
|
ChannelsScreen (selected=1)
|
||||||
|
├─ [quick-switch 0] → pushReplacement → ContactsScreen
|
||||||
|
├─ [quick-switch 2] → pushReplacement → MapScreen
|
||||||
|
├─ [tap channel] → push → ChannelChatScreen
|
||||||
|
└─ [overflow > Settings] → push → SettingsScreen
|
||||||
|
|
||||||
|
MapScreen (selected=2)
|
||||||
|
├─ [quick-switch 0] → pushReplacement → ContactsScreen
|
||||||
|
├─ [quick-switch 1] → pushReplacement → ChannelsScreen
|
||||||
|
├─ [radar button] → push → PathTraceMapScreen
|
||||||
|
├─ [terrain button] → push → LineOfSightMapScreen
|
||||||
|
└─ [long-press] → share marker / set location
|
||||||
|
|
||||||
|
Settings (push from any main screen)
|
||||||
|
└─ [App Settings] → push → AppSettingsScreen
|
||||||
|
└─ [Offline Map Cache] → push → MapCacheScreen
|
||||||
|
```
|
||||||
|
|
||||||
|
Any disconnection from any screen triggers `popUntil(route.isFirst)`, returning to the Scanner.
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# Notifications
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
MeshCore Open provides both **system notifications** (push-style OS alerts) and **in-app unread badges** to inform users of new activity.
|
||||||
|
|
||||||
|
## Notification Types
|
||||||
|
|
||||||
|
### 1. Direct Message Notifications
|
||||||
|
- **Triggered when**: A new incoming message arrives from a Chat or Room contact
|
||||||
|
- **Title**: Contact's name
|
||||||
|
- **Body**: Message text (reactions show "Reacted [emoji]", GIFs show "Sent a GIF")
|
||||||
|
- **Priority**: High
|
||||||
|
- **Android channel**: `messages`
|
||||||
|
|
||||||
|
### 2. Channel Message Notifications
|
||||||
|
- **Triggered when**: A new message arrives on a non-muted channel
|
||||||
|
- **Title**: Channel name (or "Channel N" if unnamed)
|
||||||
|
- **Body**: `"<senderName>: <message text>"`
|
||||||
|
- **Priority**: High
|
||||||
|
- **Android channel**: `channel_messages`
|
||||||
|
|
||||||
|
### 3. Advertisement Notifications
|
||||||
|
- **Triggered when**: A new node is discovered on the mesh for the first time
|
||||||
|
- **Title**: "New [type] discovered" (e.g., "New chat node discovered")
|
||||||
|
- **Body**: Contact's name
|
||||||
|
- **Priority**: Default
|
||||||
|
- **Android channel**: `adverts`
|
||||||
|
|
||||||
|
### 4. Background Service Notification (Android Only)
|
||||||
|
- A persistent low-priority notification: "MeshCore running — Keeping BLE connected"
|
||||||
|
- Required by Android for foreground services to keep BLE alive in the background
|
||||||
|
- Tap to re-launch the app
|
||||||
|
- **Does not auto-start on reboot** — the user must re-open the app manually after a phone restart
|
||||||
|
|
||||||
|
### Notification Tap Behavior
|
||||||
|
|
||||||
|
Tapping a notification currently re-launches the app at the root route. It does **not** navigate directly to the relevant chat or channel.
|
||||||
|
|
||||||
|
## In-App Unread Badges
|
||||||
|
|
||||||
|
Red numeric badges appear throughout the UI:
|
||||||
|
- **Contacts list**: Each contact row shows a red pill badge (e.g., "3") for unread messages
|
||||||
|
- **Channels list**: Each channel row shows an unread badge
|
||||||
|
- **Chat screen subtitle**: Shows unread count inline
|
||||||
|
- Badges cap at "99+" for display
|
||||||
|
|
||||||
|
### How Unread Counts Work
|
||||||
|
|
||||||
|
- Stored per contact (by public key) and per channel, **scoped to the connected device's identity** (first 10 hex characters of its public key). Switching between different radios gives each its own independent unread state
|
||||||
|
- **Suppressed when viewing**: Opening a chat resets the count to 0 and cancels the OS notification
|
||||||
|
- **Ignored for**: Outgoing messages, CLI messages, and repeater contacts
|
||||||
|
- Debounced writes (500ms) to avoid excessive storage I/O during message bursts
|
||||||
|
|
||||||
|
## Notification Settings
|
||||||
|
|
||||||
|
Access via **App Settings → Notifications**:
|
||||||
|
|
||||||
|
| Setting | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| Enable Notifications | On | Master toggle; requests OS permission when turned on |
|
||||||
|
| Message Notifications | On | DM alerts (greyed out if master is off) |
|
||||||
|
| Channel Message Notifications | On | Channel alerts (greyed out if master is off) |
|
||||||
|
| Advertisement Notifications | On | New node alerts (greyed out if master is off) |
|
||||||
|
|
||||||
|
### Per-Channel Muting
|
||||||
|
|
||||||
|
Long-press a channel in the channels list → "Mute channel" / "Unmute channel". Muted channels do not generate OS notifications.
|
||||||
|
|
||||||
|
There is no per-contact muting.
|
||||||
|
|
||||||
|
## Rate Limiting
|
||||||
|
|
||||||
|
The notification system prevents notification storms:
|
||||||
|
- **Minimum interval**: 3 seconds between individual notifications
|
||||||
|
- **Batch window**: If multiple notifications arrive within 5 seconds, they are combined into a single summary notification on a fourth Android channel (`batch_summary`). The title is "MeshCore Activity" and the body lists the grouped counts (e.g., "2 messages, 1 channel message, 3 new nodes"). Batch summaries are Android-only; queued notifications that overflow the batch window are silently dropped on other platforms
|
||||||
|
|
||||||
|
## Notification Clearing
|
||||||
|
|
||||||
|
- **Opening a contact chat**: Cancels the OS notification and resets unread count
|
||||||
|
- **Opening a channel**: Cancels the channel notification and resets unread count
|
||||||
|
- **Opening Contacts screen**: Cancels all advertisement notifications
|
||||||
|
|
||||||
|
## Platform Support
|
||||||
|
|
||||||
|
| Platform | Message Notifs | Badge | Background Service |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Android | Yes | Via notification number | Yes (foreground service) |
|
||||||
|
| iOS | Yes | Yes (app badge) | No |
|
||||||
|
| macOS | Yes | Yes | No |
|
||||||
|
| Windows | Yes | No | No |
|
||||||
|
| Linux | Yes (if D-Bus available) | No | No |
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
# Repeater Management
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Repeater Management provides tools for administering MeshCore repeater and room server nodes. It includes device status monitoring, CLI access, telemetry reading, neighbor discovery, and remote configuration.
|
||||||
|
|
||||||
|
## How to Access
|
||||||
|
|
||||||
|
From the Contacts screen:
|
||||||
|
1. Long-press a **Repeater** or **Room** contact
|
||||||
|
2. Select "Manage Repeater" or "Room Management"
|
||||||
|
3. Enter the admin password in the login dialog
|
||||||
|
4. Navigate to the Repeater Hub Screen
|
||||||
|
|
||||||
|
### Login Dialog
|
||||||
|
|
||||||
|
- Password field with show/hide toggle
|
||||||
|
- "Save password" checkbox (persists for future logins). If a saved password exists, it is pre-filled and the checkbox is pre-checked, making login one-tap
|
||||||
|
- Routing mode selector and "Manage Paths" link are available directly in the dialog (configure routing before login)
|
||||||
|
- Auto-retries up to 5 times on timeout, showing progress ("Attempt 2 of 5"). A wrong password (explicit failure response) stops immediately — only timeouts trigger retries
|
||||||
|
- If auto-clock-sync is enabled for this repeater (configured in Repeater Settings), a `clock sync` command is sent automatically on successful login
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Repeater Hub Screen
|
||||||
|
|
||||||
|
The central management screen showing:
|
||||||
|
|
||||||
|
- **Header card**: Repeater name, short public key, path label, GPS coordinates (if known)
|
||||||
|
- **Battery chemistry selector**: NMC / LiFePO4 / LiPo (saved per repeater)
|
||||||
|
- **Management tool cards** (full-width cards with chevron arrows, not a grid). Title dynamically shows "Repeater Management" or "Room Management" (admin) or "Repeater Guest" / "Room Guest" (guest) based on contact type and login result:
|
||||||
|
|
||||||
|
| Card | Destination | Visibility |
|
||||||
|
|---|---|---|
|
||||||
|
| Status | Repeater Status Screen | All users |
|
||||||
|
| Telemetry | Telemetry Screen | All users |
|
||||||
|
| CLI | Repeater CLI Screen | Admin only |
|
||||||
|
| Neighbors | Neighbors Screen | All users |
|
||||||
|
| Settings | Repeater Settings Screen | Admin only |
|
||||||
|
|
||||||
|
The battery chemistry selector and CLI/Settings cards are hidden from guest users.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Repeater Status
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
|
||||||
|
Three information cards:
|
||||||
|
|
||||||
|
**System Information**:
|
||||||
|
- Battery percentage and voltage (e.g. "85% / 3.95V"), using the battery chemistry set in the hub screen
|
||||||
|
- Clock at login time
|
||||||
|
- Uptime (days/hours/minutes/seconds)
|
||||||
|
- Queue length
|
||||||
|
- Debug flags (error event count)
|
||||||
|
|
||||||
|
**Radio Statistics**:
|
||||||
|
- Last RSSI and SNR
|
||||||
|
- Noise floor
|
||||||
|
- TX airtime and RX airtime
|
||||||
|
|
||||||
|
**Packet Statistics**:
|
||||||
|
- Packets sent and received, each broken down by flood vs. direct
|
||||||
|
- Duplicates, broken down by flood vs. direct
|
||||||
|
- Channel utilization (% of uptime used by TX + RX)
|
||||||
|
|
||||||
|
### Key Interactions
|
||||||
|
- Auto-queries the repeater on open; shows a loading spinner until data arrives
|
||||||
|
- On timeout: red snackbar error. On success: data appears in-place (no extra snackbar)
|
||||||
|
- Pull-to-refresh or refresh button in the app bar to re-query
|
||||||
|
- Routing mode popup and path management dialog in app bar (these controls appear on **all** management sub-screens, not just Status)
|
||||||
|
- Accepts both binary `RESP_CODE_STATUS_RESPONSE` frames and legacy JSON text responses
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Repeater CLI
|
||||||
|
|
||||||
|
A terminal-style interface for sending commands directly to the repeater.
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
|
||||||
|
- **Quick-command bar** (horizontal scroll): Shortcut buttons for 9 common commands (advert, get name, get radio, get tx, discover.neighbors, neighbors, ver, clock, clock sync)
|
||||||
|
- **Command history list**: Sent commands in primary color, responses in secondary color
|
||||||
|
- **Input bar**: Up/down history arrows, monospace text field with `> ` prefix, send button
|
||||||
|
|
||||||
|
### Key Interactions
|
||||||
|
|
||||||
|
- Type a command and press send (or Enter on desktop)
|
||||||
|
- Up/down arrows navigate through command history
|
||||||
|
- Quick-command buttons populate and send common commands
|
||||||
|
- Bug report icon: Shows raw frame debug info for the next typed command (shows error snackbar if input field is empty)
|
||||||
|
- Help icon: Opens a scrollable reference of all known CLI commands. Tapping any command populates the input field immediately
|
||||||
|
- Clear icon: Wipes the command/response history
|
||||||
|
- Failed/timed-out commands are automatically retried once
|
||||||
|
|
||||||
|
### Available CLI Commands
|
||||||
|
|
||||||
|
The in-app help reference (help icon) documents all known commands. Categories:
|
||||||
|
|
||||||
|
**General**: `advert`, `advert.zerohop`, `reboot`, `clock`, `clock sync`, `password`, `ver`, `clear stats`, `erase`, `poweroff`, `shutdown`, `clkreboot`, `start ota`, `time`, `board`, `discover.neighbors`, `powersaving`, `stats-packets`, `stats-radio`, `stats-core`
|
||||||
|
|
||||||
|
**Get**: `get name`, `get role`, `get public.key`, `get prv.key`, `get repeat`, `get tx`, `get freq`, `get radio`, `get radio.rxgain`, `get af`, `get dutycycle`, `get int.thresh`, `get agc.reset.interval`, `get multi.acks`, `get allow.read.only`, `get advert.interval`, `get flood.advert.interval`, `get guest.password`, `get lat`, `get lon`, `get rxdelay`, `get txdelay`, `get direct.txdelay`, `get flood.max`, `get owner.info`, `get path.hash.mode`, `get loop.detect`, `get acl`, `get bridge.*`, `get adc.multiplier`, `get bootloader.ver`
|
||||||
|
|
||||||
|
**Set**: `set name`, `set af`, `set tx`, `set repeat`, `set allow.read.only`, `set flood.max`, `set int.thresh`, `set agc.reset.interval`, `set multi.acks`, `set advert.interval`, `set flood.advert.interval`, `set guest.password`, `set lat`, `set lon`, `set freq`, `set radio`, `set rxdelay`, `set txdelay`, `set direct.txdelay`, `set radio.rxgain`, `set dutycycle`, `set loop.detect`, `set path.hash.mode`, `set owner.info`, `set prv.key`, `set bridge.*`, `set adc.multiplier`, `tempradio`, `setperm`
|
||||||
|
|
||||||
|
**Bridge**: `get bridge.type`
|
||||||
|
|
||||||
|
**Logging**: `log start`, `log stop`, `log erase`
|
||||||
|
|
||||||
|
**Neighbors**: `neighbors`, `neighbor.remove`
|
||||||
|
|
||||||
|
**Power Management**: `get pwrmgt.support`, `get pwrmgt.source`, `get pwrmgt.bootreason`, `get pwrmgt.bootmv`
|
||||||
|
|
||||||
|
**Sensors**: `sensor get {key}`
|
||||||
|
|
||||||
|
**Region Management**: `region`, `region load`, `region get`, `region put`, `region remove`, `region allowf`, `region denyf`, `region home`, `region save`, `region default`, `region list allowed`, `region list denied`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Telemetry
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
|
||||||
|
A list of Cayenne LPP sensor channel cards:
|
||||||
|
|
||||||
|
- **Channel 1** (special): Battery voltage (shown as percentage or raw mV) and MCU temperature
|
||||||
|
- **Other channels**: Raw sensor values with appropriate labels
|
||||||
|
|
||||||
|
Shows "No data" until a response arrives from the repeater.
|
||||||
|
|
||||||
|
### Key Interactions
|
||||||
|
- Auto-queries on open
|
||||||
|
- Pull-to-refresh
|
||||||
|
- Temperature respects metric/imperial setting
|
||||||
|
- Battery readings are stored for the repeater's battery snapshot
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Neighbors
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
|
||||||
|
A card titled "Repeater's Neighbors - N" listing each neighbor as:
|
||||||
|
- Repeater name (or hex key prefix if unknown)
|
||||||
|
- Time since last heard
|
||||||
|
- SNR quality icon with color coding and label
|
||||||
|
|
||||||
|
### Key Interactions
|
||||||
|
- Auto-queries up to 15 neighbors on open
|
||||||
|
- Matches public key prefixes against known contacts to show names
|
||||||
|
- Pull-to-refresh
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Repeater Settings
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
|
||||||
|
Nine configuration cards, each with its own per-field refresh button(s):
|
||||||
|
|
||||||
|
**1. Basic Settings**
|
||||||
|
- Name field
|
||||||
|
- Admin password field (write-only; always sent when non-empty)
|
||||||
|
- Guest password field (write-only; always sent when non-empty)
|
||||||
|
|
||||||
|
**2. Radio Settings**
|
||||||
|
- Frequency (MHz)
|
||||||
|
- TX Power (dBm) — has its own independent refresh button
|
||||||
|
- Bandwidth dropdown (kHz)
|
||||||
|
- Spreading Factor (SF5–SF12)
|
||||||
|
- Coding Rate (4/5–4/8)
|
||||||
|
- RX Gain boost toggle
|
||||||
|
|
||||||
|
**3. Location Settings**
|
||||||
|
- Latitude and longitude fields, each with an independent refresh button
|
||||||
|
|
||||||
|
**4. Features**
|
||||||
|
- Packet forwarding toggle (`set repeat`)
|
||||||
|
- Guest access toggle (`set allow.read.only`)
|
||||||
|
- Multi-ACKs toggle (`set multi.acks`)
|
||||||
|
- Auto clock sync after login toggle (local app setting only, not sent to repeater)
|
||||||
|
|
||||||
|
**5. Network Health**
|
||||||
|
- Loop detection dropdown (off / minimal / moderate / strict; `set loop.detect`)
|
||||||
|
- Duty cycle slider (1–100%; `set dutycycle`)
|
||||||
|
|
||||||
|
**6. Advertisement Settings**
|
||||||
|
- Local advert interval slider (60–240 minutes) with enable/disable toggle
|
||||||
|
- Flood advert interval slider (3–168 hours) with enable/disable toggle
|
||||||
|
- Flood max hops slider (0–64; `set flood.max`)
|
||||||
|
|
||||||
|
**7. Owner Info**
|
||||||
|
- Multi-line text field for operator contact info (`set owner.info`); newlines sent as `|`
|
||||||
|
|
||||||
|
**8. Actions** (one-tap, no save needed)
|
||||||
|
- Send Advertisement (`advert`)
|
||||||
|
- Send Zero-Hop Advertisement (`advert.zerohop`)
|
||||||
|
- Clock Sync (`clock sync`)
|
||||||
|
|
||||||
|
**9. Advanced** (collapsed by default)
|
||||||
|
- Path hash mode dropdown (0–2; `set path.hash.mode`)
|
||||||
|
- TX delay field (`set txdelay`)
|
||||||
|
- Direct TX delay field (`set direct.txdelay`)
|
||||||
|
- Interference threshold field (`set int.thresh`)
|
||||||
|
- AGC reset interval slider (0–240s in multiples of 4; `set agc.reset.interval`)
|
||||||
|
|
||||||
|
**Danger Zone** (red-styled card)
|
||||||
|
- Reboot repeater (sends `reboot` with confirmation dialog)
|
||||||
|
- Erase filesystem (serial-only; shows informational snackbar only — no command is sent over the air)
|
||||||
|
|
||||||
|
### Key Interactions
|
||||||
|
- **Settings are NOT auto-fetched on open**. Name is pre-filled from cached contact data. Each section has its own refresh button to fetch live values from the repeater
|
||||||
|
- TX Power, RX Gain, latitude, longitude, and advanced fields each have independent inline refresh buttons
|
||||||
|
- Save button in app bar appears when any change is detected; failed commands keep those fields dirty for retry
|
||||||
|
- Settings are sent sequentially with 200ms delays between commands; firmware responses are checked and partial failures are reported in a snackbar
|
||||||
|
- Some changes (e.g. radio frequency) require a reboot; the firmware response triggers an orange "reboot needed" snackbar
|
||||||
|
- Advertisement interval sliders reset to defaults when re-enabled (local: 60 min, flood: 3 hours)
|
||||||
|
- **Erase Filesystem** does NOT send any command over the air — tapping it only shows a snackbar explaining the operation requires physical serial access
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# Scanner & Connection
|
||||||
|
|
||||||
|
## BLE Scanner (Home Screen)
|
||||||
|
|
||||||
|
The BLE Scanner is the app's home screen, displayed immediately on launch.
|
||||||
|
|
||||||
|
### How to Access
|
||||||
|
|
||||||
|
- Opens automatically when the app starts
|
||||||
|
- Returns here when disconnecting from any device
|
||||||
|
- Accessible by navigating back from a connected session
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
|
||||||
|
**App Bar**: Centered title "Scanner".
|
||||||
|
|
||||||
|
**Bluetooth-Off Warning Banner** (conditional): Appears when the Bluetooth adapter is off, showing a `bluetooth_disabled` icon, a warning message, and on Android, an "Enable Bluetooth" button.
|
||||||
|
|
||||||
|
**Status Bar**: A full-width colored strip reflecting the current connection state:
|
||||||
|
|
||||||
|
| State | Text | Color |
|
||||||
|
|---|---|---|
|
||||||
|
| Disconnected | "Not connected" | Grey |
|
||||||
|
| Scanning | "Scanning..." | Blue |
|
||||||
|
| Connecting | "Connecting..." | Orange |
|
||||||
|
| Connected | "Connected to \<device name\>" | Green |
|
||||||
|
| Disconnecting | "Disconnecting..." | Orange |
|
||||||
|
|
||||||
|
**Device List**: When no devices are found, shows a large Bluetooth icon with a prompt. The prompt text is dynamic: "Searching for devices..." while actively scanning, or "Tap Scan to search" when idle. When devices are found, shows a scrollable list of `DeviceTile` widgets.
|
||||||
|
|
||||||
|
**Bottom FAB Row**: Up to three floating action buttons:
|
||||||
|
- **USB** button - Opens USB connection screen (Android, Windows, Linux, macOS, Chrome web only)
|
||||||
|
- **TCP/IP** button - Opens TCP connection screen (all non-web platforms)
|
||||||
|
- **BLE Scan** button - Toggles BLE scanning on/off; shows a spinner when scanning. **Disabled** (greyed out, not tappable) when Bluetooth is off
|
||||||
|
|
||||||
|
### Device Tile
|
||||||
|
|
||||||
|
Each discovered device is displayed as a list tile showing:
|
||||||
|
- **Signal strength icon** (color-coded by RSSI):
|
||||||
|
- Green: >= -60 dBm (excellent)
|
||||||
|
- Light green: -60 to -70 dBm (good)
|
||||||
|
- Amber: -70 to -80 dBm (fair)
|
||||||
|
- Orange: -80 to -90 dBm (weak)
|
||||||
|
- Red: < -90 dBm (poor)
|
||||||
|
- **RSSI value** in dBm (e.g., "-72 dBm")
|
||||||
|
- **Device name** (falls back to "Unknown Device")
|
||||||
|
- **Device ID** (BLE MAC address on Android; a system-assigned UUID on iOS/macOS)
|
||||||
|
- **Connect button** (the entire tile row is also tappable — both trigger connection)
|
||||||
|
|
||||||
|
Note: The weak (-80 to -90 dBm) and poor (< -90 dBm) tiers share the same icon shape and are only differentiated by color (orange vs. red).
|
||||||
|
|
||||||
|
### How Scanning Works
|
||||||
|
|
||||||
|
- Filters for devices with names starting with one of the known prefixes: `MeshCore-`, `Whisper-`, `WisCore-`, `Seeed`, `Lilygo`, `HT-`, `LowMesh_MC_`
|
||||||
|
- Uses low-latency scan mode on Android
|
||||||
|
- Scans for 10 seconds then auto-stops
|
||||||
|
- On iOS/macOS, waits for BLE adapter initialization before starting
|
||||||
|
- If Bluetooth is turned off during a scan, scanning stops immediately
|
||||||
|
|
||||||
|
### Connecting to a Device
|
||||||
|
|
||||||
|
Tap a device tile or its Connect button:
|
||||||
|
1. The connector stops scanning and transitions to "connecting"
|
||||||
|
2. Connects to the device with a 15-second timeout (6 seconds on Linux)
|
||||||
|
3. Requests MTU 185 bytes for optimal throughput
|
||||||
|
4. Discovers BLE services and locates the Nordic UART Service
|
||||||
|
5. Subscribes to TX notifications for receiving data
|
||||||
|
6. On success, automatically navigates to the Contacts screen
|
||||||
|
7. On failure, shows a red error snackbar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## USB Connection
|
||||||
|
|
||||||
|
### How to Access
|
||||||
|
|
||||||
|
From the Scanner screen, tap the **USB** FAB button.
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
|
||||||
|
- A colored status bar at the top (same color scheme as BLE scanner)
|
||||||
|
- A list of detected USB serial ports, each showing:
|
||||||
|
- Friendly display name
|
||||||
|
- Raw port name (subtitle, only shown when it differs from the display name)
|
||||||
|
- "Connect" button
|
||||||
|
- FABs at the bottom to switch to BLE or TCP (these use `pushReplacement`, so back navigation returns to Scanner, not between USB/TCP)
|
||||||
|
|
||||||
|
### Key Interactions
|
||||||
|
|
||||||
|
- On desktop (Windows, Linux, macOS): ports are polled every 2 seconds for hot-plug detection (polling pauses while connecting/connected)
|
||||||
|
- On mobile: tap the "Scan" FAB to manually refresh
|
||||||
|
- Tap a port or its Connect button to connect
|
||||||
|
- On successful connection, navigates to Contacts screen
|
||||||
|
- On connection failure, the port list automatically refreshes
|
||||||
|
- Platform-specific error messages for common USB failures (permission denied, device missing, device detached, device busy, driver missing, port invalid, timeout, and more)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TCP Connection
|
||||||
|
|
||||||
|
### How to Access
|
||||||
|
|
||||||
|
From the Scanner screen, tap the **TCP/IP** FAB button.
|
||||||
|
|
||||||
|
### What the User Sees
|
||||||
|
|
||||||
|
- A colored status bar at the top
|
||||||
|
- **Host address** text field
|
||||||
|
- **Port number** text field
|
||||||
|
- **Connect** button
|
||||||
|
- FABs at the bottom to switch to USB or BLE
|
||||||
|
|
||||||
|
### Key Interactions
|
||||||
|
|
||||||
|
- Last-used host and port are pre-populated from saved settings
|
||||||
|
- Tap Connect to validate inputs and connect
|
||||||
|
- Host must not be empty
|
||||||
|
- Port must be a number between 1 and 65535
|
||||||
|
- Validation errors are shown as red snackbars
|
||||||
|
- The Connect button shows a spinner and "Connecting..." label while in progress
|
||||||
|
- The status bar shows the specific host:port being connected to (e.g., "Connecting to 192.168.1.1:5000")
|
||||||
|
- On success, navigates to Contacts screen and saves the host/port to settings
|
||||||
|
- On connection, the status bar shows the active TCP endpoint (e.g., "Connected to 192.168.1.1:5000")
|
||||||
|
- Error messages for timeout, unsupported platform, and connection failures
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
# Settings
|
||||||
|
|
||||||
|
## How to Access
|
||||||
|
|
||||||
|
- From the Device Screen: tap the tune/sliders icon in the app bar
|
||||||
|
- From Contacts or Channels: overflow menu (three-dot) → Settings
|
||||||
|
|
||||||
|
Settings are only accessible while a device is connected.
|
||||||
|
|
||||||
|
## Settings Screen Layout
|
||||||
|
|
||||||
|
The settings screen is a scrollable list of cards:
|
||||||
|
|
||||||
|
1. [Device Info](#device-info)
|
||||||
|
2. [App Settings](#app-settings) (link to sub-screen)
|
||||||
|
3. [Node Settings](#node-settings)
|
||||||
|
4. [Actions](#actions)
|
||||||
|
5. [Debug](#debug)
|
||||||
|
6. [Export](#export)
|
||||||
|
7. [About](#about)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Device Info
|
||||||
|
|
||||||
|
A collapsible card showing read-only device information. **Collapsed by default** — tap the header to expand with an animated chevron indicator:
|
||||||
|
|
||||||
|
| Field | Description |
|
||||||
|
|---|---|
|
||||||
|
| Name | Connected device's display name |
|
||||||
|
| ID | Device identifier |
|
||||||
|
| Status | Connected / Disconnected |
|
||||||
|
| Battery | Percentage or voltage (tap to toggle) |
|
||||||
|
| Node Name | The node's mesh identity name |
|
||||||
|
| Public Key | First 16 hex characters + "..." |
|
||||||
|
| Contacts Count | Number of known contacts |
|
||||||
|
| Channel Count | Number of configured channels |
|
||||||
|
|
||||||
|
Battery shows an alert icon and orange text when at 15% or below. The toggle only works when millivolt data is available from the firmware.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## App Settings
|
||||||
|
|
||||||
|
A dedicated sub-screen for app-level preferences (nothing here is sent to the device). All settings persist locally via SharedPreferences.
|
||||||
|
|
||||||
|
### Appearance
|
||||||
|
- **Theme**: System / Light / Dark
|
||||||
|
- **Language**: System default or one of 18 languages (English, French, Spanish, German, Polish, Slovenian, Portuguese, Italian, Chinese, Swedish, Dutch, Slovak, Bulgarian, Russian, Ukrainian, Hungarian, Japanese, Korean)
|
||||||
|
- **Enable Message Tracing**: Shows path trace overlays and extra metadata on messages
|
||||||
|
|
||||||
|
### Notifications
|
||||||
|
- **Master enable/disable**: Requests OS permission when enabling
|
||||||
|
- **Message notifications**: New direct message alerts
|
||||||
|
- **Channel message notifications**: New channel message alerts
|
||||||
|
- **Advertisement notifications**: New node discovery alerts
|
||||||
|
|
||||||
|
### Messaging
|
||||||
|
- **Clear Path on Max Retry**: Erases the stored routing path after all retries fail
|
||||||
|
- **Jump to Oldest Unread**: When opening a chat, scrolls to the oldest unread message instead of the newest
|
||||||
|
- **Auto Route Rotation**: Enables weighted routing algorithm. When enabled, expands to show five slider sub-settings (hidden when off):
|
||||||
|
- Max Route Weight (1–10, default 5, integer steps)
|
||||||
|
- Initial Route Weight (0.5–5.0, default 3.0)
|
||||||
|
- Success Increment (0.1–2.0, default 0.5, 0.1 steps)
|
||||||
|
- Failure Decrement (0.1–2.0, default 0.2, 0.1 steps)
|
||||||
|
- Max Message Retries (2–10, default 5)
|
||||||
|
|
||||||
|
### Battery
|
||||||
|
- **Battery Chemistry**: NMC / LiFePO4 / LiPo (per device, used to calibrate percentage from voltage)
|
||||||
|
|
||||||
|
### Translation
|
||||||
|
Not shown on web. Controls on-device message translation powered by a locally-downloaded ML model:
|
||||||
|
- **Enable Translation**: Translates incoming messages into the selected target language
|
||||||
|
- **Translate Composer**: Translates outgoing messages from the target language back before sending
|
||||||
|
- **Target Language**: Language to translate into (searchable list; defaults to the app language)
|
||||||
|
- **Downloaded Model**: Dropdown to select among already-downloaded translation models
|
||||||
|
- **Preset Model**: Download a curated preset model with one tap
|
||||||
|
- **Custom Model URL**: Enter a URL to download a custom GGUF-format model; shows download progress and a cancel button
|
||||||
|
|
||||||
|
### Map Display
|
||||||
|
- **Show Repeaters**: Toggle repeater markers on map
|
||||||
|
- **Show Chat Nodes**: Toggle chat node markers
|
||||||
|
- **Show Other Nodes**: Toggle room/sensor markers
|
||||||
|
- **Time Filter**: All time / Last 1h / Last 6h / Last 24h / Last week
|
||||||
|
- **Units**: Metric / Imperial
|
||||||
|
- **Offline Map Cache**: Navigate to tile download screen
|
||||||
|
|
||||||
|
### Debug
|
||||||
|
- **App Debug Logging**: Enable the in-app debug log
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Node Settings
|
||||||
|
|
||||||
|
These settings are sent directly to the connected device firmware.
|
||||||
|
|
||||||
|
### Node Name
|
||||||
|
- Opens a dialog with a text field (max 31 characters)
|
||||||
|
- Sends the new name to the device
|
||||||
|
- Confirmed via snackbar
|
||||||
|
|
||||||
|
### Radio Settings
|
||||||
|
Opens a dialog pre-populated with the device's current radio settings. Contains:
|
||||||
|
- **Preset dropdown**: 19 regional presets — selecting a preset immediately fills all fields below. Full list: Australia, Australia (Narrow), Australia SA, WA, QLD, Czech Republic, EU 433MHz, EU/UK (Long Range), EU/UK (Medium Range), EU/UK (Narrow), New Zealand, New Zealand (Narrow), Portugal 433, Portugal 869, Switzerland, USA Arizona, USA/Canada, Vietnam, Off-Grid 433, Off-Grid 869, Off-Grid 918
|
||||||
|
- **Frequency** (MHz): Free text, validated 300–2500 MHz
|
||||||
|
- **Bandwidth**: Dropdown (7.8 / 10.4 / 15.6 / 20.8 / 31.25 / 41.7 / 62.5 / 125 / 250 / 500 kHz)
|
||||||
|
- **Spreading Factor**: SF5–SF12
|
||||||
|
- **Coding Rate**: 4/5, 4/6, 4/7, 4/8
|
||||||
|
- **TX Power** (dBm): Validated 0 to device max (typically 22 dBm)
|
||||||
|
- **Client Repeat** toggle: Only shown on firmware v9+; requires frequency to be exactly 433.000, 869.000, or 918.000 MHz (the Off-Grid presets). Save is blocked with a warning if enabled on other frequencies
|
||||||
|
|
||||||
|
### Location
|
||||||
|
Opens a dialog pre-populated with the device's current coordinates (if known):
|
||||||
|
- Latitude and longitude fields (decimal, 6 decimal places). If only one field is provided, the other uses the device's current value
|
||||||
|
- If GPS-capable hardware (detected via `gps` custom variable):
|
||||||
|
- GPS Update Interval (seconds, 60–86399, default 900 = 15 minutes). Validated and sent separately before lat/lon
|
||||||
|
- Enable GPS toggle (takes effect immediately, not deferred to Save)
|
||||||
|
- Validation: lat ±90, lon ±180
|
||||||
|
|
||||||
|
### Contact Settings
|
||||||
|
Five toggles controlling which node types are auto-added when heard:
|
||||||
|
- Auto-add Chat Users
|
||||||
|
- Auto-add Repeaters
|
||||||
|
- Auto-add Room Servers
|
||||||
|
- Auto-add Sensors
|
||||||
|
- Overwrite Oldest (when contact list is full)
|
||||||
|
|
||||||
|
### Privacy Mode
|
||||||
|
Opens a confirmation dialog with three buttons: Cancel, Enable, and Disable. Both states can be set from the same dialog regardless of current state. A snackbar confirms which state was applied. When on, the node stops broadcasting its location in advertisements.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Actions
|
||||||
|
|
||||||
|
One-tap device operations:
|
||||||
|
|
||||||
|
| Action | Description |
|
||||||
|
|---|---|
|
||||||
|
| Send Advertisement | Floods the mesh with your node's advertisement |
|
||||||
|
| Sync Time | Sends current Unix timestamp to the device |
|
||||||
|
| Refresh Contacts | Re-requests the full contact list |
|
||||||
|
| Reboot Device | Confirmation dialog → reboots the device (shown in orange) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Debug
|
||||||
|
|
||||||
|
Two log viewers accessible via list tiles:
|
||||||
|
|
||||||
|
### BLE Debug Log
|
||||||
|
Two views (togglable via segmented button):
|
||||||
|
- **Frames view**: Direction icon, description, hex preview, timestamp per frame. Long-press to copy hex.
|
||||||
|
- **Raw Log RX view**: Decoded LoRa packets with route type, payload type, path, and summary.
|
||||||
|
- Copy-all and Clear buttons in the app bar.
|
||||||
|
|
||||||
|
### App Debug Log
|
||||||
|
Structured log entries (Info / Warning / Error), with tag, message, and timestamp.
|
||||||
|
- Must be enabled first in App Settings → Debug
|
||||||
|
- Copy-all and Clear buttons
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Export
|
||||||
|
|
||||||
|
Three GPX export options (not available on web):
|
||||||
|
|
||||||
|
| Option | Exports |
|
||||||
|
|---|---|
|
||||||
|
| Export Repeaters | Repeaters and Rooms with GPS coordinates |
|
||||||
|
| Export Contacts | Chat contacts with GPS coordinates |
|
||||||
|
| Export All | All contacts with GPS coordinates |
|
||||||
|
|
||||||
|
Each creates a `.gpx` file and opens the OS share sheet. Feedback via snackbar for four outcomes: success, no contacts with coordinates, feature not available (web), or error.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
Shows the standard Flutter about dialog with app name, version, and legal notice.
|
||||||
@@ -20,7 +20,5 @@
|
|||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
|
||||||
<string>13.0</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
+4
-1
@@ -1,4 +1,4 @@
|
|||||||
platform :ios, '15.5'
|
platform :ios, '16.4'
|
||||||
|
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
@@ -32,5 +32,8 @@ end
|
|||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
flutter_additional_ios_build_settings(target)
|
flutter_additional_ios_build_settings(target)
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.4'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+13
-84
@@ -7,57 +7,13 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- flutter_local_notifications (0.0.1):
|
- flutter_local_notifications (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- GoogleDataTransport (10.1.0):
|
- mobile_scanner (7.0.0):
|
||||||
- nanopb (~> 3.30910.0)
|
|
||||||
- PromisesObjC (~> 2.4)
|
|
||||||
- GoogleMLKit/BarcodeScanning (7.0.0):
|
|
||||||
- GoogleMLKit/MLKitCore
|
|
||||||
- MLKitBarcodeScanning (~> 6.0.0)
|
|
||||||
- GoogleMLKit/MLKitCore (7.0.0):
|
|
||||||
- MLKitCommon (~> 12.0.0)
|
|
||||||
- GoogleToolboxForMac/Defines (4.2.1)
|
|
||||||
- GoogleToolboxForMac/Logger (4.2.1):
|
|
||||||
- GoogleToolboxForMac/Defines (= 4.2.1)
|
|
||||||
- "GoogleToolboxForMac/NSData+zlib (4.2.1)":
|
|
||||||
- GoogleToolboxForMac/Defines (= 4.2.1)
|
|
||||||
- GoogleUtilities/Environment (8.1.0):
|
|
||||||
- GoogleUtilities/Privacy
|
|
||||||
- GoogleUtilities/Logger (8.1.0):
|
|
||||||
- GoogleUtilities/Environment
|
|
||||||
- GoogleUtilities/Privacy
|
|
||||||
- GoogleUtilities/Privacy (8.1.0)
|
|
||||||
- GoogleUtilities/UserDefaults (8.1.0):
|
|
||||||
- GoogleUtilities/Logger
|
|
||||||
- GoogleUtilities/Privacy
|
|
||||||
- GTMSessionFetcher/Core (3.5.0)
|
|
||||||
- MLImage (1.0.0-beta6)
|
|
||||||
- MLKitBarcodeScanning (6.0.0):
|
|
||||||
- MLKitCommon (~> 12.0)
|
|
||||||
- MLKitVision (~> 8.0)
|
|
||||||
- MLKitCommon (12.0.0):
|
|
||||||
- GoogleDataTransport (~> 10.0)
|
|
||||||
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
|
|
||||||
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
|
|
||||||
- GoogleUtilities/Logger (~> 8.0)
|
|
||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
|
||||||
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
|
|
||||||
- MLKitVision (8.0.0):
|
|
||||||
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
|
|
||||||
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
|
|
||||||
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
|
|
||||||
- MLImage (= 1.0.0-beta6)
|
|
||||||
- MLKitCommon (~> 12.0)
|
|
||||||
- mobile_scanner (6.0.2):
|
|
||||||
- Flutter
|
- Flutter
|
||||||
- GoogleMLKit/BarcodeScanning (~> 7.0.0)
|
- FlutterMacOS
|
||||||
- nanopb (3.30910.0):
|
|
||||||
- nanopb/decode (= 3.30910.0)
|
|
||||||
- nanopb/encode (= 3.30910.0)
|
|
||||||
- nanopb/decode (3.30910.0)
|
|
||||||
- nanopb/encode (3.30910.0)
|
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- PromisesObjC (2.4.0)
|
- share_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@@ -66,34 +22,18 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- wakelock_plus (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_blue_plus_darwin (from `.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
|
- flutter_blue_plus_darwin (from `.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
|
||||||
- flutter_foreground_task (from `.symlinks/plugins/flutter_foreground_task/ios`)
|
- flutter_foreground_task (from `.symlinks/plugins/flutter_foreground_task/ios`)
|
||||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`)
|
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
|
||||||
|
|
||||||
SPEC REPOS:
|
|
||||||
trunk:
|
|
||||||
- GoogleDataTransport
|
|
||||||
- GoogleMLKit
|
|
||||||
- GoogleToolboxForMac
|
|
||||||
- GoogleUtilities
|
|
||||||
- GTMSessionFetcher
|
|
||||||
- MLImage
|
|
||||||
- MLKitBarcodeScanning
|
|
||||||
- MLKitCommon
|
|
||||||
- MLKitVision
|
|
||||||
- nanopb
|
|
||||||
- PromisesObjC
|
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
Flutter:
|
Flutter:
|
||||||
@@ -105,41 +45,30 @@ EXTERNAL SOURCES:
|
|||||||
flutter_local_notifications:
|
flutter_local_notifications:
|
||||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||||
mobile_scanner:
|
mobile_scanner:
|
||||||
:path: ".symlinks/plugins/mobile_scanner/ios"
|
:path: ".symlinks/plugins/mobile_scanner/darwin"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
|
share_plus:
|
||||||
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
sqflite_darwin:
|
sqflite_darwin:
|
||||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
wakelock_plus:
|
|
||||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
|
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
|
||||||
flutter_foreground_task: a159d2c2173b33699ddb3e6c2a067045d7cebb89
|
flutter_foreground_task: a159d2c2173b33699ddb3e6c2a067045d7cebb89
|
||||||
flutter_local_notifications: 395056b3175ba4f08480a7c5de30cd36d69827e4
|
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
|
||||||
GoogleMLKit: eff9e23ec1d90ea4157a1ee2e32a4f610c5b3318
|
|
||||||
GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8
|
|
||||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
|
||||||
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
|
||||||
MLImage: 0ad1c5f50edd027672d8b26b0fee78a8b4a0fc56
|
|
||||||
MLKitBarcodeScanning: 0a3064da0a7f49ac24ceb3cb46a5bc67496facd2
|
|
||||||
MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d
|
|
||||||
MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e
|
|
||||||
mobile_scanner: af8f71879eaba2bbcb4d86c6a462c3c0e7f23036
|
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
|
||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
|
||||||
|
|
||||||
PODFILE CHECKSUM: 570da2a631486c6bd6496bed1e605e63e2471be5
|
PODFILE CHECKSUM: e42b502c78c33aa1ed9d42eaea8960ce2139504b
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@@ -179,6 +179,7 @@
|
|||||||
97C146EC1CF9000F007C117D /* Resources */,
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
F0D7F2413C6E4B7A9B1C2D3E /* Fix Native Asset Minimum OS */,
|
||||||
B788CEDB957A87EE8AC593BB /* [CP] Copy Pods Resources */,
|
B788CEDB957A87EE8AC593BB /* [CP] Copy Pods Resources */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
@@ -299,6 +300,22 @@
|
|||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
|
F0D7F2413C6E4B7A9B1C2D3E /* Fix Native Asset Minimum OS */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}",
|
||||||
|
);
|
||||||
|
name = "Fix Native Asset Minimum OS";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "set -e\nFRAMEWORKS_DIR=\"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}\"\nMIN_OS=\"${IPHONEOS_DEPLOYMENT_TARGET}\"\nif [ ! -d \"$FRAMEWORKS_DIR\" ] || [ -z \"$MIN_OS\" ]; then\n exit 0\nfi\nfind \"$FRAMEWORKS_DIR\" -maxdepth 2 -name Info.plist | while read -r plist; do\n bundle_id=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' \"$plist\" 2>/dev/null || true)\n case \"$bundle_id\" in\n io.flutter.flutter.native-assets.*)\n /usr/libexec/PlistBuddy -c \"Set :MinimumOSVersion $MIN_OS\" \"$plist\" 2>/dev/null || \\\n /usr/libexec/PlistBuddy -c \"Add :MinimumOSVersion string $MIN_OS\" \"$plist\"\n ;;\n esac\ndone\n";
|
||||||
|
};
|
||||||
DE3B2E091393835C0B38492E /* [CP] Check Pods Manifest.lock */ = {
|
DE3B2E091393835C0B38492E /* [CP] Check Pods Manifest.lock */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -414,7 +431,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@@ -540,7 +557,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -591,7 +608,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ import Flutter
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||||
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-19
@@ -2,6 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
@@ -22,8 +24,46 @@
|
|||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>http</string>
|
||||||
|
<string>https</string>
|
||||||
|
</array>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||||
|
<string>This app uses Bluetooth to communicate with MeshCore devices.</string>
|
||||||
|
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||||
|
<string>This app uses Bluetooth to communicate with MeshCore devices.</string>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>This app uses the camera to scan QR codes for joining communities.</string>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<false/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneClassName</key>
|
||||||
|
<string>UIWindowScene</string>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>flutter</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>FlutterSceneDelegate</string>
|
||||||
|
<key>UISceneStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIBackgroundModes</key>
|
||||||
|
<array>
|
||||||
|
<string>bluetooth-central</string>
|
||||||
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
@@ -41,24 +81,5 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
|
||||||
<true/>
|
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
|
||||||
<true/>
|
|
||||||
<key>UIBackgroundModes</key>
|
|
||||||
<array>
|
|
||||||
<string>bluetooth-central</string>
|
|
||||||
</array>
|
|
||||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
|
||||||
<string>This app uses Bluetooth to communicate with MeshCore devices.</string>
|
|
||||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
|
||||||
<string>This app uses Bluetooth to communicate with MeshCore devices.</string>
|
|
||||||
<key>NSCameraUsageDescription</key>
|
|
||||||
<string>This app uses the camera to scan QR codes for joining communities.</string>
|
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
|
||||||
<array>
|
|
||||||
<string>http</string>
|
|
||||||
<string>https</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
+2263
-478
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import '../services/app_debug_log_service.dart';
|
||||||
|
import '../services/tcp_transport_service.dart';
|
||||||
|
|
||||||
|
/// Manages TCP transport for MeshCore devices.
|
||||||
|
///
|
||||||
|
/// Owns the [TcpTransportService] and TCP-specific connection state.
|
||||||
|
/// The main [MeshCoreConnector] delegates all TCP operations here.
|
||||||
|
class MeshCoreTcpConnector {
|
||||||
|
final TcpTransportService _service = TcpTransportService();
|
||||||
|
AppDebugLogService? _debugLog;
|
||||||
|
StreamSubscription<Uint8List>? _frameSubscription;
|
||||||
|
|
||||||
|
// --- Getters ---
|
||||||
|
String? get activeEndpoint => _service.activeEndpoint;
|
||||||
|
bool get isConnected => _service.isConnected;
|
||||||
|
|
||||||
|
// --- Configuration ---
|
||||||
|
void setDebugLogService(AppDebugLogService? service) {
|
||||||
|
_debugLog = service;
|
||||||
|
_service.setDebugLogService(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Connection lifecycle ---
|
||||||
|
Future<void> connect({required String host, required int port}) async {
|
||||||
|
_debugLog?.info('TcpConnector.connect endpoint=$host:$port', tag: 'TCP');
|
||||||
|
await _frameSubscription?.cancel();
|
||||||
|
_frameSubscription = null;
|
||||||
|
await _service.connect(host: host, port: port);
|
||||||
|
_debugLog?.info(
|
||||||
|
'TcpConnector.connect done, endpoint=${_service.activeEndpoint}',
|
||||||
|
tag: 'TCP',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription<Uint8List> listenFrames({
|
||||||
|
required void Function(Uint8List) onFrame,
|
||||||
|
required void Function(Object, StackTrace?) onError,
|
||||||
|
required void Function() onDone,
|
||||||
|
}) {
|
||||||
|
_frameSubscription = _service.frameStream.listen(
|
||||||
|
onFrame,
|
||||||
|
onError: onError,
|
||||||
|
onDone: onDone,
|
||||||
|
);
|
||||||
|
return _frameSubscription!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> cancelFrameSubscription() async {
|
||||||
|
await _frameSubscription?.cancel();
|
||||||
|
_frameSubscription = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> disconnect() async {
|
||||||
|
if (!_service.isConnected && _frameSubscription == null) return;
|
||||||
|
_debugLog?.info('TcpConnector.disconnect', tag: 'TCP');
|
||||||
|
await _frameSubscription?.cancel();
|
||||||
|
_frameSubscription = null;
|
||||||
|
await _service.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> write(Uint8List data) => _service.write(data);
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_frameSubscription?.cancel();
|
||||||
|
_service.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,6 +53,9 @@ class MeshCoreUsbManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> disconnect() async {
|
Future<void> disconnect() async {
|
||||||
|
if (!_service.isConnected && _activePortKey == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_debugLog?.info('UsbManager.disconnect', tag: 'USB');
|
_debugLog?.info('UsbManager.disconnect', tag: 'USB');
|
||||||
await _service.disconnect();
|
await _service.disconnect();
|
||||||
_activePortKey = null;
|
_activePortKey = null;
|
||||||
@@ -61,6 +64,8 @@ class MeshCoreUsbManager {
|
|||||||
|
|
||||||
Future<void> write(Uint8List data) => _service.write(data);
|
Future<void> write(Uint8List data) => _service.write(data);
|
||||||
|
|
||||||
|
Future<void> writeRaw(Uint8List data) => _service.writeRaw(data);
|
||||||
|
|
||||||
// --- Label management ---
|
// --- Label management ---
|
||||||
void updateConnectedLabel(String selfName) {
|
void updateConnectedLabel(String selfName) {
|
||||||
_service.updateConnectedLabel(selfName);
|
_service.updateConnectedLabel(selfName);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
// Buffer Reader - sequential binary data reader with pointer tracking
|
// Buffer Reader - sequential binary data reader with pointer tracking
|
||||||
class BufferReader {
|
class BufferReader {
|
||||||
int _pointer = 0;
|
int _pointer = 0;
|
||||||
@@ -37,16 +39,6 @@ class BufferReader {
|
|||||||
|
|
||||||
Uint8List readRemainingBytes() => readBytes(remaining);
|
Uint8List readRemainingBytes() => readBytes(remaining);
|
||||||
|
|
||||||
String readString() {
|
|
||||||
_lastPointer = _pointer;
|
|
||||||
final value = readRemainingBytes();
|
|
||||||
try {
|
|
||||||
return utf8.decode(Uint8List.fromList(value), allowMalformed: true);
|
|
||||||
} catch (e) {
|
|
||||||
return String.fromCharCodes(value); // Latin-1 fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String readCStringGreedy(int maxLength) {
|
String readCStringGreedy(int maxLength) {
|
||||||
_lastPointer = _pointer;
|
_lastPointer = _pointer;
|
||||||
final value = <int>[];
|
final value = <int>[];
|
||||||
@@ -62,11 +54,12 @@ class BufferReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String readCString(int maxLength) {
|
String readCString({int maxLength = -1}) {
|
||||||
final backupPointer = _pointer;
|
final backupPointer = _pointer;
|
||||||
final value = <int>[];
|
final value = <int>[];
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
while (counter < maxLength) {
|
final maxLen = maxLength >= 0 ? maxLength : remaining;
|
||||||
|
while (counter < maxLen) {
|
||||||
final byte = readByte();
|
final byte = readByte();
|
||||||
if (byte == 0) break;
|
if (byte == 0) break;
|
||||||
value.add(byte);
|
value.add(byte);
|
||||||
@@ -148,6 +141,19 @@ class BufferWriter {
|
|||||||
void writeHex(String hex) {
|
void writeHex(String hex) {
|
||||||
writeBytes(hex2Uint8List(hex));
|
writeBytes(hex2Uint8List(hex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void writeBytesPadded(Uint8List bytes, int totalLength) {
|
||||||
|
// Path data (64 bytes, zero-padded)
|
||||||
|
final bytesPadded = Uint8List(totalLength);
|
||||||
|
final len = bytes.length < totalLength ? bytes.length : totalLength;
|
||||||
|
if (bytes.isNotEmpty && len > 0) {
|
||||||
|
final copyLen = bytes.length < totalLength ? bytes.length : totalLength;
|
||||||
|
for (int i = 0; i < copyLen; i++) {
|
||||||
|
bytesPadded[i] = bytes[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeBytes(bytesPadded);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint8List hex2Uint8List(String hex) {
|
Uint8List hex2Uint8List(String hex) {
|
||||||
@@ -196,17 +202,20 @@ const int cmdGetChannel = 31;
|
|||||||
const int cmdSetChannel = 32;
|
const int cmdSetChannel = 32;
|
||||||
const int cmdSendTracePath = 36;
|
const int cmdSendTracePath = 36;
|
||||||
const int cmdSetOtherParams = 38;
|
const int cmdSetOtherParams = 38;
|
||||||
const int cmdSendAnonReq = 57;
|
const int cmdSendTelemetryReq = 39;
|
||||||
const int cmdGetTelemetryReq = 39;
|
|
||||||
const int cmdGetCustomVar = 40;
|
const int cmdGetCustomVar = 40;
|
||||||
const int cmdSetCustomVar = 41;
|
const int cmdSetCustomVar = 41;
|
||||||
const int cmdSendBinaryReq = 50;
|
const int cmdSendBinaryReq = 50;
|
||||||
|
const int cmdGetStats = 56;
|
||||||
|
const int cmdSendAnonReq = 57;
|
||||||
const int cmdSetAutoAddConfig = 58;
|
const int cmdSetAutoAddConfig = 58;
|
||||||
const int cmdGetAutoAddConfig = 59;
|
const int cmdGetAutoAddConfig = 59;
|
||||||
|
const int cmdSetPathHashMode = 61;
|
||||||
|
|
||||||
// Text message types
|
// Text message types
|
||||||
const int txtTypePlain = 0;
|
const int txtTypePlain = 0;
|
||||||
const int txtTypeCliData = 1;
|
const int txtTypeCliData = 1;
|
||||||
|
const int txtTypeSigned = 2;
|
||||||
|
|
||||||
// Repeater request types (for server requests)
|
// Repeater request types (for server requests)
|
||||||
const int reqTypeGetStatus = 0x01;
|
const int reqTypeGetStatus = 0x01;
|
||||||
@@ -238,6 +247,11 @@ const int respCodeChannelMsgRecvV3 = 17;
|
|||||||
const int respCodeChannelInfo = 18;
|
const int respCodeChannelInfo = 18;
|
||||||
const int respCodeCustomVars = 21;
|
const int respCodeCustomVars = 21;
|
||||||
const int respCodeAutoAddConfig = 25;
|
const int respCodeAutoAddConfig = 25;
|
||||||
|
const int respCodeStats = 24;
|
||||||
|
|
||||||
|
const int statsTypeCore = 0;
|
||||||
|
const int statsTypeRadio = 1;
|
||||||
|
const int statsTypePackets = 2;
|
||||||
|
|
||||||
// Push codes (async from device)
|
// Push codes (async from device)
|
||||||
const int pushCodeAdvert = 0x80;
|
const int pushCodeAdvert = 0x80;
|
||||||
@@ -259,6 +273,10 @@ const int advTypeRepeater = 2;
|
|||||||
const int advTypeRoom = 3;
|
const int advTypeRoom = 3;
|
||||||
const int advTypeSensor = 4;
|
const int advTypeSensor = 4;
|
||||||
|
|
||||||
|
const int teleModeDeny = 0;
|
||||||
|
const int teleModeAllowFlags = 1; // use contact.flags
|
||||||
|
const int teleModeAllowAll = 2;
|
||||||
|
|
||||||
// Payload Types
|
// Payload Types
|
||||||
const int payloadTypeREQ =
|
const int payloadTypeREQ =
|
||||||
0x00; // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
|
0x00; // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
|
||||||
@@ -297,11 +315,12 @@ const int autoAddSensorFlag =
|
|||||||
|
|
||||||
// Sizes
|
// Sizes
|
||||||
const int pubKeySize = 32;
|
const int pubKeySize = 32;
|
||||||
|
const int signatureSize = 64;
|
||||||
const int maxPathSize = 64;
|
const int maxPathSize = 64;
|
||||||
const int pathHashSize = 1;
|
const int pathHashSize = 1;
|
||||||
const int maxNameSize = 32;
|
const int maxNameSize = 32;
|
||||||
const int maxFrameSize = 172;
|
const int maxFrameSize = 172;
|
||||||
const int appProtocolVersion = 3;
|
const int appProtocolVersion = 4;
|
||||||
// Matches firmware MAX_TEXT_LEN (10 * CIPHER_BLOCK_SIZE).
|
// Matches firmware MAX_TEXT_LEN (10 * CIPHER_BLOCK_SIZE).
|
||||||
const int maxTextPayloadBytes = 160;
|
const int maxTextPayloadBytes = 160;
|
||||||
const int _sendTextMsgOverheadBytes =
|
const int _sendTextMsgOverheadBytes =
|
||||||
@@ -339,6 +358,9 @@ const int contactPubKeyOffset = 1;
|
|||||||
const int contactTypeOffset = 33;
|
const int contactTypeOffset = 33;
|
||||||
const int contactFlagsOffset = 34;
|
const int contactFlagsOffset = 34;
|
||||||
const int contactFlagFavorite = 0x01;
|
const int contactFlagFavorite = 0x01;
|
||||||
|
const int contactFlagTeleBase = 0x02; // 'base' permission includes battery
|
||||||
|
const int contactFlagTeleLoc = 0x04;
|
||||||
|
const int contactFlagTeleEnv = 0x08; //access environment sensors
|
||||||
const int contactPathLenOffset = 35;
|
const int contactPathLenOffset = 35;
|
||||||
const int contactPathOffset = 36;
|
const int contactPathOffset = 36;
|
||||||
const int contactNameOffset = 100;
|
const int contactNameOffset = 100;
|
||||||
@@ -357,52 +379,44 @@ const int msgTextOffset = 38;
|
|||||||
class ParsedContactText {
|
class ParsedContactText {
|
||||||
final Uint8List senderPrefix;
|
final Uint8List senderPrefix;
|
||||||
final String text;
|
final String text;
|
||||||
|
|
||||||
const ParsedContactText({required this.senderPrefix, required this.text});
|
const ParsedContactText({required this.senderPrefix, required this.text});
|
||||||
}
|
}
|
||||||
|
|
||||||
ParsedContactText? parseContactMessageText(Uint8List frame) {
|
ParsedContactText? parseContactMessageText(Uint8List frame) {
|
||||||
if (frame.isEmpty) return null;
|
if (frame.isEmpty) return null;
|
||||||
final code = frame[0];
|
|
||||||
if (code != respCodeContactMsgRecv && code != respCodeContactMsgRecvV3) {
|
final message = BufferReader(frame);
|
||||||
|
try {
|
||||||
|
final code = message.readByte();
|
||||||
|
if (code != respCodeContactMsgRecv && code != respCodeContactMsgRecvV3) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Companion radio layout:
|
||||||
|
// [code][snr?][res?][res?][prefix x6][path_len][txt_type][timestamp x4][extra?][text...]
|
||||||
|
if (code == respCodeContactMsgRecvV3) {
|
||||||
|
// Skip SNR and reserved bytes in v3 layout
|
||||||
|
message.skipBytes(3);
|
||||||
|
}
|
||||||
|
final senderPrefix = message.readBytes(6); // public key
|
||||||
|
message.skipBytes(1); // path length
|
||||||
|
final textType = message.readByte();
|
||||||
|
message.skipBytes(4); // timestamp (4 bytes)
|
||||||
|
|
||||||
|
final shiftedType = textType >> 2;
|
||||||
|
final isSigned = shiftedType == txtTypeSigned || textType == txtTypeSigned;
|
||||||
|
if (isSigned) {
|
||||||
|
// Signed messages have a 4-byte signature after the timestamp, before the text
|
||||||
|
message.skipBytes(4);
|
||||||
|
}
|
||||||
|
final text = message.readCString();
|
||||||
|
if (text.isEmpty) return null;
|
||||||
|
|
||||||
|
return ParsedContactText(senderPrefix: senderPrefix, text: text);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error parsing contact message text: $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Companion radio layout:
|
|
||||||
// [code][snr?][res?][res?][prefix x6][path_len][txt_type][timestamp x4][extra?][text...]
|
|
||||||
final isV3 = code == respCodeContactMsgRecvV3;
|
|
||||||
final prefixOffset = isV3 ? 4 : 1;
|
|
||||||
const prefixLen = 6;
|
|
||||||
final txtTypeOffset = prefixOffset + prefixLen + 1;
|
|
||||||
final timestampOffset = txtTypeOffset + 1;
|
|
||||||
final baseTextOffset = timestampOffset + 4;
|
|
||||||
if (frame.length <= baseTextOffset) return null;
|
|
||||||
|
|
||||||
final flags = frame[txtTypeOffset];
|
|
||||||
final shiftedType = flags >> 2;
|
|
||||||
final rawType = flags;
|
|
||||||
final isPlain = shiftedType == txtTypePlain || rawType == txtTypePlain;
|
|
||||||
final isCli = shiftedType == txtTypeCliData || rawType == txtTypeCliData;
|
|
||||||
if (!isPlain && !isCli) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var text = readCString(
|
|
||||||
frame,
|
|
||||||
baseTextOffset,
|
|
||||||
frame.length - baseTextOffset,
|
|
||||||
).trim();
|
|
||||||
if (text.isEmpty && frame.length > baseTextOffset + 4) {
|
|
||||||
text = readCString(
|
|
||||||
frame,
|
|
||||||
baseTextOffset + 4,
|
|
||||||
frame.length - (baseTextOffset + 4),
|
|
||||||
).trim();
|
|
||||||
}
|
|
||||||
if (text.isEmpty) return null;
|
|
||||||
|
|
||||||
final senderPrefix = frame.sublist(prefixOffset, prefixOffset + prefixLen);
|
|
||||||
return ParsedContactText(senderPrefix: senderPrefix, text: text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to read uint32 little-endian
|
// Helper to read uint32 little-endian
|
||||||
@@ -425,18 +439,9 @@ int readInt32LE(Uint8List data, int offset) {
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to read null-terminated UTF-8 string
|
// Helper to convert uint32 to hex string
|
||||||
String readCString(Uint8List data, int offset, int maxLen) {
|
String ackHashToHex(int ackHash) {
|
||||||
int end = offset;
|
return ackHash.toRadixString(16).padLeft(8, '0');
|
||||||
while (end < offset + maxLen && end < data.length && data[end] != 0) {
|
|
||||||
end++;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return utf8.decode(data.sublist(offset, end), allowMalformed: true);
|
|
||||||
} catch (e) {
|
|
||||||
// Fallback to Latin-1 if UTF-8 decoding fails
|
|
||||||
return String.fromCharCodes(data.sublist(offset, end));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to convert public key to hex string
|
// Helper to convert public key to hex string
|
||||||
@@ -496,7 +501,7 @@ Uint8List buildSendTextMsgFrame(
|
|||||||
final writer = BufferWriter();
|
final writer = BufferWriter();
|
||||||
writer.writeByte(cmdSendTxtMsg);
|
writer.writeByte(cmdSendTxtMsg);
|
||||||
writer.writeByte(txtTypePlain);
|
writer.writeByte(txtTypePlain);
|
||||||
writer.writeByte(attempt.clamp(0, 3));
|
writer.writeByte(attempt.clamp(0, 255));
|
||||||
writer.writeUInt32LE(timestamp);
|
writer.writeUInt32LE(timestamp);
|
||||||
writer.writeBytes(recipientPubKey.sublist(0, 6));
|
writer.writeBytes(recipientPubKey.sublist(0, 6));
|
||||||
writer.writeString(text);
|
writer.writeString(text);
|
||||||
@@ -556,6 +561,17 @@ Uint8List buildGetBattAndStorageFrame() {
|
|||||||
return Uint8List.fromList([cmdGetBattAndStorage]);
|
return Uint8List.fromList([cmdGetBattAndStorage]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Companion radio stats: [56][statsType] where statsType is statsTypeCore/Radio/Packets.
|
||||||
|
Uint8List buildGetStatsFrame(int statsType) {
|
||||||
|
return Uint8List.fromList([cmdGetStats, statsType & 0xFF]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Path hash width on air: [61][0][mode], mode 0..2 → (mode+1) bytes per hop hash.
|
||||||
|
Uint8List buildSetPathHashModeFrame(int mode) {
|
||||||
|
final m = mode.clamp(0, 2);
|
||||||
|
return Uint8List.fromList([cmdSetPathHashMode, 0, m]);
|
||||||
|
}
|
||||||
|
|
||||||
// Build CMD_SET_DEVICE_TIME frame
|
// Build CMD_SET_DEVICE_TIME frame
|
||||||
Uint8List buildSetDeviceTimeFrame(int timestamp) {
|
Uint8List buildSetDeviceTimeFrame(int timestamp) {
|
||||||
final writer = BufferWriter();
|
final writer = BufferWriter();
|
||||||
@@ -676,14 +692,17 @@ Uint8List buildResetPathFrame(Uint8List pubKey) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build CMD_ADD_UPDATE_CONTACT frame to set custom path
|
// Build CMD_ADD_UPDATE_CONTACT frame to set custom path
|
||||||
// Format: [cmd][pub_key x32][type][flags][path_len][path x64][name x32][timestamp x4]
|
// Format: [cmd][pub_key x32][type][flags][path_len][path x64][name x32][Lat? x4, Lon? x4][timestamp? x4]
|
||||||
Uint8List buildUpdateContactPathFrame(
|
Uint8List buildUpdateContactPathFrame(
|
||||||
Uint8List pubKey,
|
Uint8List pubKey,
|
||||||
Uint8List customPath,
|
Uint8List path,
|
||||||
int pathLen, {
|
int pathLen, {
|
||||||
int type = 1, // ADV_TYPE_CHAT
|
int type = 1, // ADV_TYPE_CHAT
|
||||||
int flags = 0,
|
int flags = 0,
|
||||||
String name = '',
|
String name = '',
|
||||||
|
double? lat,
|
||||||
|
double? lon,
|
||||||
|
DateTime? lastModified,
|
||||||
}) {
|
}) {
|
||||||
final writer = BufferWriter();
|
final writer = BufferWriter();
|
||||||
writer.writeByte(cmdAddUpdateContact);
|
writer.writeByte(cmdAddUpdateContact);
|
||||||
@@ -692,17 +711,7 @@ Uint8List buildUpdateContactPathFrame(
|
|||||||
writer.writeByte(flags);
|
writer.writeByte(flags);
|
||||||
writer.writeByte(pathLen);
|
writer.writeByte(pathLen);
|
||||||
|
|
||||||
// Path data (64 bytes, zero-padded)
|
writer.writeBytesPadded(path, maxPathSize);
|
||||||
final pathPadded = Uint8List(maxPathSize);
|
|
||||||
if (customPath.isNotEmpty && pathLen > 0) {
|
|
||||||
final copyLen = customPath.length < maxPathSize
|
|
||||||
? customPath.length
|
|
||||||
: maxPathSize;
|
|
||||||
for (int i = 0; i < copyLen; i++) {
|
|
||||||
pathPadded[i] = customPath[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writer.writeBytes(pathPadded);
|
|
||||||
|
|
||||||
// Name (32 bytes, null-padded)
|
// Name (32 bytes, null-padded)
|
||||||
writer.writeCString(name, maxNameSize);
|
writer.writeCString(name, maxNameSize);
|
||||||
@@ -711,6 +720,21 @@ Uint8List buildUpdateContactPathFrame(
|
|||||||
final timestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
final timestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
writer.writeUInt32LE(timestamp);
|
writer.writeUInt32LE(timestamp);
|
||||||
|
|
||||||
|
// Optional [Lat x4, Lon x4][timestamp x4] tail per the doc comment above.
|
||||||
|
// Emit 8 bytes of position (zero-filled when only lastModified is provided)
|
||||||
|
// followed by an optional 4-byte timestamp. Earlier code emitted the
|
||||||
|
// position block twice, which corrupted the tail and caused the firmware
|
||||||
|
// to parse the second lat as the timestamp. See #427.
|
||||||
|
final hasLocation = lat != null && lon != null;
|
||||||
|
if (hasLocation || lastModified != null) {
|
||||||
|
writer.writeInt32LE(hasLocation ? (lat * 1e6).round() : 0);
|
||||||
|
writer.writeInt32LE(hasLocation ? (lon * 1e6).round() : 0);
|
||||||
|
if (lastModified != null) {
|
||||||
|
final lastModifiedTimestamp = lastModified.millisecondsSinceEpoch ~/ 1000;
|
||||||
|
writer.writeUInt32LE(lastModifiedTimestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return writer.toBytes();
|
return writer.toBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -811,7 +835,7 @@ Uint8List buildSendCliCommandFrame(
|
|||||||
final writer = BufferWriter();
|
final writer = BufferWriter();
|
||||||
writer.writeByte(cmdSendTxtMsg);
|
writer.writeByte(cmdSendTxtMsg);
|
||||||
writer.writeByte(txtTypeCliData);
|
writer.writeByte(txtTypeCliData);
|
||||||
writer.writeByte(attempt.clamp(0, 3));
|
writer.writeByte(attempt.clamp(0, 255));
|
||||||
writer.writeUInt32LE(timestamp);
|
writer.writeUInt32LE(timestamp);
|
||||||
writer.writeBytes(repeaterPubKey.sublist(0, 6));
|
writer.writeBytes(repeaterPubKey.sublist(0, 6));
|
||||||
writer.writeString(command);
|
writer.writeString(command);
|
||||||
@@ -910,3 +934,18 @@ Uint8List buildSetAutoAddConfigFrame({
|
|||||||
writer.writeByte(flags);
|
writer.writeByte(flags);
|
||||||
return writer.toBytes();
|
return writer.toBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Build CMD_SEND_TELEMETRY_REQ
|
||||||
|
// Format: [cmd][reserved x3][pub_key? x32]
|
||||||
|
Uint8List buildSendTelemetryReq(Uint8List? pubKey) {
|
||||||
|
final writer = BufferWriter();
|
||||||
|
writer.writeByte(cmdSendTelemetryReq);
|
||||||
|
|
||||||
|
if (pubKey != null && pubKey.length == pubKeySize) {
|
||||||
|
writer.writeBytes(Uint8List(3)); // reserved bytes
|
||||||
|
writer.writeBytes(pubKey);
|
||||||
|
} else {
|
||||||
|
writer.writeBytes(Uint8List(4)); // reserved bytes
|
||||||
|
}
|
||||||
|
return writer.toBytes();
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
class MeshCoreUuids {
|
||||||
|
static const String service = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
|
||||||
|
static const String rxCharacteristic = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
|
||||||
|
static const String txCharacteristic = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
|
||||||
|
|
||||||
|
static const List<String> deviceNamePrefixes = [
|
||||||
|
"MeshCore-",
|
||||||
|
"Whisper-",
|
||||||
|
"WisCore-",
|
||||||
|
"Seeed",
|
||||||
|
"Lilygo",
|
||||||
|
"HT-",
|
||||||
|
"LowMesh_MC_",
|
||||||
|
"NRF52",
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -49,6 +49,25 @@ class ChatScrollController extends ScrollController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Jumps toward an off-screen message so that lazy ListView.builder builds
|
||||||
|
/// items near it. Only visible + cacheExtent items have real heights, so we
|
||||||
|
/// use proportion of maxScrollExtent (itself an estimate from built items'
|
||||||
|
/// avg height). Call [onJumped] on the next frame to ensureVisible/scroll
|
||||||
|
/// to the exact target.
|
||||||
|
void jumpToEstimatedOffset({
|
||||||
|
required int unreadCount,
|
||||||
|
required int totalMessages,
|
||||||
|
required VoidCallback onJumped,
|
||||||
|
}) {
|
||||||
|
if (!hasClients || totalMessages == 0) return;
|
||||||
|
final maxExtent = position.maxScrollExtent;
|
||||||
|
final jumpOffset = maxExtent * (unreadCount / totalMessages);
|
||||||
|
if (jumpOffset > 100) {
|
||||||
|
jumpTo(jumpOffset);
|
||||||
|
}
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => onJumped());
|
||||||
|
}
|
||||||
|
|
||||||
void scrollToBottomIfAtBottom() {
|
void scrollToBottomIfAtBottom() {
|
||||||
// Only scroll if jump button is NOT showing (i.e., already at bottom)
|
// Only scroll if jump button is NOT showing (i.e., already at bottom)
|
||||||
if (!showJumpToBottom.value && hasClients && position.maxScrollExtent > 0) {
|
if (!showJumpToBottom.value && hasClients && position.maxScrollExtent > 0) {
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
class Cyr2Lat {
|
||||||
|
static Map<String, String> _charMap = {
|
||||||
|
'А': 'A',
|
||||||
|
'В': 'B',
|
||||||
|
'Е': 'E',
|
||||||
|
'Ё': 'E',
|
||||||
|
'З': '3',
|
||||||
|
'К': 'K',
|
||||||
|
'М': 'M',
|
||||||
|
'Н': 'H',
|
||||||
|
'О': 'O',
|
||||||
|
'Р': 'P',
|
||||||
|
'С': 'C',
|
||||||
|
'Т': 'T',
|
||||||
|
'Х': 'X',
|
||||||
|
'Ь': 'b',
|
||||||
|
'а': 'a',
|
||||||
|
'е': 'e',
|
||||||
|
'ё': 'e',
|
||||||
|
'о': 'o',
|
||||||
|
'р': 'p',
|
||||||
|
'с': 'c',
|
||||||
|
'у': 'y',
|
||||||
|
'х': 'x',
|
||||||
|
};
|
||||||
|
|
||||||
|
static final RegExp _prefixRegExp = RegExp(r'\@\[[\S\s]+\] ');
|
||||||
|
|
||||||
|
static void setCharMap(Map<String, String> charMap) {
|
||||||
|
_charMap = Map.from(charMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String encode(String text) {
|
||||||
|
if (text.isEmpty) return text;
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
|
||||||
|
final senderName = extractSenderName(text);
|
||||||
|
final msgText = removeSenderName(text);
|
||||||
|
|
||||||
|
for (final rune in msgText.runes) {
|
||||||
|
final char = String.fromCharCode(rune);
|
||||||
|
buffer.write(_charMap[char] ?? char);
|
||||||
|
}
|
||||||
|
|
||||||
|
return senderName + buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String removeSenderName(String text) {
|
||||||
|
final match = _prefixRegExp.matchAsPrefix(text);
|
||||||
|
if (match != null) {
|
||||||
|
return text.substring(match.end);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String extractSenderName(String text) {
|
||||||
|
final match = _prefixRegExp.matchAsPrefix(text);
|
||||||
|
if (match != null) {
|
||||||
|
return match.group(0) ?? '';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
class GifHelper {
|
||||||
|
/// Parse a known GIF format, which can be any of:
|
||||||
|
/// g:GIFID
|
||||||
|
/// https://media.giphy.com/media/GIFID/giphy.gif
|
||||||
|
/// https://giphy.com/gifs/Optional-title-with-dashes-GIFID
|
||||||
|
///
|
||||||
|
/// GIFID is a Giphy GIF ID. The https:// is optional (and
|
||||||
|
/// can also be http://). The giphy.com/gifs form can also
|
||||||
|
/// include a trailing slash.
|
||||||
|
///
|
||||||
|
/// Returns null if text is not a valid GIF format
|
||||||
|
static String? parseGif(String text) {
|
||||||
|
final trimmed = text.trim();
|
||||||
|
final match = RegExp(r'^g:([A-Za-z0-9_-]+)$').firstMatch(trimmed);
|
||||||
|
if (match != null) {
|
||||||
|
return match.group(1);
|
||||||
|
}
|
||||||
|
final directUrlMatch = RegExp(
|
||||||
|
r'^(?:https?:\/\/)?media\.giphy\.com\/media\/([A-Za-z0-9_-]+)\/giphy\.gif$',
|
||||||
|
).firstMatch(trimmed);
|
||||||
|
if (directUrlMatch != null) {
|
||||||
|
return directUrlMatch.group(1);
|
||||||
|
}
|
||||||
|
// Giphy understands page URLs with just the ID, or any string and a
|
||||||
|
// dash before the ID, and redirects to a page with a dash-separated
|
||||||
|
// title, a dash, and the ID. IDs in this form *probably* can't
|
||||||
|
// contain dashes.
|
||||||
|
final pageMatch = RegExp(
|
||||||
|
r'^(?:https?:\/\/)?giphy\.com\/gifs\/(?:[^/?]*-)?([A-Za-z0-9_]+)\/?$',
|
||||||
|
).firstMatch(trimmed);
|
||||||
|
return pageMatch?.group(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encode a GIF in a format that parseGif() can parse.
|
||||||
|
static String encodeGif(String gifId) {
|
||||||
|
return 'g:$gifId';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,51 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import '../l10n/l10n.dart';
|
import '../l10n/l10n.dart';
|
||||||
|
import '../utils/platform_info.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
class LinkHandler {
|
class LinkHandler {
|
||||||
|
static TextStyle defaultLinkStyle(BuildContext context, TextStyle base) {
|
||||||
|
final brightness = Theme.of(context).brightness;
|
||||||
|
final orange = brightness == Brightness.dark
|
||||||
|
? const Color(0xFFFFB74D)
|
||||||
|
: const Color(0xFFE65100);
|
||||||
|
return base.copyWith(color: orange, decoration: TextDecoration.underline);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a [SelectableLinkify] on desktop or a [Linkify] on mobile.
|
||||||
|
static Widget buildLinkifyText({
|
||||||
|
required BuildContext context,
|
||||||
|
required String text,
|
||||||
|
required TextStyle style,
|
||||||
|
TextStyle? linkStyle,
|
||||||
|
}) {
|
||||||
|
final effectiveLinkStyle = linkStyle ?? defaultLinkStyle(context, style);
|
||||||
|
const options = LinkifyOptions(humanize: false, defaultToHttps: false);
|
||||||
|
const linkifiers = [UrlLinkifier(), EmailLinkifier()];
|
||||||
|
void onOpen(LinkableElement link) => handleLinkTap(context, link.url);
|
||||||
|
|
||||||
|
if (PlatformInfo.isDesktop) {
|
||||||
|
return SelectableLinkify(
|
||||||
|
text: text,
|
||||||
|
style: style,
|
||||||
|
linkStyle: effectiveLinkStyle,
|
||||||
|
options: options,
|
||||||
|
linkifiers: linkifiers,
|
||||||
|
onOpen: onOpen,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Linkify(
|
||||||
|
text: text,
|
||||||
|
style: style,
|
||||||
|
linkStyle: effectiveLinkStyle,
|
||||||
|
options: options,
|
||||||
|
linkifiers: linkifiers,
|
||||||
|
onOpen: onOpen,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static Future<void> handleLinkTap(BuildContext context, String url) async {
|
static Future<void> handleLinkTap(BuildContext context, String url) async {
|
||||||
// Show confirmation dialog
|
// Show confirmation dialog
|
||||||
final shouldOpen = await showDialog<bool>(
|
final shouldOpen = await showDialog<bool>(
|
||||||
@@ -51,21 +94,19 @@ class LinkHandler {
|
|||||||
final uri = Uri.parse(url);
|
final uri = Uri.parse(url);
|
||||||
if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) {
|
if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.chat_couldNotOpenLink(url)),
|
content: Text(context.l10n.chat_couldNotOpenLink(url)),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.chat_invalidLink),
|
content: Text(context.l10n.chat_invalidLink),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import '../models/contact.dart';
|
||||||
|
import '../connector/meshcore_protocol.dart';
|
||||||
|
|
||||||
|
class PathHelper {
|
||||||
|
static String formatPathHex(List<int> pathBytes) {
|
||||||
|
return pathBytes
|
||||||
|
.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase())
|
||||||
|
.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
static String resolvePathNames(
|
||||||
|
List<int> pathBytes,
|
||||||
|
List<Contact> allContacts,
|
||||||
|
) {
|
||||||
|
return pathBytes
|
||||||
|
.map((b) {
|
||||||
|
final hex = b.toRadixString(16).padLeft(2, '0').toUpperCase();
|
||||||
|
final matches = allContacts
|
||||||
|
.where(
|
||||||
|
(c) =>
|
||||||
|
c.publicKey.first == b &&
|
||||||
|
(c.type == advTypeRepeater || c.type == advTypeRoom),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
if (matches.isEmpty) return hex;
|
||||||
|
if (matches.length == 1) return matches.first.name;
|
||||||
|
return matches.map((c) => c.name).join(' | ');
|
||||||
|
})
|
||||||
|
.join(' \u2192 ');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,50 @@ class ReactionInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ReactionHelper {
|
class ReactionHelper {
|
||||||
|
/// Apply a reaction to a list of messages by matching the reaction hash.
|
||||||
|
///
|
||||||
|
/// [messages] - the message list to search
|
||||||
|
/// [reactionInfo] - the parsed reaction
|
||||||
|
/// [getTimestampSecs] - extract timestamp seconds from a message
|
||||||
|
/// [getSenderName] - extract sender name for hash (null for 1:1 implicit)
|
||||||
|
/// [getMessageText] - extract message text
|
||||||
|
/// [getReactions] - extract current reactions map
|
||||||
|
/// [shouldSkip] - filter function to skip messages (e.g., skip outgoing for incoming reactions)
|
||||||
|
/// [updateMessage] - callback to update the message at index with new reactions
|
||||||
|
///
|
||||||
|
/// Returns whether a match was found.
|
||||||
|
static bool applyReaction<T>({
|
||||||
|
required List<T> messages,
|
||||||
|
required ReactionInfo reactionInfo,
|
||||||
|
required int Function(T) getTimestampSecs,
|
||||||
|
required String? Function(T) getSenderName,
|
||||||
|
required String Function(T) getMessageText,
|
||||||
|
required Map<String, int> Function(T) getReactions,
|
||||||
|
required bool Function(T) shouldSkip,
|
||||||
|
required void Function(int index, Map<String, int> newReactions)
|
||||||
|
updateMessage,
|
||||||
|
}) {
|
||||||
|
final targetHash = reactionInfo.targetHash;
|
||||||
|
for (int i = messages.length - 1; i >= 0; i--) {
|
||||||
|
final msg = messages[i];
|
||||||
|
if (shouldSkip(msg)) continue;
|
||||||
|
|
||||||
|
final msgHash = computeReactionHash(
|
||||||
|
getTimestampSecs(msg),
|
||||||
|
getSenderName(msg),
|
||||||
|
getMessageText(msg),
|
||||||
|
);
|
||||||
|
if (msgHash == targetHash) {
|
||||||
|
final currentReactions = Map<String, int>.from(getReactions(msg));
|
||||||
|
currentReactions[reactionInfo.emoji] =
|
||||||
|
(currentReactions[reactionInfo.emoji] ?? 0) + 1;
|
||||||
|
updateMessage(i, currentReactions);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static List<String>? _cachedEmojis;
|
static List<String>? _cachedEmojis;
|
||||||
|
|
||||||
/// Combined list of all reaction emojis in fixed order.
|
/// Combined list of all reaction emojis in fixed order.
|
||||||
@@ -65,4 +109,9 @@ class ReactionHelper {
|
|||||||
|
|
||||||
return ReactionInfo(targetHash: match.group(1)!, emoji: emoji);
|
return ReactionInfo(targetHash: match.group(1)!, emoji: emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Encode a reaction message that parseReaction() can parse.
|
||||||
|
static String encodeReaction(String hash, String emojiIndex) {
|
||||||
|
return 'r:$hash:$emojiIndex';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
// showDismissibleSnackBar shows a [SnackBar] with tap to dismiss
|
||||||
|
// all other properties are default and optional
|
||||||
|
void showDismissibleSnackBar(
|
||||||
|
BuildContext context, {
|
||||||
|
Key? key,
|
||||||
|
required Widget content,
|
||||||
|
Color? backgroundColor,
|
||||||
|
double? elevation,
|
||||||
|
EdgeInsetsGeometry? margin,
|
||||||
|
EdgeInsetsGeometry? padding,
|
||||||
|
double? width,
|
||||||
|
ShapeBorder? shape,
|
||||||
|
HitTestBehavior? hitTestBehavior,
|
||||||
|
SnackBarBehavior? behavior,
|
||||||
|
SnackBarAction? action,
|
||||||
|
double? actionOverflowThreshold,
|
||||||
|
bool? showCloseIcon,
|
||||||
|
Color? closeIconColor,
|
||||||
|
Duration? duration,
|
||||||
|
bool? persist,
|
||||||
|
Animation<double>? animation,
|
||||||
|
void Function()? onVisible,
|
||||||
|
DismissDirection? dismissDirection,
|
||||||
|
Clip? clipBehavior,
|
||||||
|
}) {
|
||||||
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
|
messenger.showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
key: key,
|
||||||
|
content: GestureDetector(
|
||||||
|
onTap: () => messenger.hideCurrentSnackBar(),
|
||||||
|
child: content,
|
||||||
|
),
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
elevation: elevation,
|
||||||
|
margin: margin,
|
||||||
|
padding: padding,
|
||||||
|
width: width,
|
||||||
|
shape: shape,
|
||||||
|
hitTestBehavior: hitTestBehavior,
|
||||||
|
behavior: behavior,
|
||||||
|
action: action,
|
||||||
|
actionOverflowThreshold: actionOverflowThreshold,
|
||||||
|
showCloseIcon: showCloseIcon,
|
||||||
|
closeIconColor: closeIconColor,
|
||||||
|
duration: duration ?? const Duration(seconds: 4),
|
||||||
|
persist: persist,
|
||||||
|
animation: animation,
|
||||||
|
onVisible: onVisible,
|
||||||
|
dismissDirection: dismissDirection ?? DismissDirection.down,
|
||||||
|
clipBehavior: clipBehavior ?? Clip.hardEdge,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,8 +4,14 @@ import 'package:flutter/services.dart';
|
|||||||
|
|
||||||
class Utf8LengthLimitingTextInputFormatter extends TextInputFormatter {
|
class Utf8LengthLimitingTextInputFormatter extends TextInputFormatter {
|
||||||
final int maxBytes;
|
final int maxBytes;
|
||||||
|
final String Function(String)? encoder;
|
||||||
|
|
||||||
const Utf8LengthLimitingTextInputFormatter(this.maxBytes);
|
const Utf8LengthLimitingTextInputFormatter(this.maxBytes, {this.encoder});
|
||||||
|
|
||||||
|
int _effectiveByteLength(String text) {
|
||||||
|
final effective = encoder != null ? encoder!(text) : text;
|
||||||
|
return utf8.encode(effective).length;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TextEditingValue formatEditUpdate(
|
TextEditingValue formatEditUpdate(
|
||||||
@@ -13,8 +19,7 @@ class Utf8LengthLimitingTextInputFormatter extends TextInputFormatter {
|
|||||||
TextEditingValue newValue,
|
TextEditingValue newValue,
|
||||||
) {
|
) {
|
||||||
if (maxBytes <= 0) return oldValue;
|
if (maxBytes <= 0) return oldValue;
|
||||||
final bytes = utf8.encode(newValue.text);
|
if (_effectiveByteLength(newValue.text) <= maxBytes) return newValue;
|
||||||
if (bytes.length <= maxBytes) return newValue;
|
|
||||||
|
|
||||||
final truncated = _truncateToMaxBytes(newValue.text, maxBytes);
|
final truncated = _truncateToMaxBytes(newValue.text, maxBytes);
|
||||||
return TextEditingValue(
|
return TextEditingValue(
|
||||||
@@ -25,6 +30,14 @@ class Utf8LengthLimitingTextInputFormatter extends TextInputFormatter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _truncateToMaxBytes(String text, int limit) {
|
String _truncateToMaxBytes(String text, int limit) {
|
||||||
|
if (encoder != null) {
|
||||||
|
final runes = text.runes.toList();
|
||||||
|
while (runes.isNotEmpty &&
|
||||||
|
_effectiveByteLength(String.fromCharCodes(runes)) > maxBytes) {
|
||||||
|
runes.removeLast();
|
||||||
|
}
|
||||||
|
return String.fromCharCodes(runes);
|
||||||
|
}
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
var used = 0;
|
var used = 0;
|
||||||
for (final rune in text.runes) {
|
for (final rune in text.runes) {
|
||||||
|
|||||||
+484
-28
@@ -52,7 +52,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scanner_title": "MeshCore Open",
|
"scanner_title": "MeshCore – Отворена версия",
|
||||||
"scanner_scanning": "Сканиране за устройства...",
|
"scanner_scanning": "Сканиране за устройства...",
|
||||||
"scanner_connecting": "Свързвам се...",
|
"scanner_connecting": "Свързвам се...",
|
||||||
"scanner_disconnecting": "Изключване...",
|
"scanner_disconnecting": "Изключване...",
|
||||||
@@ -104,6 +104,8 @@
|
|||||||
"settings_privacyModeEnabled": "Режим на поверителност е активиран",
|
"settings_privacyModeEnabled": "Режим на поверителност е активиран",
|
||||||
"settings_privacyModeDisabled": "Режим на поверителност е деактивиран",
|
"settings_privacyModeDisabled": "Режим на поверителност е деактивиран",
|
||||||
"settings_actions": "Действия",
|
"settings_actions": "Действия",
|
||||||
|
"settings_deleteAllPaths": "Delete All Paths",
|
||||||
|
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
|
||||||
"settings_sendAdvertisement": "Изпрати Реклама",
|
"settings_sendAdvertisement": "Изпрати Реклама",
|
||||||
"settings_sendAdvertisementSubtitle": "Сега присъствие в ефир",
|
"settings_sendAdvertisementSubtitle": "Сега присъствие в ефир",
|
||||||
"settings_advertisementSent": "Реклама изпратена",
|
"settings_advertisementSent": "Реклама изпратена",
|
||||||
@@ -121,7 +123,7 @@
|
|||||||
"settings_appDebugLog": "Лог на отстраняване на грешки на приложението",
|
"settings_appDebugLog": "Лог на отстраняване на грешки на приложението",
|
||||||
"settings_appDebugLogSubtitle": "Съобщения за отстраняване на грешки на приложението",
|
"settings_appDebugLogSubtitle": "Съобщения за отстраняване на грешки на приложението",
|
||||||
"settings_about": "За нас",
|
"settings_about": "За нас",
|
||||||
"settings_aboutVersion": "MeshCore Open v{version}",
|
"settings_aboutVersion": "MeshCore Open, версия {version}",
|
||||||
"@settings_aboutVersion": {
|
"@settings_aboutVersion": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"version": {
|
"version": {
|
||||||
@@ -140,7 +142,7 @@
|
|||||||
"settings_infoChannelCount": "Брой канали",
|
"settings_infoChannelCount": "Брой канали",
|
||||||
"settings_presets": "Предварителни настройки",
|
"settings_presets": "Предварителни настройки",
|
||||||
"settings_frequency": "Честота (MHz)",
|
"settings_frequency": "Честота (MHz)",
|
||||||
"settings_frequencyHelper": "300.0 - 2500.0",
|
"settings_frequencyHelper": "300,0 – 2500,0",
|
||||||
"settings_frequencyInvalid": "Невалидна честота (300-2500 MHz)",
|
"settings_frequencyInvalid": "Невалидна честота (300-2500 MHz)",
|
||||||
"settings_bandwidth": "Ширина на честотния спектър",
|
"settings_bandwidth": "Ширина на честотния спектър",
|
||||||
"settings_spreadingFactor": "Фактор на разпространение",
|
"settings_spreadingFactor": "Фактор на разпространение",
|
||||||
@@ -164,18 +166,18 @@
|
|||||||
"appSettings_themeDark": "Тъмно",
|
"appSettings_themeDark": "Тъмно",
|
||||||
"appSettings_language": "Език",
|
"appSettings_language": "Език",
|
||||||
"appSettings_languageSystem": "Система по подразбиране",
|
"appSettings_languageSystem": "Система по подразбиране",
|
||||||
"appSettings_languageEn": "English",
|
"appSettings_languageEn": "Английски",
|
||||||
"appSettings_languageFr": "Français",
|
"appSettings_languageFr": "Френски",
|
||||||
"appSettings_languageEs": "Español",
|
"appSettings_languageEs": "Испански",
|
||||||
"appSettings_languageDe": "Deutsch",
|
"appSettings_languageDe": "Немски",
|
||||||
"appSettings_languagePl": "Polski",
|
"appSettings_languagePl": "Полски",
|
||||||
"appSettings_languageSl": "Slovenščina",
|
"appSettings_languageSl": "Словенски език",
|
||||||
"appSettings_languagePt": "Português",
|
"appSettings_languagePt": "Португалски",
|
||||||
"appSettings_languageIt": "Italiano",
|
"appSettings_languageIt": "Италиански",
|
||||||
"appSettings_languageZh": "中文",
|
"appSettings_languageZh": "Китайски",
|
||||||
"appSettings_languageSv": "Svenska",
|
"appSettings_languageSv": "Шведски",
|
||||||
"appSettings_languageNl": "Nederlands",
|
"appSettings_languageNl": "Хололандски",
|
||||||
"appSettings_languageSk": "Slovenčina",
|
"appSettings_languageSk": "Словенски",
|
||||||
"appSettings_languageBg": "Български",
|
"appSettings_languageBg": "Български",
|
||||||
"appSettings_notifications": "Уведомления",
|
"appSettings_notifications": "Уведомления",
|
||||||
"appSettings_enableNotifications": "Активирай Известия",
|
"appSettings_enableNotifications": "Активирай Известия",
|
||||||
@@ -285,6 +287,7 @@
|
|||||||
"contacts_newGroup": "Нова група",
|
"contacts_newGroup": "Нова група",
|
||||||
"contacts_groupName": "Група",
|
"contacts_groupName": "Група",
|
||||||
"contacts_groupNameRequired": "Името на групата е задължително.",
|
"contacts_groupNameRequired": "Името на групата е задължително.",
|
||||||
|
"contacts_groupNameReserved": "Това име на група е запазено",
|
||||||
"contacts_groupAlreadyExists": "Групата \"{name}\" вече съществува.",
|
"contacts_groupAlreadyExists": "Групата \"{name}\" вече съществува.",
|
||||||
"@contacts_groupAlreadyExists": {
|
"@contacts_groupAlreadyExists": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -336,11 +339,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_hashtagChannel": "Канал с хаштаг",
|
|
||||||
"channels_public": "Публично",
|
"channels_public": "Публично",
|
||||||
"channels_private": "Личен",
|
"channels_private": "Личен",
|
||||||
"channels_publicChannel": "Публичен канал",
|
|
||||||
"channels_privateChannel": "Частен канал",
|
|
||||||
"channels_editChannel": "Редактирай канал",
|
"channels_editChannel": "Редактирай канал",
|
||||||
"channels_muteChannel": "Заглуши канала",
|
"channels_muteChannel": "Заглуши канала",
|
||||||
"channels_unmuteChannel": "Включи известията на канала",
|
"channels_unmuteChannel": "Включи известията на канала",
|
||||||
@@ -366,7 +366,7 @@
|
|||||||
"channels_channelName": "Име на канала",
|
"channels_channelName": "Име на канала",
|
||||||
"channels_usePublicChannel": "Използвайте публичен канал",
|
"channels_usePublicChannel": "Използвайте публичен канал",
|
||||||
"channels_standardPublicPsk": "Стандартен публичен PSK",
|
"channels_standardPublicPsk": "Стандартен публичен PSK",
|
||||||
"channels_pskHex": "PSK (Hex)",
|
"channels_pskHex": "PSK (шестнадесетичен код)",
|
||||||
"channels_generateRandomPsk": "Генерирай случайна PSK",
|
"channels_generateRandomPsk": "Генерирай случайна PSK",
|
||||||
"channels_enterChannelName": "Моля, въведете име на канал.",
|
"channels_enterChannelName": "Моля, въведете име на канал.",
|
||||||
"channels_pskMustBe32Hex": "PSK трябва да бъде 32 шестнаредни знака.",
|
"channels_pskMustBe32Hex": "PSK трябва да бъде 32 шестнаредни знака.",
|
||||||
@@ -387,6 +387,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_smazCompression": "Компресия SMAZ",
|
"channels_smazCompression": "Компресия SMAZ",
|
||||||
|
"channels_cyr2latCompression": "Компресия Cyr2Lat",
|
||||||
|
"channels_cyr2latCompressionDscr": "Заменя някои кирилични символи с латиница при изпращане.",
|
||||||
|
"channels_cyr2latSettingsHeading": "Настройки на Cyr2Lat",
|
||||||
|
"channels_cyr2latSettingsSubheading": "Списък със замествания",
|
||||||
|
"channels_cyr2latSettingsDscr": "Редактиране на JSON конфигурацията за заместване на символи",
|
||||||
|
"channels_cyr2latSettingsDialogHint": "JSON карта за замествания",
|
||||||
|
"channels_cyr2latSettingsDialogWrongJSON": "Неправилен JSON: {error}",
|
||||||
|
"settings_cyr2latProfileAdd": "Добавяне на профил Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileName": "Име на профила",
|
||||||
|
"settings_cyr2latProfileNameEmpty": "Името на профила не може да бъде празно",
|
||||||
|
"settings_cyr2latProfileAdded": "Профилът е добавен успешно",
|
||||||
|
"settings_cyr2latProfileUpdated": "Профилът е актуализиран успешно",
|
||||||
|
"settings_cyr2latProfileEdit": "Редактиране на Cyr2Lat профил",
|
||||||
|
"settings_cyr2latProfileDelete": "Изтриване на профил Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileDeleted": "Профилът беше изтрит успешно",
|
||||||
|
"settings_cyr2latProfileDeleteDscr": "Сигурен ли сте, че искате да изтриете профила \"{name}\"?",
|
||||||
"channels_channelUpdated": "Каналът \"{name}\" е актуализиран",
|
"channels_channelUpdated": "Каналът \"{name}\" е актуализиран",
|
||||||
"@channels_channelUpdated": {
|
"@channels_channelUpdated": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -398,7 +414,7 @@
|
|||||||
"channels_publicChannelAdded": "Публичен канал добавен",
|
"channels_publicChannelAdded": "Публичен канал добавен",
|
||||||
"channels_sortBy": "Сортирай по",
|
"channels_sortBy": "Сортирай по",
|
||||||
"channels_sortManual": "Ръчно",
|
"channels_sortManual": "Ръчно",
|
||||||
"channels_sortAZ": "A-Z",
|
"channels_sortAZ": "От A до Я",
|
||||||
"channels_sortLatestMessages": "Последни съобщения",
|
"channels_sortLatestMessages": "Последни съобщения",
|
||||||
"channels_sortUnread": "Непрочетено",
|
"channels_sortUnread": "Непрочетено",
|
||||||
"chat_noMessages": "Няма съобщения.",
|
"chat_noMessages": "Няма съобщения.",
|
||||||
@@ -476,7 +492,7 @@
|
|||||||
"debugLog_noEntries": "Все още няма дебъг логове.",
|
"debugLog_noEntries": "Все още няма дебъг логове.",
|
||||||
"debugLog_enableInSettings": "Активирайте отстраняване на грешки в настройките на приложението",
|
"debugLog_enableInSettings": "Активирайте отстраняване на грешки в настройките на приложението",
|
||||||
"debugLog_frames": "Рамки",
|
"debugLog_frames": "Рамки",
|
||||||
"debugLog_rawLogRx": "Raw Log-RX",
|
"debugLog_rawLogRx": "Необработен лог-RX",
|
||||||
"debugLog_noBleActivity": "Няма BLE активност към момента.",
|
"debugLog_noBleActivity": "Няма BLE активност към момента.",
|
||||||
"debugFrame_length": "Дължина на кадъра: {count} байта",
|
"debugFrame_length": "Дължина на кадъра: {count} байта",
|
||||||
"@debugFrame_length": {
|
"@debugFrame_length": {
|
||||||
@@ -530,7 +546,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debugFrame_textTypeCli": "CLI",
|
"debugFrame_textTypeCli": "Команден ред (CLI)",
|
||||||
"debugFrame_textTypePlain": "Просто",
|
"debugFrame_textTypePlain": "Просто",
|
||||||
"debugFrame_text": "- Текст: \"{text}\"",
|
"debugFrame_text": "- Текст: \"{text}\"",
|
||||||
"@debugFrame_text": {
|
"@debugFrame_text": {
|
||||||
@@ -549,7 +565,7 @@
|
|||||||
"chat_pathHistoryFull": "Историята на пътя е пълна. Премахнете записи, за да добавите нови.",
|
"chat_pathHistoryFull": "Историята на пътя е пълна. Премахнете записи, за да добавите нови.",
|
||||||
"chat_hopSingular": "скочи",
|
"chat_hopSingular": "скочи",
|
||||||
"chat_hopPlural": "скоци",
|
"chat_hopPlural": "скоци",
|
||||||
"chat_hopsCount": "{count} {count, plural, =1{hop} other{hops}}",
|
"chat_hopsCount": "{count} {count, plural, =1{скача} other{скача}}",
|
||||||
"@chat_hopsCount": {
|
"@chat_hopsCount": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {
|
"count": {
|
||||||
@@ -882,7 +898,7 @@
|
|||||||
"repeater_statusSubtitle": "Прегледайте статуса, статистиката и съседните устройства.",
|
"repeater_statusSubtitle": "Прегледайте статуса, статистиката и съседните устройства.",
|
||||||
"repeater_telemetry": "Телеметрия",
|
"repeater_telemetry": "Телеметрия",
|
||||||
"repeater_telemetrySubtitle": "Прегледайте телеметрията на сензорите и системните статистики",
|
"repeater_telemetrySubtitle": "Прегледайте телеметрията на сензорите и системните статистики",
|
||||||
"repeater_cli": "CLI",
|
"repeater_cli": "Команден ред (CLI)",
|
||||||
"repeater_cliSubtitle": "Изпрати команди към ретранслатора",
|
"repeater_cliSubtitle": "Изпрати команди към ретранслатора",
|
||||||
"repeater_settings": "Настройки",
|
"repeater_settings": "Настройки",
|
||||||
"repeater_settingsSubtitle": "Конфигурирайте параметрите на репитера",
|
"repeater_settingsSubtitle": "Конфигурирайте параметрите на репитера",
|
||||||
@@ -1058,6 +1074,81 @@
|
|||||||
},
|
},
|
||||||
"repeater_confirm": "БеПотвърди",
|
"repeater_confirm": "БеПотвърди",
|
||||||
"repeater_settingsSaved": "Настройките са запазени успешно.",
|
"repeater_settingsSaved": "Настройките са запазени успешно.",
|
||||||
|
"repeater_rxGain": "Увеличен коефициент на възвръщаемост (RX)",
|
||||||
|
"repeater_rxGainHelper": "По-висока чувствителност, по-голям ток (само за SX1262/SX1268)",
|
||||||
|
"repeater_refreshRxGain": "Възстановете повишената ефективност на RX",
|
||||||
|
"repeater_multiAcks": "Множество потвърждения",
|
||||||
|
"repeater_multiAcksSubtitle": "Признавайте съобщения по множество канали за по-добро доставяне.",
|
||||||
|
"repeater_refreshMultiAcks": "Обновете множество потвърждения",
|
||||||
|
"repeater_networkHealth": "Състояние на мрежата",
|
||||||
|
"repeater_loopDetect": "Откриване на цикли",
|
||||||
|
"repeater_loopDetectHelper": "Изпратете пакети, които изглеждат като цикли в маршрутизацията.",
|
||||||
|
"repeater_loopDetectOff": "Изключено",
|
||||||
|
"repeater_loopDetectMinimal": "Минимален",
|
||||||
|
"repeater_loopDetectModerate": "Умерен",
|
||||||
|
"repeater_loopDetectStrict": "Строг",
|
||||||
|
"repeater_dutyCycle": "Цикъл на работа/почивка",
|
||||||
|
"repeater_dutyCycleHelper": "Максимален процент на използване на времето на въздуха",
|
||||||
|
"repeater_dutyCyclePercent": "{percent}%",
|
||||||
|
"@repeater_dutyCyclePercent": {
|
||||||
|
"placeholders": {
|
||||||
|
"percent": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_ownerInfo": "Информация за оператора",
|
||||||
|
"repeater_ownerInfoHelper": "Публични метаданни за този репитер",
|
||||||
|
"repeater_refreshOwnerInfo": "Обновете информацията за оператора",
|
||||||
|
"repeater_floodMax": "Максимален брой скачания при наводнение",
|
||||||
|
"repeater_floodMaxHelper": "Максималният брой пакети, които един поток може да пренесе (0-64)",
|
||||||
|
"repeater_advancedSettings": "Напреднал",
|
||||||
|
"repeater_advancedSettingsSubtitle": "Регулаторни копчета за опитни оператори",
|
||||||
|
"repeater_pathHashMode": "Режим за хеширане на пътища",
|
||||||
|
"repeater_pathHashModeHelper": "Байтовете, използвани за кодиране на идентификатора на този репитер в таговете за откриване на потоци/цикли, са: 0=1 байт (256 идентификатора, до 64 скача), 1=2 байта (65 000 идентификатора, до 32 скача), 2=3 байта (16 милиона идентификатора, до 21 скача). Версии 1.13 и по-стари версии на фърмуера използват многобайтови пътища – само след като мрежата е актуализирана до версия 1.14 или по-нова.",
|
||||||
|
"repeater_txDelay": "Забавяне на проекта \"Flood TX\"",
|
||||||
|
"repeater_txDelayHelper": "Предавайте разстоянието между пакетите за трафик при наводнения, като множител на времето за пренос на пакета (0-2, по подразбиране 0.5). По-висока стойност означава по-малко сблъсъци, но по-бавно предаване.",
|
||||||
|
"repeater_directTxDelay": "Директно забавяне на сигнала",
|
||||||
|
"repeater_directTxDelayHelper": "Предаване на интервали за директен (не-масивен) трафик, като множител на времето за пренос на пакета (0-2, по подразбиране 0.3).",
|
||||||
|
"repeater_intThresh": "Праг на интерференция",
|
||||||
|
"repeater_intThreshHelper": "Прагът е зададен на нивото на шума на радиото, така че да отхвърля смущения, които са над този праг. 0 – изключва; активирайте само, ако забележите грешки в шумна честотна лента.",
|
||||||
|
"repeater_agcResetInterval": "Интервал за рестартиране на AGC",
|
||||||
|
"repeater_agcResetIntervalHelper": "Колко често да се рестартира автоматичната настройка на усилването, за да се възстанови от състояние, в което усилването е блокирано. Времето за рестартиране е няколко секунди, като се определя като кратна на 4. 0 деактивира периодичното рестартиране.",
|
||||||
|
"repeater_actionsTitle": "Действия",
|
||||||
|
"repeater_sendAdvert": "Изпратете реклама за навод",
|
||||||
|
"repeater_sendAdvertSubtitle": "Публикувайте реклама за навод в мрежата.",
|
||||||
|
"repeater_sendAdvertZeroHop": "Изпратете реклама без преминаване през други системи",
|
||||||
|
"repeater_sendAdvertZeroHopSubtitle": "Публикувайте реклама, която достига до целевата аудитория само чрез директно разпространение (без използване на посредници).",
|
||||||
|
"repeater_clockSync": "Синхронизиране на часовника сега",
|
||||||
|
"repeater_clockSyncSubtitle": "Настройте времето на телефона си да съвпада с времето на репитера.",
|
||||||
|
"repeater_actionSucceeded": "{action} succeeded",
|
||||||
|
"@repeater_actionSucceeded": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_actionFailed": "{action} failed: {error}",
|
||||||
|
"@repeater_actionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_settingsSavedRebootNeeded": "Настройки запаметени – рестартирайте ретранслатора, за да ги приложите.",
|
||||||
|
"repeater_settingsPartialFailure": "Някои настройки не успяха: {failures}",
|
||||||
|
"@repeater_settingsPartialFailure": {
|
||||||
|
"placeholders": {
|
||||||
|
"failures": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repeater_errorSavingSettings": "Грешка при запазване на настройките: {error}",
|
"repeater_errorSavingSettings": "Грешка при запазване на настройките: {error}",
|
||||||
"@repeater_errorSavingSettings": {
|
"@repeater_errorSavingSettings": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1069,11 +1160,9 @@
|
|||||||
"repeater_refreshBasicSettings": "Обнови Основни Настройки",
|
"repeater_refreshBasicSettings": "Обнови Основни Настройки",
|
||||||
"repeater_refreshRadioSettings": "Обнови настройките на радиопредавателите",
|
"repeater_refreshRadioSettings": "Обнови настройките на радиопредавателите",
|
||||||
"repeater_refreshTxPower": "Обнови TX захранване",
|
"repeater_refreshTxPower": "Обнови TX захранване",
|
||||||
"repeater_refreshLocationSettings": "Обнови настройките на местоположението",
|
|
||||||
"repeater_refreshPacketForwarding": "Обнови пакетно пренасочване",
|
"repeater_refreshPacketForwarding": "Обнови пакетно пренасочване",
|
||||||
"repeater_refreshGuestAccess": "Обнови достъп за гости",
|
"repeater_refreshGuestAccess": "Обнови достъп за гости",
|
||||||
"repeater_refreshPrivacyMode": "Обнови Режим на поверителност",
|
"repeater_refreshPrivacyMode": "Обнови Режим на поверителност",
|
||||||
"repeater_refreshAdvertisementSettings": "Обнови Настройки на Рекламата",
|
|
||||||
"repeater_refreshed": "{label} е обновено",
|
"repeater_refreshed": "{label} е обновено",
|
||||||
"@repeater_refreshed": {
|
"@repeater_refreshed": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1346,7 +1435,7 @@
|
|||||||
"listFilter_sortBy": "Сортирай по",
|
"listFilter_sortBy": "Сортирай по",
|
||||||
"listFilter_latestMessages": "Последни съобщения",
|
"listFilter_latestMessages": "Последни съобщения",
|
||||||
"listFilter_heardRecently": "Слушано е наскоро",
|
"listFilter_heardRecently": "Слушано е наскоро",
|
||||||
"listFilter_az": "A-Z",
|
"listFilter_az": "А-Я",
|
||||||
"listFilter_filters": "Филтри",
|
"listFilter_filters": "Филтри",
|
||||||
"listFilter_all": "Всички",
|
"listFilter_all": "Всички",
|
||||||
"listFilter_users": "Потребители",
|
"listFilter_users": "Потребители",
|
||||||
@@ -1859,5 +1948,372 @@
|
|||||||
"usbConnectionFailed": "Неуспешно свързване през USB: {error}",
|
"usbConnectionFailed": "Неуспешно свързване през USB: {error}",
|
||||||
"usbStatus_notConnected": "Изберете USB устройство",
|
"usbStatus_notConnected": "Изберете USB устройство",
|
||||||
"usbStatus_searching": "Търсене на USB устройства...",
|
"usbStatus_searching": "Търсене на USB устройства...",
|
||||||
"usbErrorConnectTimedOut": "Връзката прекъсна. Уверете се, че устройството има софтуер за USB връзка."
|
"usbErrorConnectTimedOut": "Връзката прекъсна. Уверете се, че устройството има софтуер за USB връзка.",
|
||||||
|
"@tcpStatus_connectingTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"endpoint": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tcpConnectionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tcpHostHint": "192.168.40.10",
|
||||||
|
"tcpScreenTitle": "Свържете се чрез TCP",
|
||||||
|
"connectionChoiceTcpLabel": "TCP",
|
||||||
|
"tcpHostLabel": "IP адрес",
|
||||||
|
"tcpPortLabel": "Пристанище",
|
||||||
|
"tcpPortHint": "5000",
|
||||||
|
"tcpStatus_notConnected": "Въведете крайната точка и свържете се.",
|
||||||
|
"tcpStatus_connectingTo": "Свързване към {endpoint}...",
|
||||||
|
"tcpErrorHostRequired": "Необходим е IP адрес.",
|
||||||
|
"tcpErrorPortInvalid": "Портът трябва да бъде между 1 и 65535.",
|
||||||
|
"tcpErrorUnsupported": "Транспортът чрез TCP не се поддържа на тази платформа.",
|
||||||
|
"tcpErrorTimedOut": "Връзката TCP изтекла.",
|
||||||
|
"tcpConnectionFailed": "Неуспешно е установено TCP връзката: {error}",
|
||||||
|
"map_showDiscoveryContacts": "Покажи контакти за откриване",
|
||||||
|
"map_setAsMyLocation": "Задайте като моя местоположение",
|
||||||
|
"@path_routeWeight": {
|
||||||
|
"placeholders": {
|
||||||
|
"weight": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings_denyAll": "Откажи всичко",
|
||||||
|
"settings_allowAll": "Позволи всичко",
|
||||||
|
"settings_allowByContact": "Позволи по флагове за контакт",
|
||||||
|
"settings_privacy": "Настройки на поверителността",
|
||||||
|
"settings_privacySettingsDescription": "Изберете каква информация устройството ви споделя с другите.",
|
||||||
|
"settings_privacySubtitle": "Контролирайте каква информация се споделя.",
|
||||||
|
"settings_telemetryBaseMode": "Базов режим на телеметрия",
|
||||||
|
"settings_telemetryLocationMode": "Режим на местоположение на телеметрията",
|
||||||
|
"settings_advertLocation": "Място на обявата",
|
||||||
|
"settings_advertLocationSubtitle": "Включи местоположение в обявата",
|
||||||
|
"contact_info": "Контактна информация",
|
||||||
|
"settings_telemetryEnvironmentMode": "Режим на средата на телеметрията",
|
||||||
|
"contact_telemetry": "Телеметрия",
|
||||||
|
"contact_lastSeen": "Последно видян",
|
||||||
|
"contact_clearChat": "Изчисти чата",
|
||||||
|
"contact_teleBase": "Базата данни за телеметрия",
|
||||||
|
"contact_settings": "Настройки за контакти",
|
||||||
|
"contact_teleBaseSubtitle": "Позволи споделяне на ниво на батерията и основна телеметрия",
|
||||||
|
"contact_teleEnv": "Среда на телеметрия",
|
||||||
|
"contact_teleLocSubtitle": "Позволи споделяне на данни за местоположение",
|
||||||
|
"contact_teleLoc": "Местоположение на телеметрията",
|
||||||
|
"contact_teleEnvSubtitle": "Позволи споделяне на данни от средносферните датчици",
|
||||||
|
"appSettings_initialRouteWeight": "Първоначална тежест на маршрута",
|
||||||
|
"appSettings_maxRouteWeight": "Максимално допустимо тегло на маршрута",
|
||||||
|
"appSettings_initialRouteWeightSubtitle": "Начално тегло за новооткрити маршрути",
|
||||||
|
"appSettings_maxRouteWeightSubtitle": "Максималното тегло, което един маршрут може да събере от успешни доставки.",
|
||||||
|
"appSettings_routeWeightSuccessIncrement": "Увеличение на теглото за успех",
|
||||||
|
"appSettings_routeWeightSuccessIncrementSubtitle": "Тегло, добавено към път след успешно доставяне.",
|
||||||
|
"appSettings_routeWeightFailureDecrement": "Намаляване на теглото, свързано с неуспех",
|
||||||
|
"appSettings_routeWeightFailureDecrementSubtitle": "Тегло, което е било премахнато от пътя след неуспешен опит за доставка.",
|
||||||
|
"appSettings_maxMessageRetries": "Максимален брой опити за изпращане на съобщение",
|
||||||
|
"appSettings_maxMessageRetriesSubtitle": "Брой опити за повторно изпращане, преди съобщението да бъде маркирано като неуспешно.",
|
||||||
|
"path_routeWeight": "{weight}/{max}",
|
||||||
|
"settings_telemetryModeUpdated": "Режим на телеметрията е обновен",
|
||||||
|
"map_showOverlaps": "Покриване на ключа на повтаряча",
|
||||||
|
"map_runTraceWithReturnPath": "Върни се по същия път.",
|
||||||
|
"@radioStats_noiseFloor": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastRssi": {
|
||||||
|
"placeholders": {
|
||||||
|
"rssiDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastSnr": {
|
||||||
|
"placeholders": {
|
||||||
|
"snr": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_txAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_rxAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_stripNoise": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chat_sendCooldown": "Моля, изчакайте малко, преди да изпратите отново.",
|
||||||
|
"appSettings_languageHu": "Унгарски",
|
||||||
|
"appSettings_jumpToOldestUnread": "Преминете към най-старата непочетена статия",
|
||||||
|
"appSettings_jumpToOldestUnreadSubtitle": "Когато отворите чат с непрочетени съобщения, плъзнете надолу, за да видите първото непрочетено съобщение, вместо най-новото.",
|
||||||
|
"appSettings_languageJa": "Японски",
|
||||||
|
"appSettings_languageKo": "Корейски",
|
||||||
|
"radioStats_tooltip": "Статистика за радио и мрежа",
|
||||||
|
"radioStats_screenTitle": "Статистически данни за радиопредаванията",
|
||||||
|
"radioStats_notConnected": "Свържете се с устройство, за да видите статистически данни за радиопредаване.",
|
||||||
|
"radioStats_firmwareTooOld": "Статистиката на радиостанцията изисква съвместимо софтуерно решение версия 8 или по-нова.",
|
||||||
|
"radioStats_waiting": "Изчакване на данни…",
|
||||||
|
"radioStats_noiseFloor": "Ниво на шума: {noiseDbm} dBm",
|
||||||
|
"radioStats_lastRssi": "Последен RSSI: {rssiDbm} dBm",
|
||||||
|
"radioStats_lastSnr": "Последна стойност на SNR: {snr} dB",
|
||||||
|
"radioStats_txAir": "Време на въздух (общо): {seconds} секунди",
|
||||||
|
"radioStats_rxAir": "Общо време на използване на RX (в секунди): {seconds} с",
|
||||||
|
"radioStats_chartCaption": "Ниво на шума (dBm) за последните измервания.",
|
||||||
|
"radioStats_stripNoise": "Ниво на шума: {noiseDbm} dBm",
|
||||||
|
"radioStats_stripWaiting": "Извличане на данни за радиото…",
|
||||||
|
"radioStats_settingsTile": "Статистически данни за радиостанции",
|
||||||
|
"radioStats_settingsSubtitle": "Ниво на шума, RSSI, SNR и време на пренос",
|
||||||
|
"@translation_downloadFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_enableTitle": "Активирайте превода",
|
||||||
|
"translation_title": "Превод",
|
||||||
|
"translation_composerTitle": "Преведете преди да изпратите",
|
||||||
|
"translation_enableSubtitle": "Превеждайте входящите съобщения и позволявайте предварително превеждане преди изпращане.",
|
||||||
|
"translation_composerSubtitle": "Контролира началния статус на иконата за превод, създадена от композитора.",
|
||||||
|
"translation_targetLanguage": "Целеви език",
|
||||||
|
"translation_useAppLanguage": "Използвайте езика на приложението",
|
||||||
|
"translation_downloadedModelLabel": "Изтегнат модел",
|
||||||
|
"translation_presetModelLabel": "Предварително конфигуриран модел от Hugging Face",
|
||||||
|
"translation_manualUrlLabel": "URL на ръководството",
|
||||||
|
"translation_downloadModel": "Изтеглете модела",
|
||||||
|
"translation_downloading": "Изтегляне...",
|
||||||
|
"translation_working": "Работа...",
|
||||||
|
"translation_stop": "Спрете",
|
||||||
|
"translation_mergingChunks": "Съединяване на изтеглените части в един файл...",
|
||||||
|
"translation_downloadedModels": "Изтеглени модели",
|
||||||
|
"translation_deleteModel": "Изтриване на модела",
|
||||||
|
"translation_modelDownloaded": "Моделът за превод е изтеглен.",
|
||||||
|
"translation_downloadStopped": "Изтеглянето беше прекъснато.",
|
||||||
|
"translation_downloadFailed": "Не успях да изтегля: {error}",
|
||||||
|
"translation_enterUrlFirst": "Въведете първо URL адрес на модела.",
|
||||||
|
"@scanner_linuxPairingPinPrompt": {
|
||||||
|
"placeholders": {
|
||||||
|
"deviceName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@translation_translateTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"language": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_composerDisabledHint": "Изпращайте съобщения на оригиналния въведен език.",
|
||||||
|
"translation_translateBeforeSending": "Преведете преди да изпратите",
|
||||||
|
"translation_messageTranslation": "Превод на съобщението",
|
||||||
|
"translation_composerEnabledHint": "Съобщенията ще бъдат преведени, преди да бъдат изпратени.",
|
||||||
|
"translation_translateTo": "Превеждане на {language}",
|
||||||
|
"translation_translationOptions": "Опции за превод",
|
||||||
|
"translation_systemLanguage": "Език на системата",
|
||||||
|
"scanner_linuxPairingPinTitle": "PIN за съвпадение чрез Bluetooth",
|
||||||
|
"scanner_linuxPairingPinPrompt": "Въведете PIN кода за {deviceName} (оставете празно, ако няма такъв).",
|
||||||
|
"scanner_linuxPairingHidePin": "Скриване на PIN кода",
|
||||||
|
"scanner_linuxPairingShowPin": "Покажи PIN",
|
||||||
|
"repeater_cliQuickClockSync": "Синхронизация на часовника",
|
||||||
|
"repeater_cliQuickDiscovery": "Открий Съседи",
|
||||||
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Автоматично изпращайте съобщение \"синхронизиране на часовника\" след успешно влизане.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Синхронизиране на часовника след влизане",
|
||||||
|
"chat_sendMessage": "Изпратете съобщение",
|
||||||
|
"room_guest": "Информация за сървъра на стаята",
|
||||||
|
"repeater_guest": "Информация за ретранслаторите",
|
||||||
|
"repeater_guestTools": "Инструменти за гости",
|
||||||
|
"repeater_getCategory": "Получете стойности",
|
||||||
|
"repeater_powerMgmt": "Управление на енергията",
|
||||||
|
"repeater_sensors": "Датчици",
|
||||||
|
"repeater_cliHelpPowerOff": "Изключва устройството. (не се очаква отговор)",
|
||||||
|
"repeater_cliHelpClkReboot": "Възстановява часовника до известна историческа дата и рестартира устройството.",
|
||||||
|
"repeater_cliHelpAdvertZeroHop": "Изпраща реклама, която достига само до съседни устройства (само до съседни мрежи).",
|
||||||
|
"repeater_cliHelpStartOta": "Стартира актуализация на фърмуера чрез въздушното, на всички поддържани платки.",
|
||||||
|
"repeater_cliHelpTime": "Задава времето на устройството към зададените секунди от началото на Unix ерата. Времето не може да се върне назад.",
|
||||||
|
"repeater_cliHelpBoard": "Показва производителя на платката / идентификатора на хардуера.",
|
||||||
|
"repeater_cliHelpDiscoverNeighbors": "Изпраща заявка за откриване на съседни възли. (Само за устройства тип репитер)",
|
||||||
|
"repeater_cliHelpPowersaving": "Показва дали режимът за пестене на енергия е активиран или деактивиран.",
|
||||||
|
"repeater_cliHelpPowersavingOnOff": "Активира или деактивира режима за пестене на енергия (ако е поддържан).",
|
||||||
|
"repeater_cliHelpErase": "(Само за серийни устройства) Форматира файловата система на устройството. Изтрива всички настройки и контакти.",
|
||||||
|
"repeater_cliHelpSetDutyCycle": "Задава максимално допустимия процент на използване на времето за предаване (от 1 до 100 процента). Вътрешно коригира фактора за времето на предаване.",
|
||||||
|
"repeater_cliHelpSetPrvKey": "(Само за серийни номера) Заменя личната част от ключа за идентификация на устройството. Необходимо е да се рестартира устройството, за да се приложи. Генерира нов публичен ключ.",
|
||||||
|
"repeater_cliHelpSetRadioRxGain": "(Само за SX126x) Превключва усиления на приемния сигнал (RX gain) за подобрена чувствителност при по-високо потребление на ток.",
|
||||||
|
"repeater_cliHelpSetOwnerInfo": "Задава низовете с информация за контакт на собственика, които са включени в рекламите. Използвайте '|' за нови редове.",
|
||||||
|
"repeater_cliHelpSetPathHashMode": "Задава режима за хеширане на пътищата. 0 = за стари системи, 1 = за стандартни системи, 2 = за строги системи. Влияе върху начина, по който се съпоставят маршрутите.",
|
||||||
|
"repeater_cliHelpSetLoopDetect": "Задава чувствителността за откриване на цикли в маршрутизацията: изключена, минимална, умерена или строга.",
|
||||||
|
"repeater_cliHelpSetFreq": "(Само за серийно управление) Бързо задава само честотата. Необходимо е рестартиране. Препоръчително е да се използват настройките за \"радио\", за да се зададат всички параметри.",
|
||||||
|
"repeater_cliHelpSetBridgeChannel": "(Само за моста ESPNow) Определя WiFi канала (от 1 до 14), използван от моста.",
|
||||||
|
"repeater_cliHelpGetName": "Показва зададеното име на възела.",
|
||||||
|
"repeater_cliHelpGetRole": "Показва ролята на фърмуера (например, репитер, сървър за стая и т.н.).",
|
||||||
|
"repeater_cliHelpGetPublicKey": "Показва публичния ключ на устройството.",
|
||||||
|
"repeater_cliHelpGetPrvKey": "(Само за серийния номер) Показва личната ключа на устройството. Трябва да се третира като тайна.",
|
||||||
|
"repeater_cliHelpGetRepeat": "Показва дали функцията за пренасочване на пакети (ролята на репитер) е активирана или деактивирана.",
|
||||||
|
"repeater_cliHelpGetTx": "Показва текущата мощност на TX в dBm.",
|
||||||
|
"repeater_cliHelpGetFreq": "Показва зададената честота в MHz.",
|
||||||
|
"repeater_cliHelpGetRadio": "Показва пълните радио параметри: честота, ширина на честотния обхват, фактор на разпространение, скорост на кодиране.",
|
||||||
|
"repeater_cliHelpGetRadioRxGain": "(Само за SX126x) Показва състоянието на усиления сигнал на RX.",
|
||||||
|
"repeater_cliHelpGetAf": "Показва текущия коефициент на въздействие върху въздуха.",
|
||||||
|
"repeater_cliHelpGetDutyCycle": "Показва текущия допустим цикъл на работа като процент.",
|
||||||
|
"repeater_cliHelpGetIntThresh": "Показва прага на интерференцията на канала в децибели (dB).",
|
||||||
|
"repeater_cliHelpGetAgcResetInterval": "Показва интервала за рестартиране на AGC в секунди.",
|
||||||
|
"repeater_cliHelpGetMultiAcks": "Показва дали режимът \"двоен ACK\" е активиран (1) или деактивиран (0).",
|
||||||
|
"repeater_cliHelpGetAllowReadOnly": "Показва дали е разрешено само четене за гостите.",
|
||||||
|
"repeater_cliHelpGetAdvertInterval": "Показва времето на рекламата в минути.",
|
||||||
|
"repeater_cliHelpGetFloodAdvertInterval": "Показва интервала на рекламата за навод в часове.",
|
||||||
|
"repeater_cliHelpGetGuestPassword": "Показва зададения парол за гост.",
|
||||||
|
"repeater_cliHelpGetLat": "Показва зададената географска ширина.",
|
||||||
|
"repeater_cliHelpGetLon": "Показва зададената дължина.",
|
||||||
|
"repeater_cliHelpGetRxDelay": "Показва основната стойност на забавянето на сигнала.",
|
||||||
|
"repeater_cliHelpGetTxDelay": "Показва коефициента за забавяне при режим на наводняване.",
|
||||||
|
"repeater_cliHelpGetDirectTxDelay": "Показва коефициента за забавяне при директен режим.",
|
||||||
|
"repeater_cliHelpGetFloodMax": "Показва максималния брой на повторни наводнения.",
|
||||||
|
"repeater_cliHelpGetOwnerInfo": "Показва информацията за контакт на собственика.",
|
||||||
|
"repeater_cliHelpGetPathHashMode": "Показва режима на хеширане на пътя (0/1/2).",
|
||||||
|
"repeater_cliHelpGetLoopDetect": "Показва чувствителността към откриване на цикли.",
|
||||||
|
"repeater_cliHelpGetAcl": "(Само за серийни номера) Изброява настройките за контрол на достъпа в репитера.",
|
||||||
|
"repeater_cliHelpGetBridgeEnabled": "Показва дали мостът е активиран.",
|
||||||
|
"repeater_cliHelpGetBridgeDelay": "Показва забавянето на моста в милисекунди.",
|
||||||
|
"repeater_cliHelpGetBridgeSource": "Показва дали мостът изпраща или получава пакети RX или TX.",
|
||||||
|
"repeater_cliHelpGetBridgeBaud": "(Само за мост RS232) Показва скоростта на предаване на данните на моста.",
|
||||||
|
"repeater_cliHelpGetBridgeChannel": "(Само за моста ESPNow) Показва канала на WiFi на моста.",
|
||||||
|
"repeater_cliHelpGetBridgeSecret": "(Само за моста ESPNow) Показва споделения секрет на моста.",
|
||||||
|
"repeater_cliHelpGetBootloaderVer": "(Само за NRF52) Показва версията на зареждащия софтуер.",
|
||||||
|
"repeater_cliHelpGetAdcMultiplier": "Показва множителя на аналоговия-цифров преобразувател (мащабиране на напрежението от батерията).",
|
||||||
|
"repeater_cliHelpGetPwrMgtSupport": "Описва дали борда на директорите има поддръжка за управление на захранването.",
|
||||||
|
"repeater_cliHelpGetPwrMgtSource": "Показва текущия източник на захранване: външен или батерия.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootReason": "Показва най-скорошните причини за рестартиране и изключване.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootMv": "Показва напрежението на батерията при стартиране, измерено в миливолта (mV).",
|
||||||
|
"repeater_cliHelpSensorGet": "Чете персонализирана настройка на сензор чрез клавиш.",
|
||||||
|
"repeater_cliHelpSensorSet": "Създава персонализирана настройка за сензор.",
|
||||||
|
"repeater_cliHelpSensorList": "Показва всички настройки на потребителските сензори, разделени на страници, започвайки от опционален индекс.",
|
||||||
|
"repeater_cliHelpRegionDefault": "Показва текущия обхват на региона по подразбиране.",
|
||||||
|
"repeater_cliHelpRegionDefaultSet": "Задава обхвата на региона по подразбиране. Използвайте \"<null>\", за да го изчистите.",
|
||||||
|
"repeater_cliHelpRegionListAllowed": "Списва регионите, които позволяват преминаване на превозни средства при наводнение.",
|
||||||
|
"repeater_cliHelpRegionListDenied": "Списва региони, които забраняват движението по пътищата при наводнения.",
|
||||||
|
"repeater_cliHelpStatsPackets": "(Само за серия) Показва статистически данни на ниво пакет.",
|
||||||
|
"repeater_cliHelpStatsRadio": "(Само за конкретен сериал) Показва радиостатистика.",
|
||||||
|
"repeater_cliHelpStatsCore": "(Само за серийния номер) Показва основните статистически данни за фърмуера.",
|
||||||
|
"common_done": "Done",
|
||||||
|
"background_serviceTitle": "MeshCore running",
|
||||||
|
"background_serviceText": "Keeping BLE connected",
|
||||||
|
"appSettings_translationModelDeleted": "Deleted {name}",
|
||||||
|
"@appSettings_translationModelDeleted": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
|
||||||
|
"@appSettings_translationModelDeleteFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channels_channelUpdateFailed": "Failed to update channel: {error}",
|
||||||
|
"@channels_channelUpdateFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map_type": "Type",
|
||||||
|
"map_path": "Path",
|
||||||
|
"map_location": "Location",
|
||||||
|
"map_estLocation": "Est. Location",
|
||||||
|
"map_publicKey": "Public Key",
|
||||||
|
"map_publicKeyPrefixHint": "e.g. ab12",
|
||||||
|
"contact_typeChat": "Chat",
|
||||||
|
"contact_typeRepeater": "Repeater",
|
||||||
|
"contact_typeRoom": "Room",
|
||||||
|
"contact_typeSensor": "Sensor",
|
||||||
|
"contact_typeUnknown": "Unknown",
|
||||||
|
"channels_via": "via {path}",
|
||||||
|
"chat_score": "Score",
|
||||||
|
"settings_multiAck": "Множество потвърждения",
|
||||||
|
"map_sharedAt": "Споделено",
|
||||||
|
"@losBlockedSpotChip": {
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@losSelectedObstructionDetails": {
|
||||||
|
"placeholders": {
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromA": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromB": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"losSelectedObstructionTitle": "Избрано препятствие",
|
||||||
|
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
|
||||||
|
"losBlockedSpotsHint": "Кликнете върху блокираната точка, за да я отбележите на картата.",
|
||||||
|
"losBlockedSpotsTitle": "Ограничени места",
|
||||||
|
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).",
|
||||||
|
"chat_markAsUnread": "Отбелязване като непрочетено",
|
||||||
|
"settings_companionDebugLogSubtitle": "Команди, отговори и сурови данни за протоколите BLE/TCP/USB",
|
||||||
|
"chat_newMessages": "Нови съобщения",
|
||||||
|
"settings_companionDebugLog": "Лог за отстраняване на грешки (за съпътстваща програма)",
|
||||||
|
"repeater_chanUtil": "Използване на канала",
|
||||||
|
"dialog_connectCompanion": "Свържете се с придружител, за да получите достъп до функциите на ретранслатора и сървъра за стаи.",
|
||||||
|
"dialog_disconnectedTitle": "Прекъснато",
|
||||||
|
"dialog_disconnectedMessage": "Свързването ви с вашия спътник е прекъснато.",
|
||||||
|
"contact_connectCompanion": "Свържете се с спътник, за да получите достъп до функциите на repeater и room server."
|
||||||
}
|
}
|
||||||
|
|||||||
+496
-40
@@ -52,7 +52,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scanner_title": "MeshCore Open",
|
"scanner_title": "MeshCore – Open-Version",
|
||||||
"scanner_scanning": "Scannen nach Geräten...",
|
"scanner_scanning": "Scannen nach Geräten...",
|
||||||
"scanner_connecting": "Verbunden...",
|
"scanner_connecting": "Verbunden...",
|
||||||
"scanner_disconnecting": "Trenne...",
|
"scanner_disconnecting": "Trenne...",
|
||||||
@@ -104,6 +104,8 @@
|
|||||||
"settings_privacyModeEnabled": "Datenschutzmodus aktiviert",
|
"settings_privacyModeEnabled": "Datenschutzmodus aktiviert",
|
||||||
"settings_privacyModeDisabled": "Datenschutzmodus deaktiviert",
|
"settings_privacyModeDisabled": "Datenschutzmodus deaktiviert",
|
||||||
"settings_actions": "Aktionen",
|
"settings_actions": "Aktionen",
|
||||||
|
"settings_deleteAllPaths": "Delete All Paths",
|
||||||
|
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
|
||||||
"settings_sendAdvertisement": "Sende Ankündigung",
|
"settings_sendAdvertisement": "Sende Ankündigung",
|
||||||
"settings_sendAdvertisementSubtitle": "Sende eine Ankündigung",
|
"settings_sendAdvertisementSubtitle": "Sende eine Ankündigung",
|
||||||
"settings_advertisementSent": "Ankündigung gesendet",
|
"settings_advertisementSent": "Ankündigung gesendet",
|
||||||
@@ -121,7 +123,7 @@
|
|||||||
"settings_appDebugLog": "App-Debug-Protokoll",
|
"settings_appDebugLog": "App-Debug-Protokoll",
|
||||||
"settings_appDebugLogSubtitle": "Anwendung Debug-Nachrichten",
|
"settings_appDebugLogSubtitle": "Anwendung Debug-Nachrichten",
|
||||||
"settings_about": "Über",
|
"settings_about": "Über",
|
||||||
"settings_aboutVersion": "MeshCore Open v{version}",
|
"settings_aboutVersion": "MeshCore Open, Version {version}",
|
||||||
"@settings_aboutVersion": {
|
"@settings_aboutVersion": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"version": {
|
"version": {
|
||||||
@@ -146,7 +148,7 @@
|
|||||||
"settings_spreadingFactor": "Verteilungsfaktor",
|
"settings_spreadingFactor": "Verteilungsfaktor",
|
||||||
"settings_codingRate": "Kodierungsrate",
|
"settings_codingRate": "Kodierungsrate",
|
||||||
"settings_txPower": "TX-Leistung (dBm)",
|
"settings_txPower": "TX-Leistung (dBm)",
|
||||||
"settings_txPowerHelper": "0 - 22",
|
"settings_txPowerHelper": "0 – 22",
|
||||||
"settings_txPowerInvalid": "Ungültige TX-Leistung (0-22 dBm)",
|
"settings_txPowerInvalid": "Ungültige TX-Leistung (0-22 dBm)",
|
||||||
"settings_error": "Fehler: {message}",
|
"settings_error": "Fehler: {message}",
|
||||||
"@settings_error": {
|
"@settings_error": {
|
||||||
@@ -158,25 +160,25 @@
|
|||||||
},
|
},
|
||||||
"appSettings_title": "App-Einstellungen",
|
"appSettings_title": "App-Einstellungen",
|
||||||
"appSettings_appearance": "Aussehen",
|
"appSettings_appearance": "Aussehen",
|
||||||
"appSettings_theme": "Theme",
|
"appSettings_theme": "Thema",
|
||||||
"appSettings_themeSystem": "Systemstandard",
|
"appSettings_themeSystem": "Systemstandard",
|
||||||
"appSettings_themeLight": "Hell",
|
"appSettings_themeLight": "Hell",
|
||||||
"appSettings_themeDark": "Dunkel",
|
"appSettings_themeDark": "Dunkel",
|
||||||
"appSettings_language": "Sprache",
|
"appSettings_language": "Sprache",
|
||||||
"appSettings_languageSystem": "Systemstandard",
|
"appSettings_languageSystem": "Systemstandard",
|
||||||
"appSettings_languageEn": "English",
|
"appSettings_languageEn": "Englisch",
|
||||||
"appSettings_languageFr": "Français",
|
"appSettings_languageFr": "Französisch",
|
||||||
"appSettings_languageEs": "Español",
|
"appSettings_languageEs": "Spanisch",
|
||||||
"appSettings_languageDe": "Deutsch",
|
"appSettings_languageDe": "Deutsch",
|
||||||
"appSettings_languagePl": "Polski",
|
"appSettings_languagePl": "Polnisch",
|
||||||
"appSettings_languageSl": "Slovenščina",
|
"appSettings_languageSl": "Slowenisch",
|
||||||
"appSettings_languagePt": "Português",
|
"appSettings_languagePt": "Portugiesisch",
|
||||||
"appSettings_languageIt": "Italiano",
|
"appSettings_languageIt": "Italienisch",
|
||||||
"appSettings_languageZh": "中文",
|
"appSettings_languageZh": "Chinesisch",
|
||||||
"appSettings_languageSv": "Svenska",
|
"appSettings_languageSv": "Schwedisch",
|
||||||
"appSettings_languageNl": "Nederlands",
|
"appSettings_languageNl": "Niederländisch",
|
||||||
"appSettings_languageSk": "Slovenčina",
|
"appSettings_languageSk": "Slowenisch",
|
||||||
"appSettings_languageBg": "Български",
|
"appSettings_languageBg": "Bulgarisch",
|
||||||
"appSettings_notifications": "Benachrichtigungen",
|
"appSettings_notifications": "Benachrichtigungen",
|
||||||
"appSettings_enableNotifications": "Benachrichtigungen aktivieren",
|
"appSettings_enableNotifications": "Benachrichtigungen aktivieren",
|
||||||
"appSettings_enableNotificationsSubtitle": "Erhalte Benachrichtigungen für Nachrichten und Ankündigungen",
|
"appSettings_enableNotificationsSubtitle": "Erhalte Benachrichtigungen für Nachrichten und Ankündigungen",
|
||||||
@@ -285,6 +287,7 @@
|
|||||||
"contacts_newGroup": "Neue Gruppe",
|
"contacts_newGroup": "Neue Gruppe",
|
||||||
"contacts_groupName": "Gruppenname",
|
"contacts_groupName": "Gruppenname",
|
||||||
"contacts_groupNameRequired": "Der Gruppennamen ist erforderlich.",
|
"contacts_groupNameRequired": "Der Gruppennamen ist erforderlich.",
|
||||||
|
"contacts_groupNameReserved": "Dieser Gruppenname ist reserviert",
|
||||||
"contacts_groupAlreadyExists": "Die Gruppe \"{name}\" existiert bereits.",
|
"contacts_groupAlreadyExists": "Die Gruppe \"{name}\" existiert bereits.",
|
||||||
"@contacts_groupAlreadyExists": {
|
"@contacts_groupAlreadyExists": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -336,11 +339,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_hashtagChannel": "Hashtag-Kanal",
|
|
||||||
"channels_public": "Öffentlich",
|
"channels_public": "Öffentlich",
|
||||||
"channels_private": "Privat",
|
"channels_private": "Privat",
|
||||||
"channels_publicChannel": "Öffentlicher Kanal",
|
|
||||||
"channels_privateChannel": "Privater Kanal",
|
|
||||||
"channels_editChannel": "Kanal bearbeiten",
|
"channels_editChannel": "Kanal bearbeiten",
|
||||||
"channels_muteChannel": "Kanal stummschalten",
|
"channels_muteChannel": "Kanal stummschalten",
|
||||||
"channels_unmuteChannel": "Kanal Stummschaltung aufheben",
|
"channels_unmuteChannel": "Kanal Stummschaltung aufheben",
|
||||||
@@ -366,7 +366,7 @@
|
|||||||
"channels_channelName": "Kanalname",
|
"channels_channelName": "Kanalname",
|
||||||
"channels_usePublicChannel": "Verwende öffentlichen Kanal",
|
"channels_usePublicChannel": "Verwende öffentlichen Kanal",
|
||||||
"channels_standardPublicPsk": "Öffentliche Standard PSK",
|
"channels_standardPublicPsk": "Öffentliche Standard PSK",
|
||||||
"channels_pskHex": "PSK (Hex)",
|
"channels_pskHex": "PSK (Hexadezimal)",
|
||||||
"channels_generateRandomPsk": "Zufällige PSK generieren",
|
"channels_generateRandomPsk": "Zufällige PSK generieren",
|
||||||
"channels_enterChannelName": "Bitte geben Sie einen Kanalnamen ein.",
|
"channels_enterChannelName": "Bitte geben Sie einen Kanalnamen ein.",
|
||||||
"channels_pskMustBe32Hex": "Die PSK muss 32 hexadezimale Zeichen haben.",
|
"channels_pskMustBe32Hex": "Die PSK muss 32 hexadezimale Zeichen haben.",
|
||||||
@@ -387,6 +387,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_smazCompression": "SMAZ-Komprimierung",
|
"channels_smazCompression": "SMAZ-Komprimierung",
|
||||||
|
"channels_cyr2latCompression": "Cyr2Lat-Komprimierung",
|
||||||
|
"channels_cyr2latCompressionDscr": "Ersetzt einige kyrillische Zeichen durch lateinische Zeichen, wenn sie gesendet werden.",
|
||||||
|
"channels_cyr2latSettingsHeading": "Cyr2Lat-Einstellungen",
|
||||||
|
"channels_cyr2latSettingsSubheading": "Ersetzungsliste",
|
||||||
|
"channels_cyr2latSettingsDscr": "JSON-Konfiguration für die Zeichenersetzung bearbeiten",
|
||||||
|
"channels_cyr2latSettingsDialogHint": "JSON-Ersetzungstabelle",
|
||||||
|
"channels_cyr2latSettingsDialogWrongJSON": "Ungültiges JSON: {error}",
|
||||||
|
"settings_cyr2latProfileAdd": "Cyr2Lat-Profil hinzufügen",
|
||||||
|
"settings_cyr2latProfileName": "Profilname",
|
||||||
|
"settings_cyr2latProfileNameEmpty": "Der Profilname darf nicht leer sein",
|
||||||
|
"settings_cyr2latProfileAdded": "Profil erfolgreich hinzugefügt",
|
||||||
|
"settings_cyr2latProfileUpdated": "Profil erfolgreich aktualisiert",
|
||||||
|
"settings_cyr2latProfileEdit": "Cyr2Lat-Profil bearbeiten",
|
||||||
|
"settings_cyr2latProfileDelete": "Cyr2Lat-Profil löschen",
|
||||||
|
"settings_cyr2latProfileDeleted": "Profil erfolgreich gelöscht",
|
||||||
|
"settings_cyr2latProfileDeleteDscr": "Möchten Sie das Profil \"{name}\" wirklich löschen?",
|
||||||
"channels_channelUpdated": "Kanal \"{name}\" aktualisiert",
|
"channels_channelUpdated": "Kanal \"{name}\" aktualisiert",
|
||||||
"@channels_channelUpdated": {
|
"@channels_channelUpdated": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -511,7 +527,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debugFrame_flags": "- Flags: 0x{value}",
|
"debugFrame_flags": "- Flaggen: 0x{value}",
|
||||||
"@debugFrame_flags": {
|
"@debugFrame_flags": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"value": {
|
"value": {
|
||||||
@@ -530,7 +546,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debugFrame_textTypeCli": "CLI",
|
"debugFrame_textTypeCli": "Befehlszeilen-Schnittstelle",
|
||||||
"debugFrame_textTypePlain": "Einfach",
|
"debugFrame_textTypePlain": "Einfach",
|
||||||
"debugFrame_text": "- Text: \"{text}\"",
|
"debugFrame_text": "- Text: \"{text}\"",
|
||||||
"@debugFrame_text": {
|
"@debugFrame_text": {
|
||||||
@@ -632,7 +648,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"map_pinsCount": "Pins: {count}",
|
"map_pinsCount": "Nadeln: {count}",
|
||||||
"@map_pinsCount": {
|
"@map_pinsCount": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {
|
"count": {
|
||||||
@@ -641,20 +657,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"map_chat": "Benutzer",
|
"map_chat": "Benutzer",
|
||||||
"map_repeater": "Repeater",
|
"map_repeater": "Wiederholungseinheit",
|
||||||
"map_room": "Raum",
|
"map_room": "Raum",
|
||||||
"map_sensor": "Sensor",
|
"map_sensor": "Sensor",
|
||||||
"map_pinDm": "Pin (Kontakt)",
|
"map_pinDm": "Pin (Kontakt)",
|
||||||
"map_pinPrivate": "Pin (Channel)",
|
"map_pinPrivate": "Pin (Channel)",
|
||||||
"map_pinPublic": "Pin (Public)",
|
"map_pinPublic": "Kennzeichnung (Öffentlich)",
|
||||||
"map_lastSeen": "Letzte Sichtung",
|
"map_lastSeen": "Letzte Sichtung",
|
||||||
"map_disconnectConfirm": "Sind Sie sicher, dass Sie sich von diesem Gerät trennen möchten?",
|
"map_disconnectConfirm": "Sind Sie sicher, dass Sie sich von diesem Gerät trennen möchten?",
|
||||||
"map_from": "Von",
|
"map_from": "Von",
|
||||||
"map_source": "Quelle",
|
"map_source": "Quelle",
|
||||||
"map_flags": "Flags",
|
"map_flags": "Flaggen",
|
||||||
"map_shareMarkerHere": "Teilen Sie den Marker hier.",
|
"map_shareMarkerHere": "Teilen Sie den Marker hier.",
|
||||||
"map_pinLabel": "Pin Name",
|
"map_pinLabel": "Pin Name",
|
||||||
"map_label": "Label",
|
"map_label": "Etikett",
|
||||||
"map_pointOfInterest": "Punkt von Interesse",
|
"map_pointOfInterest": "Punkt von Interesse",
|
||||||
"map_sendToContact": "Senden an Kontakt",
|
"map_sendToContact": "Senden an Kontakt",
|
||||||
"map_sendToChannel": "Senden an Kanal",
|
"map_sendToChannel": "Senden an Kanal",
|
||||||
@@ -882,7 +898,7 @@
|
|||||||
"repeater_statusSubtitle": "Status, Statistiken und Nachbarn anzeigen",
|
"repeater_statusSubtitle": "Status, Statistiken und Nachbarn anzeigen",
|
||||||
"repeater_telemetry": "Telemetrie",
|
"repeater_telemetry": "Telemetrie",
|
||||||
"repeater_telemetrySubtitle": "Sensordaten und Systemwerte anzeigen",
|
"repeater_telemetrySubtitle": "Sensordaten und Systemwerte anzeigen",
|
||||||
"repeater_cli": "CLI",
|
"repeater_cli": "Befehlszeilen-Schnittstelle",
|
||||||
"repeater_cliSubtitle": "Sende Befehle an den Repeater",
|
"repeater_cliSubtitle": "Sende Befehle an den Repeater",
|
||||||
"repeater_settings": "Einstellungen",
|
"repeater_settings": "Einstellungen",
|
||||||
"repeater_settingsSubtitle": "Repeater-parameter konfigurieren",
|
"repeater_settingsSubtitle": "Repeater-parameter konfigurieren",
|
||||||
@@ -983,7 +999,7 @@
|
|||||||
},
|
},
|
||||||
"repeater_settingsTitle": "Repeater Einstellungen",
|
"repeater_settingsTitle": "Repeater Einstellungen",
|
||||||
"repeater_basicSettings": "Grundlegende Einstellungen",
|
"repeater_basicSettings": "Grundlegende Einstellungen",
|
||||||
"repeater_repeaterName": "Repeater Name",
|
"repeater_repeaterName": "Name des Repeater",
|
||||||
"repeater_repeaterNameHelper": "Anzeigename für diesen Repeater",
|
"repeater_repeaterNameHelper": "Anzeigename für diesen Repeater",
|
||||||
"repeater_adminPassword": "Admin-Passwort",
|
"repeater_adminPassword": "Admin-Passwort",
|
||||||
"repeater_adminPasswordHelper": "Vollzugriffspasswort",
|
"repeater_adminPasswordHelper": "Vollzugriffspasswort",
|
||||||
@@ -991,7 +1007,7 @@
|
|||||||
"repeater_guestPasswordHelper": "Schreibgeschütztes Zugriffspasswort",
|
"repeater_guestPasswordHelper": "Schreibgeschütztes Zugriffspasswort",
|
||||||
"repeater_radioSettings": "Funk Einstellungen",
|
"repeater_radioSettings": "Funk Einstellungen",
|
||||||
"repeater_frequencyMhz": "Frequenz (MHz)",
|
"repeater_frequencyMhz": "Frequenz (MHz)",
|
||||||
"repeater_frequencyHelper": "300-2500 MHz",
|
"repeater_frequencyHelper": "300–2500 MHz",
|
||||||
"repeater_txPower": "TX Power",
|
"repeater_txPower": "TX Power",
|
||||||
"repeater_txPowerHelper": "1-30 dBm",
|
"repeater_txPowerHelper": "1-30 dBm",
|
||||||
"repeater_bandwidth": "Bandbreite",
|
"repeater_bandwidth": "Bandbreite",
|
||||||
@@ -1058,6 +1074,81 @@
|
|||||||
},
|
},
|
||||||
"repeater_confirm": "Bestätigen",
|
"repeater_confirm": "Bestätigen",
|
||||||
"repeater_settingsSaved": "Einstellungen erfolgreich gespeichert",
|
"repeater_settingsSaved": "Einstellungen erfolgreich gespeichert",
|
||||||
|
"repeater_rxGain": "Erhöhter RX-Gewinn",
|
||||||
|
"repeater_rxGainHelper": "Höhere Empfindlichkeit, höherer Stromverbrauch (nur für SX1262/SX1268)",
|
||||||
|
"repeater_refreshRxGain": "Erneuerung des verstärkten RX-Effekts",
|
||||||
|
"repeater_multiAcks": "Mehrere Bestätigungen",
|
||||||
|
"repeater_multiAcksSubtitle": "Nachrichten über verschiedene Pfade senden, um die Zustellbarkeit zu verbessern.",
|
||||||
|
"repeater_refreshMultiAcks": "Mehrere Bestätigungen neu senden/aktualisieren",
|
||||||
|
"repeater_networkHealth": "Netzwerkgesundheit",
|
||||||
|
"repeater_loopDetect": "Erkennung von Schleifen",
|
||||||
|
"repeater_loopDetectHelper": "Erstellen Sie \"Flood\"-Pakete, die so aussehen, als ob sie Schleifen erzeugen.",
|
||||||
|
"repeater_loopDetectOff": "Aus",
|
||||||
|
"repeater_loopDetectMinimal": "Minimal",
|
||||||
|
"repeater_loopDetectModerate": "mäßig",
|
||||||
|
"repeater_loopDetectStrict": "streng",
|
||||||
|
"repeater_dutyCycle": "Betriebsdauer",
|
||||||
|
"repeater_dutyCycleHelper": "Höchster zulässiger Prozentsatz der Sendefläche",
|
||||||
|
"repeater_dutyCyclePercent": "{percent}%",
|
||||||
|
"@repeater_dutyCyclePercent": {
|
||||||
|
"placeholders": {
|
||||||
|
"percent": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_ownerInfo": "Information zum Betreiber",
|
||||||
|
"repeater_ownerInfoHelper": "Öffentliche Metadaten für dieses Gerät",
|
||||||
|
"repeater_refreshOwnerInfo": "Aktualisieren Sie die Informationen zum Betreiber",
|
||||||
|
"repeater_floodMax": "Max-Hops-Flut",
|
||||||
|
"repeater_floodMaxHelper": "Maximale Anzahl an Hop-Paketen, die ein einzelnes Paket durchlaufen kann (0-64)",
|
||||||
|
"repeater_advancedSettings": "Fortgeschritten",
|
||||||
|
"repeater_advancedSettingsSubtitle": "Regler für erfahrene Bediener",
|
||||||
|
"repeater_pathHashMode": "Hash-Modus für Pfade",
|
||||||
|
"repeater_pathHashModeHelper": "Bytes, die zur Kodierung der ID dieses Repeaters in Flood-Pfad-/Schleifen-Erkennung-Tags verwendet werden. 0 = 1 Byte (256 IDs, bis zu 64 Hops), 1 = 2 Bytes (65.000 IDs, bis zu 32 Hops), 2 = 3 Bytes (16 Millionen IDs, bis zu 21 Hops). Firmware-Versionen 1.13 und älter verwenden mehrstellige Pfade – ab Version 1.14+ wird nur ein Pfad erstellt, sobald das Netzwerk aktiv ist.",
|
||||||
|
"repeater_txDelay": "Verzögerung bei Flood TX",
|
||||||
|
"repeater_txDelayHelper": "Wiederholung des Abstands für Hochwasser-Verkehr, als Multiplikator der Übertragungszeit des Pakets (0-2, Standardwert 0,5). Höherer Wert = weniger Kollisionen, aber langsamere Übertragung.",
|
||||||
|
"repeater_directTxDelay": "Direkter TX-Verzögerung",
|
||||||
|
"repeater_directTxDelayHelper": "Die Übertragungsrate für direkten (nicht-fluten) Datenverkehr wird als Vielfaches der Übertragungszeit des Pakets festgelegt (0-2, Standardwert 0,3).",
|
||||||
|
"repeater_intThresh": "Grenzwert für Störungen",
|
||||||
|
"repeater_intThreshHelper": "Der Schwellenwert wird an die Rauschpegel-Kalibrierung des Radios angepasst, sodass Störungen über diesem Wert abgefangen werden. 0 deaktiviert – erhöhen Sie diesen Wert nur, wenn Sie in einem verrauschten Frequenzbereich RX-Fehler feststellen.",
|
||||||
|
"repeater_agcResetInterval": "Intervall für die Rücksetzung von AGC",
|
||||||
|
"repeater_agcResetIntervalHelper": "Wie oft sollte die automatische Verstärkungskontrolle des Radios zurückgesetzt werden, um von einem Zustand mit zu hoher Verstärkung wieder in einen normalen Zustand zu gelangen? Die Einstellung „Sekunden“ ermöglicht eine Rücksetzung alle 4 Sekunden. Die Einstellung „0“ deaktiviert die periodische Rücksetzung.",
|
||||||
|
"repeater_actionsTitle": "Aktionen",
|
||||||
|
"repeater_sendAdvert": "Flood-Werbung versenden",
|
||||||
|
"repeater_sendAdvertSubtitle": "Eine Werbekampagne für Überschwemmungen über das Netzwerk verbreiten.",
|
||||||
|
"repeater_sendAdvertZeroHop": "Versenden Sie eine Anzeige ohne Zwischenvermittler.",
|
||||||
|
"repeater_sendAdvertZeroHopSubtitle": "Eine Werbekampagne mit einem einzigen Sender (ohne Weiterleitung) senden.",
|
||||||
|
"repeater_clockSync": "Uhr jetzt synchronisieren",
|
||||||
|
"repeater_clockSyncSubtitle": "Übertragen Sie die Uhrzeit Ihres Telefons an den Repeater.",
|
||||||
|
"repeater_actionSucceeded": "{action} war erfolgreich",
|
||||||
|
"@repeater_actionSucceeded": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_actionFailed": "{action} fehlgeschlagen: {error}",
|
||||||
|
"@repeater_actionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_settingsSavedRebootNeeded": "Einstellungen gespeichert – Repeater neu starten, um die Änderungen anzuwenden.",
|
||||||
|
"repeater_settingsPartialFailure": "Einige Einstellungen sind fehlgeschlagen: {failures}",
|
||||||
|
"@repeater_settingsPartialFailure": {
|
||||||
|
"placeholders": {
|
||||||
|
"failures": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repeater_errorSavingSettings": "Fehler beim Speichern der Einstellungen: {error}",
|
"repeater_errorSavingSettings": "Fehler beim Speichern der Einstellungen: {error}",
|
||||||
"@repeater_errorSavingSettings": {
|
"@repeater_errorSavingSettings": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1069,11 +1160,9 @@
|
|||||||
"repeater_refreshBasicSettings": "Grundlegende Einstellungen aktualisieren",
|
"repeater_refreshBasicSettings": "Grundlegende Einstellungen aktualisieren",
|
||||||
"repeater_refreshRadioSettings": "Radio-Einstellungen aktualisieren",
|
"repeater_refreshRadioSettings": "Radio-Einstellungen aktualisieren",
|
||||||
"repeater_refreshTxPower": "Sendeleistung aktualisieren",
|
"repeater_refreshTxPower": "Sendeleistung aktualisieren",
|
||||||
"repeater_refreshLocationSettings": "Aktualisieren Sie die Standort Einstellungen",
|
|
||||||
"repeater_refreshPacketForwarding": "Aktualisieren Paketweiterleitung",
|
"repeater_refreshPacketForwarding": "Aktualisieren Paketweiterleitung",
|
||||||
"repeater_refreshGuestAccess": "Aktualisieren Sie den Gastzugriff",
|
"repeater_refreshGuestAccess": "Aktualisieren Sie den Gastzugriff",
|
||||||
"repeater_refreshPrivacyMode": "Wiederherstellen des Datenschutzzustands",
|
"repeater_refreshPrivacyMode": "Wiederherstellen des Datenschutzzustands",
|
||||||
"repeater_refreshAdvertisementSettings": "Aktualisieren Sie die Ankündigungseinstellungen",
|
|
||||||
"repeater_refreshed": "{label} wurde aktualisiert",
|
"repeater_refreshed": "{label} wurde aktualisiert",
|
||||||
"@repeater_refreshed": {
|
"@repeater_refreshed": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1090,7 +1179,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repeater_cliTitle": "Repeater CLI",
|
"repeater_cliTitle": "Befehlszeilen-Schnittstelle (CLI) für Repeater",
|
||||||
"repeater_debugNextCommand": "Fehlersuche des nächsten Befehls",
|
"repeater_debugNextCommand": "Fehlersuche des nächsten Befehls",
|
||||||
"repeater_commandHelp": "Hilfe",
|
"repeater_commandHelp": "Hilfe",
|
||||||
"repeater_clearHistory": "Löschen der Historie",
|
"repeater_clearHistory": "Löschen der Historie",
|
||||||
@@ -1346,7 +1435,7 @@
|
|||||||
"listFilter_sortBy": "Sortiere nach",
|
"listFilter_sortBy": "Sortiere nach",
|
||||||
"listFilter_latestMessages": "Letzte Nachrichten",
|
"listFilter_latestMessages": "Letzte Nachrichten",
|
||||||
"listFilter_heardRecently": "Kürzlich gehört",
|
"listFilter_heardRecently": "Kürzlich gehört",
|
||||||
"listFilter_az": "A-Z",
|
"listFilter_az": "Von A bis Z",
|
||||||
"listFilter_filters": "Filtere",
|
"listFilter_filters": "Filtere",
|
||||||
"listFilter_all": "Alle",
|
"listFilter_all": "Alle",
|
||||||
"listFilter_favorites": "Favoriten",
|
"listFilter_favorites": "Favoriten",
|
||||||
@@ -1461,7 +1550,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common_ok": "OK",
|
"common_ok": "Alles klar",
|
||||||
"community_create": "Erstelle Community",
|
"community_create": "Erstelle Community",
|
||||||
"community_createDesc": "Erstelle eine neue Community und teile sie über den QR-Code.",
|
"community_createDesc": "Erstelle eine neue Community und teile sie über den QR-Code.",
|
||||||
"community_join": "Beitreten",
|
"community_join": "Beitreten",
|
||||||
@@ -1472,14 +1561,14 @@
|
|||||||
"community_showQr": "Zeige QR-Code",
|
"community_showQr": "Zeige QR-Code",
|
||||||
"community_publicChannel": "Community Öffentlich",
|
"community_publicChannel": "Community Öffentlich",
|
||||||
"community_enterName": "Bitte Community-Name eingeben",
|
"community_enterName": "Bitte Community-Name eingeben",
|
||||||
"community_title": "Community",
|
"community_title": "Gemeinschaft",
|
||||||
"community_created": "Community \"{name}\" wurde erstellt",
|
"community_created": "Community \"{name}\" wurde erstellt",
|
||||||
"community_joined": "Community \"{name}\" beigetreten",
|
"community_joined": "Community \"{name}\" beigetreten",
|
||||||
"community_qrTitle": "Teile Community",
|
"community_qrTitle": "Teile Community",
|
||||||
"community_qrInstructions": "Scannen Sie diesen QR-Code, um sich \"{name}\" anzuschließen.",
|
"community_qrInstructions": "Scannen Sie diesen QR-Code, um sich \"{name}\" anzuschließen.",
|
||||||
"community_hashtagPrivacyHint": "Community-Hashtag-Kanäle können nur von Mitgliedern der Community betreten werden",
|
"community_hashtagPrivacyHint": "Community-Hashtag-Kanäle können nur von Mitgliedern der Community betreten werden",
|
||||||
"community_hashtagChannel": "Community Hashtag",
|
"community_hashtagChannel": "Gemeinschaftlicher Hashtag",
|
||||||
"community_name": "Community Name",
|
"community_name": "Name der Gemeinde",
|
||||||
"community_invalidQrCode": "Ungültiger Community-QR-Code",
|
"community_invalidQrCode": "Ungültiger Community-QR-Code",
|
||||||
"community_alreadyMember": "Bereits registriert",
|
"community_alreadyMember": "Bereits registriert",
|
||||||
"community_alreadyMemberMessage": "Sie sind bereits Mitglied von \"{name}\".",
|
"community_alreadyMemberMessage": "Sie sind bereits Mitglied von \"{name}\".",
|
||||||
@@ -1506,7 +1595,7 @@
|
|||||||
"community_regularHashtagDesc": "Öffentlicher Hashtag (jeder kann teilnehmen)",
|
"community_regularHashtagDesc": "Öffentlicher Hashtag (jeder kann teilnehmen)",
|
||||||
"community_communityHashtagDesc": "Nur für Mitglieder der Community",
|
"community_communityHashtagDesc": "Nur für Mitglieder der Community",
|
||||||
"community_forCommunity": "Für {name}",
|
"community_forCommunity": "Für {name}",
|
||||||
"community_communityHashtag": "Community Hashtag",
|
"community_communityHashtag": "Gemeinschaftlicher Hashtag",
|
||||||
"@community_regenerateSecretConfirm": {
|
"@community_regenerateSecretConfirm": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"name": {
|
"name": {
|
||||||
@@ -1887,5 +1976,372 @@
|
|||||||
"usbStatus_notConnected": "Wählen Sie ein USB-Gerät aus",
|
"usbStatus_notConnected": "Wählen Sie ein USB-Gerät aus",
|
||||||
"usbStatus_connecting": "Verbindung zum USB-Gerät...",
|
"usbStatus_connecting": "Verbindung zum USB-Gerät...",
|
||||||
"usbConnectionFailed": "Fehler beim USB-Verbindungsaufbau: {error}",
|
"usbConnectionFailed": "Fehler beim USB-Verbindungsaufbau: {error}",
|
||||||
"usbErrorConnectTimedOut": "Verbindung konnte nicht hergestellt werden. Stellen Sie sicher, dass das Gerät die entsprechende USB-Firmware enthält."
|
"usbErrorConnectTimedOut": "Verbindung konnte nicht hergestellt werden. Stellen Sie sicher, dass das Gerät die entsprechende USB-Firmware enthält.",
|
||||||
|
"@tcpStatus_connectingTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"endpoint": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tcpConnectionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tcpHostLabel": "IP-Adresse",
|
||||||
|
"connectionChoiceTcpLabel": "TCP",
|
||||||
|
"tcpHostHint": "192.168.40.10",
|
||||||
|
"tcpScreenTitle": "Verbinden über TCP",
|
||||||
|
"tcpPortLabel": "Hafen",
|
||||||
|
"tcpPortHint": "5000",
|
||||||
|
"tcpStatus_notConnected": "Geben Sie den Endpunkt ein und verbinden Sie sich.",
|
||||||
|
"tcpStatus_connectingTo": "Verbindung zu {endpoint}...",
|
||||||
|
"tcpErrorHostRequired": "Eine IP-Adresse ist erforderlich.",
|
||||||
|
"tcpErrorPortInvalid": "Die Portnummer muss zwischen 1 und 65535 liegen.",
|
||||||
|
"tcpErrorUnsupported": "Die TCP-Übertragung wird auf dieser Plattform nicht unterstützt.",
|
||||||
|
"tcpErrorTimedOut": "Die TCP-Verbindung ist abgelaufen.",
|
||||||
|
"tcpConnectionFailed": "Fehler beim TCP-Verbindungsaufbau: {error}",
|
||||||
|
"map_showDiscoveryContacts": "Entdeckungs-Kontakte anzeigen",
|
||||||
|
"map_setAsMyLocation": "Als meine aktuelle Position festlegen",
|
||||||
|
"@path_routeWeight": {
|
||||||
|
"placeholders": {
|
||||||
|
"weight": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings_allowByContact": "Zulassen durch Kontaktflaggen",
|
||||||
|
"settings_privacy": "Datenschutzeinstellungen",
|
||||||
|
"settings_allowAll": "Alles zulassen",
|
||||||
|
"settings_privacySettingsDescription": "Wählen Sie die Informationen, die Ihr Gerät mit anderen teilt.",
|
||||||
|
"settings_denyAll": "Alle ablehnen",
|
||||||
|
"settings_privacySubtitle": "Steuern Sie die Informationen, die freigegeben werden.",
|
||||||
|
"settings_telemetryLocationMode": "Telemetrie-Ortsmodus",
|
||||||
|
"settings_telemetryEnvironmentMode": "Telemetrie-Umgebungsmodus",
|
||||||
|
"settings_advertLocation": "Anzeigenort",
|
||||||
|
"settings_advertLocationSubtitle": "Ort in der Anzeige einbeziehen",
|
||||||
|
"settings_telemetryBaseMode": "Telemetrie-Basismodus",
|
||||||
|
"contact_teleBase": "Telemetriebasis",
|
||||||
|
"contact_teleBaseSubtitle": "Erlauben des Freigebens des Batteriestands und der grundlegenden Telemetrie",
|
||||||
|
"contact_teleLoc": "Telemetrieort",
|
||||||
|
"contact_teleLocSubtitle": "Teilen von Standortdaten zulassen",
|
||||||
|
"contact_info": "Kontaktinformationen",
|
||||||
|
"contact_settings": "Kontakteinstellungen",
|
||||||
|
"contact_telemetry": "Telemetrie",
|
||||||
|
"contact_teleEnv": "Telemetrieumgebung",
|
||||||
|
"contact_lastSeen": "Zuletzt gesehen",
|
||||||
|
"contact_clearChat": "Chat löschen",
|
||||||
|
"contact_teleEnvSubtitle": "Teilen von Umgebungsensordaten zulassen",
|
||||||
|
"appSettings_initialRouteWeightSubtitle": "Ausgangsgewicht für neu entdeckte Pfade",
|
||||||
|
"appSettings_maxRouteWeightSubtitle": "Maximales Gewicht, das ein Weg durch erfolgreiche Lieferungen erreichen kann.",
|
||||||
|
"appSettings_maxRouteWeight": "Maximale Gesamtstreckenlänge",
|
||||||
|
"appSettings_initialRouteWeight": "Anfangs-Streckengewicht",
|
||||||
|
"appSettings_routeWeightSuccessIncrement": "Erhöhung des Erfolgsgewichts",
|
||||||
|
"appSettings_routeWeightSuccessIncrementSubtitle": "Gewicht, das einem Pfad nach erfolgreicher Lieferung hinzugefügt wird.",
|
||||||
|
"appSettings_routeWeightFailureDecrement": "Reduzierung des Gewichts bei Fehlern",
|
||||||
|
"appSettings_routeWeightFailureDecrementSubtitle": "Gewicht, das nach einem fehlgeschlagenen Versand von einem Weg entfernt wurde",
|
||||||
|
"appSettings_maxMessageRetries": "Maximale Anzahl an Wiederholungsversuchen",
|
||||||
|
"appSettings_maxMessageRetriesSubtitle": "Anzahl der Versuche, eine Nachricht erneut zu senden, bevor sie als fehlgeschlagen markiert wird.",
|
||||||
|
"path_routeWeight": "{weight}/{max}",
|
||||||
|
"settings_telemetryModeUpdated": "Telemetriemodus aktualisiert",
|
||||||
|
"map_showOverlaps": "Überlappungen der Repeater-Taste",
|
||||||
|
"map_runTraceWithReturnPath": "Auf dem gleichen Pfad zurückkehren.",
|
||||||
|
"@radioStats_noiseFloor": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastRssi": {
|
||||||
|
"placeholders": {
|
||||||
|
"rssiDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastSnr": {
|
||||||
|
"placeholders": {
|
||||||
|
"snr": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_txAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_rxAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_stripNoise": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chat_sendCooldown": "Bitte warten Sie einen Moment, bevor Sie erneut senden.",
|
||||||
|
"appSettings_jumpToOldestUnread": "Zum ältesten, nicht gelesenen Eintrag springen",
|
||||||
|
"appSettings_languageHu": "Ungarisch",
|
||||||
|
"appSettings_jumpToOldestUnreadSubtitle": "Wenn Sie ein Chatfenster öffnen, in dem Nachrichten vorhanden sind, die noch nicht gelesen wurden, scrollen Sie zu der ersten unlesenen Nachricht, anstatt zur neuesten.",
|
||||||
|
"appSettings_languageJa": "Japanisch",
|
||||||
|
"appSettings_languageKo": "Koreanisch",
|
||||||
|
"radioStats_tooltip": "Daten zu Radio- und Mesh-Netzwerken",
|
||||||
|
"radioStats_screenTitle": "Senderinformationen",
|
||||||
|
"radioStats_notConnected": "Verbinden Sie ein Gerät, um Radiostatisiken anzuzeigen.",
|
||||||
|
"radioStats_firmwareTooOld": "Für die Verwendung der Funkstatistiken ist die Firmware-Version 8 oder höher erforderlich.",
|
||||||
|
"radioStats_waiting": "Warte auf Daten…",
|
||||||
|
"radioStats_noiseFloor": "Rauschpegel: {noiseDbm} dBm",
|
||||||
|
"radioStats_lastRssi": "Letzter RSSI-Wert: {rssiDbm} dBm",
|
||||||
|
"radioStats_lastSnr": "Letzter SNR: {snr} dB",
|
||||||
|
"radioStats_txAir": "Gesamt-TX-Zeit: {seconds} s",
|
||||||
|
"radioStats_rxAir": "Gesamt-RX-Zeit: {seconds} s",
|
||||||
|
"radioStats_chartCaption": "Rauschpegel (dBm) basierend auf den letzten Messwerten.",
|
||||||
|
"radioStats_stripNoise": "Rauschpegel: {noiseDbm} dBm",
|
||||||
|
"radioStats_stripWaiting": "Abrufen von Radiostatus…",
|
||||||
|
"radioStats_settingsTile": "Senderinformationen",
|
||||||
|
"radioStats_settingsSubtitle": "Rauschpegel, RSSI, Signal-Rausch-Verhältnis (SNR) und Nutzzeit",
|
||||||
|
"@translation_downloadFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_title": "Übersetzung",
|
||||||
|
"translation_composerTitle": "Übersetzen Sie vor dem Versenden",
|
||||||
|
"translation_enableSubtitle": "Nachrichten empfangen und übersetzen sowie die Möglichkeit bieten, Nachrichten vor dem Versenden zu übersetzen.",
|
||||||
|
"translation_enableTitle": "Aktivieren Sie die Übersetzung",
|
||||||
|
"translation_composerSubtitle": "Steuert den Standardzustand des Icons für die Übersetzung des Komponisten.",
|
||||||
|
"translation_targetLanguage": "Zielsprache",
|
||||||
|
"translation_useAppLanguage": "Verwenden Sie die App-Sprache",
|
||||||
|
"translation_downloadedModelLabel": "Heruntergeladenes Modell",
|
||||||
|
"translation_presetModelLabel": "Vordefinierter Hugging Face-Modell",
|
||||||
|
"translation_manualUrlLabel": "URL für das manuelle Modell",
|
||||||
|
"translation_downloadModel": "Modell herunterladen",
|
||||||
|
"translation_downloading": "Herunterladen...",
|
||||||
|
"translation_working": "Arbeiten...",
|
||||||
|
"translation_stop": "Stopp",
|
||||||
|
"translation_mergingChunks": "Zusammenführen der heruntergeladenen Teile in die finale Datei...",
|
||||||
|
"translation_downloadedModels": "Heruntergeladene Modelle",
|
||||||
|
"translation_deleteModel": "Modell löschen",
|
||||||
|
"translation_modelDownloaded": "Übersetzungsmotor heruntergeladen.",
|
||||||
|
"translation_downloadStopped": "Herunterladen abgebrochen.",
|
||||||
|
"translation_downloadFailed": "Download fehlgeschlagen: {error}",
|
||||||
|
"translation_enterUrlFirst": "Geben Sie zunächst die URL eines Modells ein.",
|
||||||
|
"@scanner_linuxPairingPinPrompt": {
|
||||||
|
"placeholders": {
|
||||||
|
"deviceName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@translation_translateTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"language": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_messageTranslation": "Nachricht übersetzen",
|
||||||
|
"translation_composerEnabledHint": "Die Nachrichten werden vor dem Versenden übersetzt.",
|
||||||
|
"translation_translateBeforeSending": "Übersetzen Sie vor dem Versenden",
|
||||||
|
"translation_composerDisabledHint": "Nachrichten in der ursprünglichen, getippten Sprache senden.",
|
||||||
|
"translation_translateTo": "Übersetzen Sie auf {language}",
|
||||||
|
"translation_translationOptions": "Übersetzungsmöglichkeiten",
|
||||||
|
"translation_systemLanguage": "Sprache des Systems",
|
||||||
|
"scanner_linuxPairingShowPin": "PIN anzeigen",
|
||||||
|
"scanner_linuxPairingHidePin": "PIN ausblenden",
|
||||||
|
"scanner_linuxPairingPinTitle": "Bluetooth-Paarungs-PIN",
|
||||||
|
"scanner_linuxPairingPinPrompt": "Geben Sie die PIN für {deviceName} ein (leer lassen, falls keine).",
|
||||||
|
"repeater_cliQuickClockSync": "Uhr Synchronisieren",
|
||||||
|
"repeater_cliQuickDiscovery": "Entdecke Nachbarn",
|
||||||
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLogin": "Uhrzeit-Synchronisation nach dem Anmelden",
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Automatisch \"Uhrzeit-Synchronisierung\" nach erfolgreicher Anmeldung senden.",
|
||||||
|
"repeater_guest": "Informationen zu Repeatern",
|
||||||
|
"repeater_guestTools": "Gastwerkzeuge",
|
||||||
|
"chat_sendMessage": "Nachricht senden",
|
||||||
|
"room_guest": "Informationen zum Room Server",
|
||||||
|
"repeater_getCategory": "Werte erhalten",
|
||||||
|
"repeater_powerMgmt": "Energieverwaltung",
|
||||||
|
"repeater_sensors": "Sensoren",
|
||||||
|
"repeater_cliHelpPowerOff": "Schaltet das Gerät aus. (keine Antwort erwartet)",
|
||||||
|
"repeater_cliHelpClkReboot": "Setzt die Uhr auf einen bekannten Zeitpunkt zurück und startet das Gerät neu.",
|
||||||
|
"repeater_cliHelpAdvertZeroHop": "Sendet eine Werbeanzeige, die nur an unmittelbare Nachbarn gesendet wird (ohne Zwischenstation).",
|
||||||
|
"repeater_cliHelpStartOta": "Startet ein Firmware-Update über Funk, das auf unterstützten Boards durchgeführt wird.",
|
||||||
|
"repeater_cliHelpTime": "Stellt die Gerätuhr auf die angegebene Unix-Epoche in Sekunden ein. Die Uhr kann nicht rückwärts laufen.",
|
||||||
|
"repeater_cliHelpBoard": "Zeigt den Hersteller/die Hardware-Kennung an.",
|
||||||
|
"repeater_cliHelpDiscoverNeighbors": "Sendet eine Anfrage zur Entdeckung von Nachbarn in der Nähe. (Nur bei Repeatern)",
|
||||||
|
"repeater_cliHelpPowersaving": "Zeigt an, ob der Energiesparmodus aktiviert oder deaktiviert ist.",
|
||||||
|
"repeater_cliHelpPowersavingOnOff": "Aktiviert oder deaktiviert den Energiesparmodus (falls unterstützt).",
|
||||||
|
"repeater_cliHelpErase": "(Nur für serielle Schnittstellen) Formatiert das Dateisystem des Geräts. Löscht alle Einstellungen und Kontakte.",
|
||||||
|
"repeater_cliHelpSetDutyCycle": "Legt den maximal zulässigen Übertragungszyklus als Prozentsatz fest (1-100). Passt den Zeitfaktor intern an.",
|
||||||
|
"repeater_cliHelpSetPrvKey": "(Nur für serielle Anwendungen) Ersetzt den privaten Schlüssel zur Geräteidentifizierung. Nach der Anwendung ist ein Neustart erforderlich. Generiert einen neuen öffentlichen Schlüssel.",
|
||||||
|
"repeater_cliHelpSetRadioRxGain": "(Nur für SX126x) Schaltet die verstärkte RX-Verstärkung ein, um die Empfindlichkeit bei höherem Stromverbrauch zu verbessern.",
|
||||||
|
"repeater_cliHelpSetOwnerInfo": "Definiert den String mit den Kontaktinformationen des Eigentümers, der in den Anzeigen enthalten ist. Verwenden Sie '|' für Zeilenumbrüche.",
|
||||||
|
"repeater_cliHelpSetPathHashMode": "Legt den Modus für die Pfad-Hashes fest. 0 = ältere Version, 1 = Standard, 2 = streng. Beeinflusst, wie Routing-Pfade abgeglichen werden.",
|
||||||
|
"repeater_cliHelpSetLoopDetect": "Legt die Empfindlichkeit der Schleifenerkennung fest: aus, minimal, moderat oder streng.",
|
||||||
|
"repeater_cliHelpSetFreq": "(Nur für die serielle Schnittstelle) Ermöglicht die schnelle Einstellung der Frequenz. Nach der Einstellung ist ein Neustart erforderlich. Für die vollständige Einstellung aller Radio-Parameter wird die Option \"Radio einstellen\" empfohlen.",
|
||||||
|
"repeater_cliHelpSetBridgeChannel": "(Nur für ESPNow-Brücke) Legt den verwendeten WLAN-Kanal (1-14) für die Brücke fest.",
|
||||||
|
"repeater_cliHelpGetName": "Zeigt den konfigurierten Knotenamen an.",
|
||||||
|
"repeater_cliHelpGetRole": "Zeigt die Funktion der Firmware an (Repeater, Raumserver usw.).",
|
||||||
|
"repeater_cliHelpGetPublicKey": "Zeigt den öffentlichen Schlüssel des Geräts an.",
|
||||||
|
"repeater_cliHelpGetPrvKey": "(Nur für serielle Kommunikation) Zeigt den privaten Schlüssel des Geräts an. Behandeln Sie diesen als ein Geheimnis.",
|
||||||
|
"repeater_cliHelpGetRepeat": "Zeigt an, ob die Weiterleitung von Paketen (als Repeater) aktiviert oder deaktiviert ist.",
|
||||||
|
"repeater_cliHelpGetTx": "Zeigt die aktuelle Sendeleistung in dBm an.",
|
||||||
|
"repeater_cliHelpGetFreq": "Zeigt die konfigurierte Funkfrequenz in MHz an.",
|
||||||
|
"repeater_cliHelpGetRadio": "Zeigt alle Funkparameter an: Frequenz, Bandbreite, Spreading-Faktor, Codierungsrate.",
|
||||||
|
"repeater_cliHelpGetRadioRxGain": "(Nur für SX126x) Zeigt den Zustand des verstärkten Empfangs (RX).",
|
||||||
|
"repeater_cliHelpGetAf": "Zeigt den aktuellen Zeitfaktor an.",
|
||||||
|
"repeater_cliHelpGetDutyCycle": "Zeigt den aktuellen zulässigen Schaltzyklus als Prozentsatz an.",
|
||||||
|
"repeater_cliHelpGetIntThresh": "Zeigt den Grenzwert für Kanalüberlagerung in dB an.",
|
||||||
|
"repeater_cliHelpGetAgcResetInterval": "Zeigt das Intervall für die Rücksetzung des AGC in Sekunden an.",
|
||||||
|
"repeater_cliHelpGetMultiAcks": "Zeigt an, ob der Modus \"doppelte ACK\"-Funktion aktiviert (1) oder deaktiviert (0) ist.",
|
||||||
|
"repeater_cliHelpGetAllowReadOnly": "Zeigt an, ob der Zugriff für Gäste nur in Lesemodus erlaubt ist.",
|
||||||
|
"repeater_cliHelpGetAdvertInterval": "Zeigt die Dauer des lokalen Werbeintervalls in Minuten an.",
|
||||||
|
"repeater_cliHelpGetFloodAdvertInterval": "Zeigt die Dauer der Werbeunterbrechung in Stunden an.",
|
||||||
|
"repeater_cliHelpGetGuestPassword": "Zeigt das konfigurierte Gast-Passwort an.",
|
||||||
|
"repeater_cliHelpGetLat": "Zeigt die konfigurierte Breitengrade.",
|
||||||
|
"repeater_cliHelpGetLon": "Zeigt die konfigurierte Länge an.",
|
||||||
|
"repeater_cliHelpGetRxDelay": "Zeigt den Basiswert für die Verzögerungszeit an.",
|
||||||
|
"repeater_cliHelpGetTxDelay": "Zeigt den Faktor für die Übertragungsverzögerung im Notfallmodus an.",
|
||||||
|
"repeater_cliHelpGetDirectTxDelay": "Zeigt den Faktor für die Verzögerung im Direktmodus an.",
|
||||||
|
"repeater_cliHelpGetFloodMax": "Zeigt die maximale Anzahl von Überschwemmungsphasen an.",
|
||||||
|
"repeater_cliHelpGetOwnerInfo": "Zeigt die Zeichenkette mit den Kontaktinformationen des Eigentümers an.",
|
||||||
|
"repeater_cliHelpGetPathHashMode": "Zeigt den Pfad-Hash-Modus (0/1/2) an.",
|
||||||
|
"repeater_cliHelpGetLoopDetect": "Zeigt die Empfindlichkeit der Schleifenerkennung an.",
|
||||||
|
"repeater_cliHelpGetAcl": "(Nur für serielle Kommunikation) Zeigt die Zugriffskontrolleinträge auf einem Repeater an.",
|
||||||
|
"repeater_cliHelpGetBridgeEnabled": "Zeigt an, ob die Brücke aktiviert ist.",
|
||||||
|
"repeater_cliHelpGetBridgeDelay": "Zeigt die Verzögerung der Brücke in Millisekunden an.",
|
||||||
|
"repeater_cliHelpGetBridgeSource": "Zeigt, ob die Brücke RX- oder TX-Pakete empfängt oder sendet.",
|
||||||
|
"repeater_cliHelpGetBridgeBaud": "(Nur für RS232-Verbindungen) Zeigt die Baudrate der Verbindung an.",
|
||||||
|
"repeater_cliHelpGetBridgeChannel": "(Nur für ESPNow-Brücke) Zeigt den WLAN-Kanal der Brücke an.",
|
||||||
|
"repeater_cliHelpGetBridgeSecret": "(Nur für ESPNow-Brücke) Zeigt das gemeinsam genutzte Geheimnis der Brücke.",
|
||||||
|
"repeater_cliHelpGetBootloaderVer": "(Nur für NRF52) Zeigt die Version des Bootloaders an.",
|
||||||
|
"repeater_cliHelpGetAdcMultiplier": "Zeigt den ADC-Verstärker (Spannungs-Skalierung) an.",
|
||||||
|
"repeater_cliHelpGetPwrMgtSupport": "Gibt an, ob der Verwaltungsrat die Funktion zur Energieverwaltung unterstützt.",
|
||||||
|
"repeater_cliHelpGetPwrMgtSource": "Zeigt die aktuelle Stromquelle an: extern oder Batterie.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootReason": "Zeigt die aktuellsten Gründe für einen Neustart und Herunterfahren an.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootMv": "Zeigt die Batteriespannung beim Start in Millivolt (mV) an.",
|
||||||
|
"repeater_cliHelpSensorGet": "Liest eine benutzerdefinierte Sensoreinstellung über eine Taste.",
|
||||||
|
"repeater_cliHelpSensorSet": "Erstellt eine benutzerdefinierte Sensoreinstellung.",
|
||||||
|
"repeater_cliHelpSensorList": "Zeigt alle benutzerdefinierten Sensoreinstellungen an, wobei die Seitennummerierung optional von einem Startindex abhängt.",
|
||||||
|
"repeater_cliHelpRegionDefault": "Zeigt den aktuellen Standard-Region-Bereich an.",
|
||||||
|
"repeater_cliHelpRegionDefaultSet": "Definiert den Standard-Regionenbereich. Verwenden Sie \"<null>\", um diesen zu löschen.",
|
||||||
|
"repeater_cliHelpRegionListAllowed": "Nennt die Regionen, die Überschwemmungsverkehr zulassen.",
|
||||||
|
"repeater_cliHelpRegionListDenied": "Auflistung von Regionen, die den Verkehr aufgrund von Überschwemmungen verbieten.",
|
||||||
|
"repeater_cliHelpStatsPackets": "(Nur für serielle Verbindungen) Zeigt Statistiken auf Paketebene.",
|
||||||
|
"repeater_cliHelpStatsRadio": "(Nur für Serien) Zeigt Radiostatistiken an.",
|
||||||
|
"repeater_cliHelpStatsCore": "(Nur für serielle Schnittstellen) Zeigt grundlegende Firmware-Statistiken.",
|
||||||
|
"common_done": "Done",
|
||||||
|
"background_serviceTitle": "MeshCore running",
|
||||||
|
"background_serviceText": "Keeping BLE connected",
|
||||||
|
"appSettings_translationModelDeleted": "Deleted {name}",
|
||||||
|
"@appSettings_translationModelDeleted": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
|
||||||
|
"@appSettings_translationModelDeleteFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channels_channelUpdateFailed": "Failed to update channel: {error}",
|
||||||
|
"@channels_channelUpdateFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map_type": "Type",
|
||||||
|
"map_path": "Path",
|
||||||
|
"map_location": "Location",
|
||||||
|
"map_estLocation": "Est. Location",
|
||||||
|
"map_publicKey": "Public Key",
|
||||||
|
"map_publicKeyPrefixHint": "e.g. ab12",
|
||||||
|
"contact_typeChat": "Chat",
|
||||||
|
"contact_typeRepeater": "Repeater",
|
||||||
|
"contact_typeRoom": "Room",
|
||||||
|
"contact_typeSensor": "Sensor",
|
||||||
|
"contact_typeUnknown": "Unknown",
|
||||||
|
"channels_via": "via {path}",
|
||||||
|
"chat_score": "Score",
|
||||||
|
"settings_multiAck": "Mehrere Bestätigungen",
|
||||||
|
"map_sharedAt": "Geteilt",
|
||||||
|
"@losBlockedSpotChip": {
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@losSelectedObstructionDetails": {
|
||||||
|
"placeholders": {
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromA": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromB": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"losBlockedSpotsTitle": "Reservierte Plätze",
|
||||||
|
"losSelectedObstructionTitle": "Ausgewählte Behinderung",
|
||||||
|
"losBlockedSpotChip": "{distance} • {distanceUnit} • {obstruction} {heightUnit}",
|
||||||
|
"losBlockedSpotsHint": "Klicken Sie auf einen blockierten Bereich, um ihn auf der Karte hervorzuheben.",
|
||||||
|
"losSelectedObstructionDetails": "Blockiert durch {obstruction} in einer Höhe von {heightUnit}, {distanceFromA} von A und {distanceFromB} von B ({distanceUnit}).",
|
||||||
|
"chat_markAsUnread": "Als nicht gelesen markieren",
|
||||||
|
"chat_newMessages": "Neue Nachrichten",
|
||||||
|
"settings_companionDebugLog": "Debug-Protokoll für die Begleitsoftware",
|
||||||
|
"settings_companionDebugLogSubtitle": "BLE/TCP/USB-Befehle, Antworten und Rohdaten",
|
||||||
|
"repeater_chanUtil": "Nutzung des Kanals",
|
||||||
|
"dialog_connectCompanion": "Verbinden Sie sich mit einem Companion, um auf die Funktionen des Repeaters und des Raumservers zuzugreifen.",
|
||||||
|
"dialog_disconnectedTitle": "Getrennt",
|
||||||
|
"dialog_disconnectedMessage": "Du wurdest von deinem Begleiter getrennt.",
|
||||||
|
"contact_connectCompanion": "Mit einem Companion verbinden, um auf Repeater- und Raumserver-Funktionen zuzugreifen."
|
||||||
}
|
}
|
||||||
|
|||||||
+491
-19
@@ -12,6 +12,7 @@
|
|||||||
"common_delete": "Delete",
|
"common_delete": "Delete",
|
||||||
"common_deleteAll": "Delete All",
|
"common_deleteAll": "Delete All",
|
||||||
"common_close": "Close",
|
"common_close": "Close",
|
||||||
|
"common_done": "Done",
|
||||||
"common_edit": "Edit",
|
"common_edit": "Edit",
|
||||||
"common_add": "Add",
|
"common_add": "Add",
|
||||||
"common_settings": "Settings",
|
"common_settings": "Settings",
|
||||||
@@ -49,6 +50,33 @@
|
|||||||
"scanner_title": "MeshCore Open",
|
"scanner_title": "MeshCore Open",
|
||||||
"connectionChoiceUsbLabel": "USB",
|
"connectionChoiceUsbLabel": "USB",
|
||||||
"connectionChoiceBluetoothLabel": "Bluetooth",
|
"connectionChoiceBluetoothLabel": "Bluetooth",
|
||||||
|
"connectionChoiceTcpLabel": "TCP",
|
||||||
|
"tcpScreenTitle": "Connect over TCP",
|
||||||
|
"tcpHostLabel": "Endpoint",
|
||||||
|
"tcpHostHint": "192.168.40.10 / example.com",
|
||||||
|
"tcpPortLabel": "Port",
|
||||||
|
"tcpPortHint": "5000",
|
||||||
|
"tcpStatus_notConnected": "Enter endpoint and connect",
|
||||||
|
"tcpStatus_connectingTo": "Connecting to {endpoint}...",
|
||||||
|
"@tcpStatus_connectingTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"endpoint": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tcpErrorHostRequired": "Host is required.",
|
||||||
|
"tcpErrorPortInvalid": "Port must be between 1 and 65535.",
|
||||||
|
"tcpErrorUnsupported": "TCP transport is not supported on this platform.",
|
||||||
|
"tcpErrorTimedOut": "TCP connection timed out.",
|
||||||
|
"tcpConnectionFailed": "TCP connection failed: {error}",
|
||||||
|
"@tcpConnectionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"usbScreenTitle": "Connect over USB",
|
"usbScreenTitle": "Connect over USB",
|
||||||
"usbScreenSubtitle": "Choose a detected serial device and connect directly to your MeshCore node.",
|
"usbScreenSubtitle": "Choose a detected serial device and connect directly to your MeshCore node.",
|
||||||
"usbScreenStatus": "Select a USB device",
|
"usbScreenStatus": "Select a USB device",
|
||||||
@@ -139,7 +167,22 @@
|
|||||||
"settings_privacyModeToggle": "Toggle privacy mode to hide your name and location in advertisements.",
|
"settings_privacyModeToggle": "Toggle privacy mode to hide your name and location in advertisements.",
|
||||||
"settings_privacyModeEnabled": "Privacy mode enabled",
|
"settings_privacyModeEnabled": "Privacy mode enabled",
|
||||||
"settings_privacyModeDisabled": "Privacy mode disabled",
|
"settings_privacyModeDisabled": "Privacy mode disabled",
|
||||||
|
"settings_privacy": "Privacy Settings",
|
||||||
|
"settings_privacySubtitle": "Control what information is shared.",
|
||||||
|
"settings_privacySettingsDescription": "Choose what information your device shares with others.",
|
||||||
|
"settings_denyAll": "Deny all",
|
||||||
|
"settings_allowByContact": "Allow by contact flags",
|
||||||
|
"settings_allowAll": "Allow all",
|
||||||
|
"settings_telemetryBaseMode": "Telemetry Base Mode",
|
||||||
|
"settings_telemetryLocationMode": "Telemetry Location Mode",
|
||||||
|
"settings_telemetryEnvironmentMode": "Telemetry Environment Mode",
|
||||||
|
"settings_advertLocation": "Advert Location",
|
||||||
|
"settings_advertLocationSubtitle": "Include location in advert.",
|
||||||
|
"settings_multiAck": "Multi-ACKs",
|
||||||
|
"settings_telemetryModeUpdated": "Telemetry mode updated",
|
||||||
"settings_actions": "Actions",
|
"settings_actions": "Actions",
|
||||||
|
"settings_deleteAllPaths": "Delete All Paths",
|
||||||
|
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
|
||||||
"settings_sendAdvertisement": "Send Advertisement",
|
"settings_sendAdvertisement": "Send Advertisement",
|
||||||
"settings_sendAdvertisementSubtitle": "Broadcast presence now",
|
"settings_sendAdvertisementSubtitle": "Broadcast presence now",
|
||||||
"settings_advertisementSent": "Advertisement sent",
|
"settings_advertisementSent": "Advertisement sent",
|
||||||
@@ -152,8 +195,8 @@
|
|||||||
"settings_rebootDeviceSubtitle": "Restart the MeshCore device",
|
"settings_rebootDeviceSubtitle": "Restart the MeshCore device",
|
||||||
"settings_rebootDeviceConfirm": "Are you sure you want to reboot the device? You will be disconnected.",
|
"settings_rebootDeviceConfirm": "Are you sure you want to reboot the device? You will be disconnected.",
|
||||||
"settings_debug": "Debug",
|
"settings_debug": "Debug",
|
||||||
"settings_bleDebugLog": "BLE Debug Log",
|
"settings_companionDebugLog": "Companion Debug Log",
|
||||||
"settings_bleDebugLogSubtitle": "BLE commands, responses, and raw data",
|
"settings_companionDebugLogSubtitle": "BLE/TCP/USB commands, responses, and raw data",
|
||||||
"settings_appDebugLog": "App Debug Log",
|
"settings_appDebugLog": "App Debug Log",
|
||||||
"settings_appDebugLogSubtitle": "Application debug messages",
|
"settings_appDebugLogSubtitle": "Application debug messages",
|
||||||
"settings_about": "About",
|
"settings_about": "About",
|
||||||
@@ -242,6 +285,27 @@
|
|||||||
"appSettings_autoRouteRotationSubtitle": "Cycle between best paths and flood mode",
|
"appSettings_autoRouteRotationSubtitle": "Cycle between best paths and flood mode",
|
||||||
"appSettings_autoRouteRotationEnabled": "Auto route rotation enabled",
|
"appSettings_autoRouteRotationEnabled": "Auto route rotation enabled",
|
||||||
"appSettings_autoRouteRotationDisabled": "Auto route rotation disabled",
|
"appSettings_autoRouteRotationDisabled": "Auto route rotation disabled",
|
||||||
|
"appSettings_maxRouteWeight": "Max Route Weight",
|
||||||
|
"appSettings_maxRouteWeightSubtitle": "Maximum weight a path can accumulate from successful deliveries",
|
||||||
|
"appSettings_initialRouteWeight": "Initial Route Weight",
|
||||||
|
"appSettings_initialRouteWeightSubtitle": "Starting weight for newly discovered paths",
|
||||||
|
"appSettings_routeWeightSuccessIncrement": "Success Weight Increment",
|
||||||
|
"appSettings_routeWeightSuccessIncrementSubtitle": "Weight added to a path after successful delivery",
|
||||||
|
"appSettings_routeWeightFailureDecrement": "Failure Weight Decrement",
|
||||||
|
"appSettings_routeWeightFailureDecrementSubtitle": "Weight removed from a path after failed delivery",
|
||||||
|
"appSettings_maxMessageRetries": "Max Message Retries",
|
||||||
|
"appSettings_maxMessageRetriesSubtitle": "Number of retry attempts before marking a message as failed",
|
||||||
|
"path_routeWeight": "{weight}/{max}",
|
||||||
|
"@path_routeWeight": {
|
||||||
|
"placeholders": {
|
||||||
|
"weight": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"appSettings_battery": "Battery",
|
"appSettings_battery": "Battery",
|
||||||
"appSettings_batteryChemistry": "Battery Chemistry",
|
"appSettings_batteryChemistry": "Battery Chemistry",
|
||||||
"appSettings_batteryChemistryPerDevice": "Set per device ({deviceName})",
|
"appSettings_batteryChemistryPerDevice": "Set per device ({deviceName})",
|
||||||
@@ -389,6 +453,7 @@
|
|||||||
"contacts_newGroup": "New Group",
|
"contacts_newGroup": "New Group",
|
||||||
"contacts_groupName": "Group name",
|
"contacts_groupName": "Group name",
|
||||||
"contacts_groupNameRequired": "Group name is required",
|
"contacts_groupNameRequired": "Group name is required",
|
||||||
|
"contacts_groupNameReserved": "This group name is reserved",
|
||||||
"contacts_groupAlreadyExists": "Group \"{name}\" already exists",
|
"contacts_groupAlreadyExists": "Group \"{name}\" already exists",
|
||||||
"@contacts_groupAlreadyExists": {
|
"@contacts_groupAlreadyExists": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -427,6 +492,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"contact_info": "Contact Info",
|
||||||
|
"contact_settings": "Contact Settings",
|
||||||
|
"contact_telemetry": "Telemetry",
|
||||||
|
"contact_lastSeen": "Last seen",
|
||||||
|
"contact_clearChat": "Clear Chat",
|
||||||
|
"contact_teleBase": "Telemetry Base",
|
||||||
|
"contact_teleBaseSubtitle": "Allow sharing battery level and basic telemetry",
|
||||||
|
"contact_teleLoc": "Telemetry Location",
|
||||||
|
"contact_teleLocSubtitle": "Allow sharing location data",
|
||||||
|
"contact_teleEnv": "Telemetry Environment",
|
||||||
|
"contact_teleEnvSubtitle": "Allow sharing environment sensor data",
|
||||||
"channels_title": "Channels",
|
"channels_title": "Channels",
|
||||||
"channels_noChannelsConfigured": "No channels configured",
|
"channels_noChannelsConfigured": "No channels configured",
|
||||||
"channels_addPublicChannel": "Add Public Channel",
|
"channels_addPublicChannel": "Add Public Channel",
|
||||||
@@ -440,11 +516,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_hashtagChannel": "Hashtag channel",
|
|
||||||
"channels_public": "Public",
|
"channels_public": "Public",
|
||||||
|
"channels_via": "via {path}",
|
||||||
|
"@channels_via": {
|
||||||
|
"placeholders": {
|
||||||
|
"path": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"channels_private": "Private",
|
"channels_private": "Private",
|
||||||
"channels_publicChannel": "Public channel",
|
|
||||||
"channels_privateChannel": "Private channel",
|
|
||||||
"channels_editChannel": "Edit channel",
|
"channels_editChannel": "Edit channel",
|
||||||
"channels_muteChannel": "Mute channel",
|
"channels_muteChannel": "Mute channel",
|
||||||
"channels_unmuteChannel": "Unmute channel",
|
"channels_unmuteChannel": "Unmute channel",
|
||||||
@@ -499,6 +580,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_smazCompression": "SMAZ compression",
|
"channels_smazCompression": "SMAZ compression",
|
||||||
|
"channels_cyr2latCompression": "Cyr2Lat compression",
|
||||||
|
"channels_cyr2latCompressionDscr": "Replaces some Cyrillic characters with Latin characters when sending.",
|
||||||
|
"channels_cyr2latSettingsHeading": "Cyr2Lat Setup",
|
||||||
|
"channels_cyr2latSettingsSubheading": "List of replacements",
|
||||||
|
"channels_cyr2latSettingsDscr": "Edit the JSON configuration of character replacement",
|
||||||
|
"channels_cyr2latSettingsDialogHint": "JSON replacement map",
|
||||||
|
"channels_cyr2latSettingsDialogWrongJSON": "Invalid JSON: {error}",
|
||||||
|
"@channels_cyr2latSettingsDialogWrongJSON": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
"channels_channelUpdated": "Channel \"{name}\" updated",
|
"channels_channelUpdated": "Channel \"{name}\" updated",
|
||||||
"@channels_channelUpdated": {
|
"@channels_channelUpdated": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -507,6 +600,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"settings_cyr2latProfileAdd": "Add Cyr2Lat Profile",
|
||||||
|
"settings_cyr2latProfileName": "Profile Name",
|
||||||
|
"settings_cyr2latProfileNameEmpty": "Profile name cannot be empty",
|
||||||
|
"settings_cyr2latProfileAdded": "Profile added successfully",
|
||||||
|
"settings_cyr2latProfileUpdated": "Profile updated successfully",
|
||||||
|
"settings_cyr2latProfileEdit": "Edit Cyr2Lat Profile",
|
||||||
|
"settings_cyr2latProfileDelete": "Delete Cyr2Lat Profile",
|
||||||
|
"settings_cyr2latProfileDeleted": "Profile deleted successfully",
|
||||||
|
"settings_cyr2latProfileDeleteDscr": "Are you sure you want to delete the profile \"{name}\"?",
|
||||||
|
"@settings_cyr2latProfileDeleteDscr": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"channels_publicChannelAdded": "Public channel added",
|
"channels_publicChannelAdded": "Public channel added",
|
||||||
"channels_sortBy": "Sort by",
|
"channels_sortBy": "Sort by",
|
||||||
"channels_sortManual": "Manual",
|
"channels_sortManual": "Manual",
|
||||||
@@ -526,6 +635,15 @@
|
|||||||
"channels_enterHashtag": "Enter hashtag",
|
"channels_enterHashtag": "Enter hashtag",
|
||||||
"channels_hashtagHint": "e.g. #team",
|
"channels_hashtagHint": "e.g. #team",
|
||||||
"chat_noMessages": "No messages yet",
|
"chat_noMessages": "No messages yet",
|
||||||
|
"chat_sendMessage": "Send message",
|
||||||
|
"chat_sendMessageTo": "Send a message to {contactName}",
|
||||||
|
"@chat_sendMessageTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"contactName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"chat_sendMessageToStart": "Send a message to get started",
|
"chat_sendMessageToStart": "Send a message to get started",
|
||||||
"chat_originalMessageNotFound": "Original message not found",
|
"chat_originalMessageNotFound": "Original message not found",
|
||||||
"chat_replyingTo": "Replying to {name}",
|
"chat_replyingTo": "Replying to {name}",
|
||||||
@@ -545,14 +663,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chat_location": "Location",
|
"chat_location": "Location",
|
||||||
"chat_sendMessageTo": "Send a message to {contactName}",
|
|
||||||
"@chat_sendMessageTo": {
|
|
||||||
"placeholders": {
|
|
||||||
"contactName": {
|
|
||||||
"type": "String"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"chat_typeMessage": "Type a message...",
|
"chat_typeMessage": "Type a message...",
|
||||||
"chat_messageTooLong": "Message too long (max {maxBytes} bytes).",
|
"chat_messageTooLong": "Message too long (max {maxBytes} bytes).",
|
||||||
"@chat_messageTooLong": {
|
"@chat_messageTooLong": {
|
||||||
@@ -683,6 +793,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chat_successes": "successes",
|
"chat_successes": "successes",
|
||||||
|
"chat_score": "Score",
|
||||||
"chat_removePath": "Remove path",
|
"chat_removePath": "Remove path",
|
||||||
"chat_noPathHistoryYet": "No path history yet.\nSend a message to discover paths.",
|
"chat_noPathHistoryYet": "No path history yet.\nSend a message to discover paths.",
|
||||||
"chat_pathActions": "Path Actions:",
|
"chat_pathActions": "Path Actions:",
|
||||||
@@ -734,6 +845,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"chat_markAsUnread": "Mark as Unread",
|
||||||
|
"chat_newMessages": "New messages",
|
||||||
"chat_openLink": "Open Link?",
|
"chat_openLink": "Open Link?",
|
||||||
"chat_openLinkConfirmation": "Do you want to open this link in your browser?",
|
"chat_openLinkConfirmation": "Do you want to open this link in your browser?",
|
||||||
"chat_open": "Open",
|
"chat_open": "Open",
|
||||||
@@ -779,7 +892,14 @@
|
|||||||
"map_from": "From",
|
"map_from": "From",
|
||||||
"map_source": "Source",
|
"map_source": "Source",
|
||||||
"map_flags": "Flags",
|
"map_flags": "Flags",
|
||||||
|
"map_type": "Type",
|
||||||
|
"map_path": "Path",
|
||||||
|
"map_location": "Location",
|
||||||
|
"map_estLocation": "Est. Location",
|
||||||
|
"map_publicKey": "Public Key",
|
||||||
|
"map_publicKeyPrefixHint": "e.g. ab12",
|
||||||
"map_shareMarkerHere": "Share marker here",
|
"map_shareMarkerHere": "Share marker here",
|
||||||
|
"map_setAsMyLocation": "Set as my location",
|
||||||
"map_pinLabel": "Pin label",
|
"map_pinLabel": "Pin label",
|
||||||
"map_label": "Label",
|
"map_label": "Label",
|
||||||
"map_pointOfInterest": "Point of interest",
|
"map_pointOfInterest": "Point of interest",
|
||||||
@@ -801,19 +921,23 @@
|
|||||||
"map_chatNodes": "Chat Nodes",
|
"map_chatNodes": "Chat Nodes",
|
||||||
"map_repeaters": "Repeaters",
|
"map_repeaters": "Repeaters",
|
||||||
"map_otherNodes": "Other Nodes",
|
"map_otherNodes": "Other Nodes",
|
||||||
|
"map_showOverlaps": "Repeater Key Overlaps",
|
||||||
"map_keyPrefix": "Key Prefix",
|
"map_keyPrefix": "Key Prefix",
|
||||||
"map_filterByKeyPrefix": "Filter by key prefix",
|
"map_filterByKeyPrefix": "Filter by key prefix",
|
||||||
"map_publicKeyPrefix": "Public key prefix",
|
"map_publicKeyPrefix": "Public key prefix",
|
||||||
"map_markers": "Markers",
|
"map_markers": "Markers",
|
||||||
"map_showSharedMarkers": "Show shared markers",
|
"map_showSharedMarkers": "Show shared markers",
|
||||||
"map_showGuessedLocations": "Show guessed node locations",
|
"map_showGuessedLocations": "Show guessed node locations",
|
||||||
|
"map_showDiscoveryContacts": "Show Discovery Contacts",
|
||||||
"map_guessedLocation": "Guessed location",
|
"map_guessedLocation": "Guessed location",
|
||||||
"map_lastSeenTime": "Last Seen Time",
|
"map_lastSeenTime": "Last Seen Time",
|
||||||
"map_sharedPin": "Shared pin",
|
"map_sharedPin": "Shared pin",
|
||||||
|
"map_sharedAt": "Shared",
|
||||||
"map_joinRoom": "Join Room",
|
"map_joinRoom": "Join Room",
|
||||||
"map_manageRepeater": "Manage Repeater",
|
"map_manageRepeater": "Manage Repeater",
|
||||||
"map_tapToAdd": "Tap on nodes to add them to the path.",
|
"map_tapToAdd": "Tap on nodes to add them to the path.",
|
||||||
"map_runTrace": "Run Path Trace",
|
"map_runTrace": "Run path trace",
|
||||||
|
"map_runTraceWithReturnPath": "Return back on the same path.",
|
||||||
"map_removeLast": "Remove Last",
|
"map_removeLast": "Remove Last",
|
||||||
"map_pathTraceCancelled": "Path trace cancelled.",
|
"map_pathTraceCancelled": "Path trace cancelled.",
|
||||||
"mapCache_title": "Offline Map Cache",
|
"mapCache_title": "Offline Map Cache",
|
||||||
@@ -938,14 +1062,17 @@
|
|||||||
"time_allTime": "All Time",
|
"time_allTime": "All Time",
|
||||||
"dialog_disconnect": "Disconnect",
|
"dialog_disconnect": "Disconnect",
|
||||||
"dialog_disconnectConfirm": "Are you sure you want to disconnect from this device?",
|
"dialog_disconnectConfirm": "Are you sure you want to disconnect from this device?",
|
||||||
|
"dialog_disconnectedTitle": "Disconnected",
|
||||||
|
"dialog_disconnectedMessage": "You have been disconnected from your companion.",
|
||||||
|
"dialog_connectCompanion": "Connect to a companion to access repeater and room server features.",
|
||||||
"login_repeaterLogin": "Repeater Login",
|
"login_repeaterLogin": "Repeater Login",
|
||||||
"login_roomLogin": "Room Server Login",
|
"login_roomLogin": "Room Server Login",
|
||||||
"login_password": "Password",
|
"login_password": "Password",
|
||||||
"login_enterPassword": "Enter password",
|
"login_enterPassword": "Enter password",
|
||||||
"login_savePassword": "Save password",
|
"login_savePassword": "Save password",
|
||||||
"login_savePasswordSubtitle": "Password will be stored securely on this device",
|
"login_savePasswordSubtitle": "Password will be stored securely on this device",
|
||||||
"login_repeaterDescription": "Enter the repeater password to access settings and status.",
|
"login_repeaterDescription": "Enter the repeater password for guest or admin access.",
|
||||||
"login_roomDescription": "Enter the room password to access settings and status.",
|
"login_roomDescription": "Enter the room password for guest or admin access.",
|
||||||
"login_routing": "Routing",
|
"login_routing": "Routing",
|
||||||
"login_routingMode": "Routing mode",
|
"login_routingMode": "Routing mode",
|
||||||
"login_autoUseSavedPath": "Auto (use saved path)",
|
"login_autoUseSavedPath": "Auto (use saved path)",
|
||||||
@@ -1011,7 +1138,10 @@
|
|||||||
"path_setPath": "Set Path",
|
"path_setPath": "Set Path",
|
||||||
"repeater_management": "Repeater Management",
|
"repeater_management": "Repeater Management",
|
||||||
"room_management": "Room Server Management",
|
"room_management": "Room Server Management",
|
||||||
|
"repeater_guest": "Repeater Information",
|
||||||
|
"room_guest": "Room Server Information",
|
||||||
"repeater_managementTools": "Management Tools",
|
"repeater_managementTools": "Management Tools",
|
||||||
|
"repeater_guestTools": "Guest Tools",
|
||||||
"repeater_status": "Status",
|
"repeater_status": "Status",
|
||||||
"repeater_statusSubtitle": "View repeater status, stats, and neighbors",
|
"repeater_statusSubtitle": "View repeater status, stats, and neighbors",
|
||||||
"repeater_telemetry": "Telemetry",
|
"repeater_telemetry": "Telemetry",
|
||||||
@@ -1022,6 +1152,14 @@
|
|||||||
"repeater_neighborsSubtitle": "View zero hop neighbors.",
|
"repeater_neighborsSubtitle": "View zero hop neighbors.",
|
||||||
"repeater_settings": "Settings",
|
"repeater_settings": "Settings",
|
||||||
"repeater_settingsSubtitle": "Configure repeater parameters",
|
"repeater_settingsSubtitle": "Configure repeater parameters",
|
||||||
|
"repeater_clockSyncAfterLogin": "Clock sync after login",
|
||||||
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Automatically send \"clock sync\" after a successful login",
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
"repeater_statusTitle": "Repeater Status",
|
"repeater_statusTitle": "Repeater Status",
|
||||||
"repeater_routingMode": "Routing mode",
|
"repeater_routingMode": "Routing mode",
|
||||||
"repeater_autoUseSavedPath": "Auto (use saved path)",
|
"repeater_autoUseSavedPath": "Auto (use saved path)",
|
||||||
@@ -1049,6 +1187,7 @@
|
|||||||
"repeater_noiseFloor": "Noise Floor",
|
"repeater_noiseFloor": "Noise Floor",
|
||||||
"repeater_txAirtime": "TX Airtime",
|
"repeater_txAirtime": "TX Airtime",
|
||||||
"repeater_rxAirtime": "RX Airtime",
|
"repeater_rxAirtime": "RX Airtime",
|
||||||
|
"repeater_chanUtil": "Channel Utilization",
|
||||||
"repeater_packetStatistics": "Packet Statistics",
|
"repeater_packetStatistics": "Packet Statistics",
|
||||||
"repeater_sent": "Sent",
|
"repeater_sent": "Sent",
|
||||||
"repeater_received": "Received",
|
"repeater_received": "Received",
|
||||||
@@ -1194,6 +1333,81 @@
|
|||||||
},
|
},
|
||||||
"repeater_confirm": "Confirm",
|
"repeater_confirm": "Confirm",
|
||||||
"repeater_settingsSaved": "Settings saved successfully",
|
"repeater_settingsSaved": "Settings saved successfully",
|
||||||
|
"repeater_rxGain": "Boosted RX gain",
|
||||||
|
"repeater_rxGainHelper": "Higher sensitivity, more current draw (SX1262/SX1268 only)",
|
||||||
|
"repeater_refreshRxGain": "Refresh boosted RX gain",
|
||||||
|
"repeater_multiAcks": "Multi-ACKs",
|
||||||
|
"repeater_multiAcksSubtitle": "Acknowledge messages over multiple paths for better delivery",
|
||||||
|
"repeater_refreshMultiAcks": "Refresh multi-ACKs",
|
||||||
|
"repeater_networkHealth": "Network health",
|
||||||
|
"repeater_loopDetect": "Loop detection",
|
||||||
|
"repeater_loopDetectHelper": "Drop flood packets that look like routing loops",
|
||||||
|
"repeater_loopDetectOff": "Off",
|
||||||
|
"repeater_loopDetectMinimal": "Minimal",
|
||||||
|
"repeater_loopDetectModerate": "Moderate",
|
||||||
|
"repeater_loopDetectStrict": "Strict",
|
||||||
|
"repeater_dutyCycle": "Duty cycle",
|
||||||
|
"repeater_dutyCycleHelper": "Maximum percentage of airtime",
|
||||||
|
"repeater_dutyCyclePercent": "{percent}%",
|
||||||
|
"@repeater_dutyCyclePercent": {
|
||||||
|
"placeholders": {
|
||||||
|
"percent": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_ownerInfo": "Operator info",
|
||||||
|
"repeater_ownerInfoHelper": "Public metadata for this repeater",
|
||||||
|
"repeater_refreshOwnerInfo": "Refresh operator info",
|
||||||
|
"repeater_floodMax": "Flood max hops",
|
||||||
|
"repeater_floodMaxHelper": "Maximum hops a flood packet may travel (0-64)",
|
||||||
|
"repeater_advancedSettings": "Advanced",
|
||||||
|
"repeater_advancedSettingsSubtitle": "Tuning knobs for experienced operators",
|
||||||
|
"repeater_pathHashMode": "Path hash mode",
|
||||||
|
"repeater_pathHashModeHelper": "Bytes used to encode this repeater's ID in flood path/loop-detect tags. 0=1 byte (256 IDs, up to 64 hops), 1=2 bytes (65K IDs, up to 32 hops), 2=3 bytes (16M IDs, up to 21 hops). v1.13 and older firmware drops multi-byte paths — only raise once your network is on v1.14+.",
|
||||||
|
"repeater_txDelay": "Flood TX delay",
|
||||||
|
"repeater_txDelayHelper": "Retransmit spacing for flood traffic, as a multiplier of the packet's airtime (0-2, default 0.5). Higher = fewer collisions but slower delivery.",
|
||||||
|
"repeater_directTxDelay": "Direct TX delay",
|
||||||
|
"repeater_directTxDelayHelper": "Retransmit spacing for direct (non-flood) traffic, as a multiplier of the packet's airtime (0-2, default 0.3).",
|
||||||
|
"repeater_intThresh": "Interference threshold",
|
||||||
|
"repeater_intThreshHelper": "Threshold passed to the radio's noise-floor calibration so it rejects interference above this level. 0 disables — only raise if you see RX errors in a noisy band.",
|
||||||
|
"repeater_agcResetInterval": "AGC reset interval",
|
||||||
|
"repeater_agcResetIntervalHelper": "How often to reset the radio's automatic gain control to recover from a stuck gain state. Seconds, snapped down to a multiple of 4. 0 disables periodic resets.",
|
||||||
|
"repeater_actionsTitle": "Actions",
|
||||||
|
"repeater_sendAdvert": "Send flood advert",
|
||||||
|
"repeater_sendAdvertSubtitle": "Broadcast a flood advert through the network",
|
||||||
|
"repeater_sendAdvertZeroHop": "Send zero-hop advert",
|
||||||
|
"repeater_sendAdvertZeroHopSubtitle": "Broadcast a one-hop advert (no relays)",
|
||||||
|
"repeater_clockSync": "Sync clock now",
|
||||||
|
"repeater_clockSyncSubtitle": "Push your phone's time to the repeater",
|
||||||
|
"repeater_actionSucceeded": "{action} succeeded",
|
||||||
|
"@repeater_actionSucceeded": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_actionFailed": "{action} failed: {error}",
|
||||||
|
"@repeater_actionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_settingsSavedRebootNeeded": "Settings saved — reboot the repeater to apply",
|
||||||
|
"repeater_settingsPartialFailure": "Some settings failed: {failures}",
|
||||||
|
"@repeater_settingsPartialFailure": {
|
||||||
|
"placeholders": {
|
||||||
|
"failures": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repeater_errorSavingSettings": "Error saving settings: {error}",
|
"repeater_errorSavingSettings": "Error saving settings: {error}",
|
||||||
"@repeater_errorSavingSettings": {
|
"@repeater_errorSavingSettings": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1205,11 +1419,9 @@
|
|||||||
"repeater_refreshBasicSettings": "Refresh Basic Settings",
|
"repeater_refreshBasicSettings": "Refresh Basic Settings",
|
||||||
"repeater_refreshRadioSettings": "Refresh Radio Settings",
|
"repeater_refreshRadioSettings": "Refresh Radio Settings",
|
||||||
"repeater_refreshTxPower": "Refresh TX power",
|
"repeater_refreshTxPower": "Refresh TX power",
|
||||||
"repeater_refreshLocationSettings": "Refresh Location Settings",
|
|
||||||
"repeater_refreshPacketForwarding": "Refresh Packet Forwarding",
|
"repeater_refreshPacketForwarding": "Refresh Packet Forwarding",
|
||||||
"repeater_refreshGuestAccess": "Refresh Guest Access",
|
"repeater_refreshGuestAccess": "Refresh Guest Access",
|
||||||
"repeater_refreshPrivacyMode": "Refresh Privacy Mode",
|
"repeater_refreshPrivacyMode": "Refresh Privacy Mode",
|
||||||
"repeater_refreshAdvertisementSettings": "Refresh Advertisement Settings",
|
|
||||||
"repeater_refreshed": "{label} refreshed",
|
"repeater_refreshed": "{label} refreshed",
|
||||||
"@repeater_refreshed": {
|
"@repeater_refreshed": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1252,6 +1464,8 @@
|
|||||||
"repeater_cliQuickVersion": "Version",
|
"repeater_cliQuickVersion": "Version",
|
||||||
"repeater_cliQuickAdvertise": "Advertise",
|
"repeater_cliQuickAdvertise": "Advertise",
|
||||||
"repeater_cliQuickClock": "Clock",
|
"repeater_cliQuickClock": "Clock",
|
||||||
|
"repeater_cliQuickClockSync": "Clock Sync",
|
||||||
|
"repeater_cliQuickDiscovery": "Discover Neighbors",
|
||||||
"repeater_cliHelpAdvert": "Sends an advertisement packet",
|
"repeater_cliHelpAdvert": "Sends an advertisement packet",
|
||||||
"repeater_cliHelpReboot": "Reboots the device. (note, you'll prob get 'Timeout' which is normal)",
|
"repeater_cliHelpReboot": "Reboots the device. (note, you'll prob get 'Timeout' which is normal)",
|
||||||
"repeater_cliHelpClock": "Displays current time per device's clock.",
|
"repeater_cliHelpClock": "Displays current time per device's clock.",
|
||||||
@@ -1317,6 +1531,77 @@
|
|||||||
"repeater_regionNote": "Region commands have been introduced to manage region definitions and permissions.",
|
"repeater_regionNote": "Region commands have been introduced to manage region definitions and permissions.",
|
||||||
"repeater_gpsManagement": "GPS Management",
|
"repeater_gpsManagement": "GPS Management",
|
||||||
"repeater_gpsNote": "gps command has been introduced to manage location related topics.",
|
"repeater_gpsNote": "gps command has been introduced to manage location related topics.",
|
||||||
|
"repeater_getCategory": "Get Values",
|
||||||
|
"repeater_powerMgmt": "Power Management",
|
||||||
|
"repeater_sensors": "Sensors",
|
||||||
|
"repeater_cliHelpPowerOff": "Powers the device off. (no response expected)",
|
||||||
|
"repeater_cliHelpClkReboot": "Resets the clock to a known epoch and reboots the device.",
|
||||||
|
"repeater_cliHelpAdvertZeroHop": "Sends a zero-hop advertisement (immediate neighbors only).",
|
||||||
|
"repeater_cliHelpStartOta": "Starts an over-the-air firmware update on supported boards.",
|
||||||
|
"repeater_cliHelpTime": "Sets the device clock to the given Unix epoch seconds. Clock cannot move backwards.",
|
||||||
|
"repeater_cliHelpBoard": "Shows the board manufacturer / hardware identifier.",
|
||||||
|
"repeater_cliHelpDiscoverNeighbors": "Sends a node-discovery request to nearby neighbors. (Repeater only)",
|
||||||
|
"repeater_cliHelpPowersaving": "Shows whether powersaving mode is on or off.",
|
||||||
|
"repeater_cliHelpPowersavingOnOff": "Enables or disables powersaving mode (where supported).",
|
||||||
|
"repeater_cliHelpErase": "(Serial only) Formats the device file system. Wipes all settings and contacts.",
|
||||||
|
"repeater_cliHelpSetDutyCycle": "Sets the maximum allowed transmit duty cycle as a percentage (1-100). Internally adjusts the airtime factor.",
|
||||||
|
"repeater_cliHelpSetPrvKey": "(Serial only) Replaces the device identity private key. Reboot required to apply. Generates a new public key.",
|
||||||
|
"repeater_cliHelpSetRadioRxGain": "(SX126x only) Toggles boosted RX gain for improved sensitivity at higher current draw.",
|
||||||
|
"repeater_cliHelpSetOwnerInfo": "Sets the owner contact info string included in adverts. Use '|' for newlines.",
|
||||||
|
"repeater_cliHelpSetPathHashMode": "Sets the path-hash mode. 0 = legacy, 1 = standard, 2 = strict. Affects how routing paths are matched.",
|
||||||
|
"repeater_cliHelpSetLoopDetect": "Sets the routing loop-detection sensitivity: off, minimal, moderate, or strict.",
|
||||||
|
"repeater_cliHelpSetFreq": "(Serial only) Quickly sets just the frequency. Reboot required. Prefer \"set radio\" for full radio params.",
|
||||||
|
"repeater_cliHelpSetBridgeChannel": "(ESPNow bridge only) Sets the WiFi channel (1-14) used by the bridge.",
|
||||||
|
"repeater_cliHelpGetName": "Shows the configured node name.",
|
||||||
|
"repeater_cliHelpGetRole": "Shows the firmware role (Repeater, Room Server, etc.).",
|
||||||
|
"repeater_cliHelpGetPublicKey": "Shows the device public key.",
|
||||||
|
"repeater_cliHelpGetPrvKey": "(Serial only) Shows the device private key. Treat as a secret.",
|
||||||
|
"repeater_cliHelpGetRepeat": "Shows whether packet forwarding (repeater role) is on or off.",
|
||||||
|
"repeater_cliHelpGetTx": "Shows the current TX power in dBm.",
|
||||||
|
"repeater_cliHelpGetFreq": "Shows the configured radio frequency in MHz.",
|
||||||
|
"repeater_cliHelpGetRadio": "Shows full radio params: freq, bandwidth, spreading factor, coding rate.",
|
||||||
|
"repeater_cliHelpGetRadioRxGain": "(SX126x only) Shows the RX boosted gain state.",
|
||||||
|
"repeater_cliHelpGetAf": "Shows the current airtime factor.",
|
||||||
|
"repeater_cliHelpGetDutyCycle": "Shows the current allowed duty cycle as a percentage.",
|
||||||
|
"repeater_cliHelpGetIntThresh": "Shows the channel interference threshold in dB.",
|
||||||
|
"repeater_cliHelpGetAgcResetInterval": "Shows the AGC reset interval in seconds.",
|
||||||
|
"repeater_cliHelpGetMultiAcks": "Shows whether double-ACK mode is on (1) or off (0).",
|
||||||
|
"repeater_cliHelpGetAllowReadOnly": "Shows whether guest read-only access is allowed.",
|
||||||
|
"repeater_cliHelpGetAdvertInterval": "Shows the local advertisement interval in minutes.",
|
||||||
|
"repeater_cliHelpGetFloodAdvertInterval": "Shows the flood advertisement interval in hours.",
|
||||||
|
"repeater_cliHelpGetGuestPassword": "Shows the configured guest password.",
|
||||||
|
"repeater_cliHelpGetLat": "Shows the configured latitude.",
|
||||||
|
"repeater_cliHelpGetLon": "Shows the configured longitude.",
|
||||||
|
"repeater_cliHelpGetRxDelay": "Shows the rxdelay base value.",
|
||||||
|
"repeater_cliHelpGetTxDelay": "Shows the flood-mode txdelay factor.",
|
||||||
|
"repeater_cliHelpGetDirectTxDelay": "Shows the direct-mode txdelay factor.",
|
||||||
|
"repeater_cliHelpGetFloodMax": "Shows the maximum flood hop count.",
|
||||||
|
"repeater_cliHelpGetOwnerInfo": "Shows the owner contact info string.",
|
||||||
|
"repeater_cliHelpGetPathHashMode": "Shows the path-hash mode (0/1/2).",
|
||||||
|
"repeater_cliHelpGetLoopDetect": "Shows the loop-detection sensitivity.",
|
||||||
|
"repeater_cliHelpGetAcl": "(Serial only) Lists the access-control entries on a repeater.",
|
||||||
|
"repeater_cliHelpGetBridgeEnabled": "Shows whether the bridge is enabled.",
|
||||||
|
"repeater_cliHelpGetBridgeDelay": "Shows the bridge delay in ms.",
|
||||||
|
"repeater_cliHelpGetBridgeSource": "Shows whether the bridge logs RX or TX packets.",
|
||||||
|
"repeater_cliHelpGetBridgeBaud": "(RS232 bridge only) Shows the bridge baud rate.",
|
||||||
|
"repeater_cliHelpGetBridgeChannel": "(ESPNow bridge only) Shows the bridge WiFi channel.",
|
||||||
|
"repeater_cliHelpGetBridgeSecret": "(ESPNow bridge only) Shows the bridge shared secret.",
|
||||||
|
"repeater_cliHelpGetBootloaderVer": "(NRF52 only) Shows the bootloader version.",
|
||||||
|
"repeater_cliHelpGetAdcMultiplier": "Shows the ADC multiplier (battery-voltage scaling).",
|
||||||
|
"repeater_cliHelpGetPwrMgtSupport": "Reports whether the board has power-management support.",
|
||||||
|
"repeater_cliHelpGetPwrMgtSource": "Shows the current power source: external or battery.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootReason": "Shows the most recent reset and shutdown reasons.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootMv": "Shows the boot-time battery voltage in mV.",
|
||||||
|
"repeater_cliHelpSensorGet": "Reads a custom sensor setting by key.",
|
||||||
|
"repeater_cliHelpSensorSet": "Writes a custom sensor setting.",
|
||||||
|
"repeater_cliHelpSensorList": "Lists all custom sensor settings, paginated from optional start index.",
|
||||||
|
"repeater_cliHelpRegionDefault": "Shows the current default region scope.",
|
||||||
|
"repeater_cliHelpRegionDefaultSet": "Sets the default region scope. Use \"<null>\" to clear.",
|
||||||
|
"repeater_cliHelpRegionListAllowed": "Lists regions that allow flood traffic.",
|
||||||
|
"repeater_cliHelpRegionListDenied": "Lists regions that deny flood traffic.",
|
||||||
|
"repeater_cliHelpStatsPackets": "(Serial only) Shows packet-level statistics.",
|
||||||
|
"repeater_cliHelpStatsRadio": "(Serial only) Shows radio statistics.",
|
||||||
|
"repeater_cliHelpStatsCore": "(Serial only) Shows core firmware statistics.",
|
||||||
"telemetry_receivedData": "Received Telemetry Data",
|
"telemetry_receivedData": "Received Telemetry Data",
|
||||||
"telemetry_requestTimeout": "Telemetry request timed out.",
|
"telemetry_requestTimeout": "Telemetry request timed out.",
|
||||||
"telemetry_errorLoading": "Error loading telemetry: {error}",
|
"telemetry_errorLoading": "Error loading telemetry: {error}",
|
||||||
@@ -1775,6 +2060,46 @@
|
|||||||
"losLegendRadioHorizon": "Radio horizon",
|
"losLegendRadioHorizon": "Radio horizon",
|
||||||
"losLegendLosBeam": "LOS beam",
|
"losLegendLosBeam": "LOS beam",
|
||||||
"losLegendTerrain": "Terrain",
|
"losLegendTerrain": "Terrain",
|
||||||
|
"losBlockedSpotsTitle": "Blocked spots",
|
||||||
|
"losBlockedSpotsHint": "Tap a blocked spot to highlight it on the map.",
|
||||||
|
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
|
||||||
|
"@losBlockedSpotChip": {
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"losSelectedObstructionTitle": "Selected obstruction",
|
||||||
|
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).",
|
||||||
|
"@losSelectedObstructionDetails": {
|
||||||
|
"placeholders": {
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromA": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromB": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"losFrequencyLabel": "Frequency",
|
"losFrequencyLabel": "Frequency",
|
||||||
"losFrequencyInfoTooltip": "View calculation details",
|
"losFrequencyInfoTooltip": "View calculation details",
|
||||||
"losFrequencyDialogTitle": "Radio horizon calculation",
|
"losFrequencyDialogTitle": "Radio horizon calculation",
|
||||||
@@ -1897,5 +2222,152 @@
|
|||||||
"discoveredContacts_copyContact": "Copy Contact to clipboard",
|
"discoveredContacts_copyContact": "Copy Contact to clipboard",
|
||||||
"discoveredContacts_deleteContact": "Delete Discovered Contact",
|
"discoveredContacts_deleteContact": "Delete Discovered Contact",
|
||||||
"discoveredContacts_deleteContactAll": "Delete All Discovered Contacts",
|
"discoveredContacts_deleteContactAll": "Delete All Discovered Contacts",
|
||||||
"discoveredContacts_deleteContactAllContent": "Are you sure you want to delete all discovered contacts?"
|
"discoveredContacts_deleteContactAllContent": "Are you sure you want to delete all discovered contacts?",
|
||||||
|
"chat_sendCooldown": "Please wait a moment before sending again.",
|
||||||
|
"appSettings_jumpToOldestUnread": "Jump to oldest unread",
|
||||||
|
"appSettings_jumpToOldestUnreadSubtitle": "When opening a chat with unread messages, scroll to the first unread instead of the latest.",
|
||||||
|
"appSettings_languageHu": "Hungarian",
|
||||||
|
"appSettings_languageJa": "Japanese",
|
||||||
|
"appSettings_languageKo": "Korean",
|
||||||
|
"radioStats_tooltip": "Radio & mesh stats",
|
||||||
|
"radioStats_screenTitle": "Radio stats",
|
||||||
|
"radioStats_notConnected": "Connect to a device to view radio statistics.",
|
||||||
|
"radioStats_firmwareTooOld": "Radio statistics require companion firmware v8 or newer.",
|
||||||
|
"radioStats_waiting": "Waiting for data…",
|
||||||
|
"radioStats_noiseFloor": "Noise floor: {noiseDbm} dBm",
|
||||||
|
"@radioStats_noiseFloor": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"radioStats_lastRssi": "Last RSSI: {rssiDbm} dBm",
|
||||||
|
"@radioStats_lastRssi": {
|
||||||
|
"placeholders": {
|
||||||
|
"rssiDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"radioStats_lastSnr": "Last SNR: {snr} dB",
|
||||||
|
"@radioStats_lastSnr": {
|
||||||
|
"placeholders": {
|
||||||
|
"snr": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"radioStats_txAir": "TX airtime (total): {seconds} s",
|
||||||
|
"@radioStats_txAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"radioStats_rxAir": "RX airtime (total): {seconds} s",
|
||||||
|
"@radioStats_rxAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"radioStats_chartCaption": "Noise floor (dBm) over recent samples.",
|
||||||
|
"radioStats_stripNoise": "Noise floor: {noiseDbm} dBm",
|
||||||
|
"@radioStats_stripNoise": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"radioStats_stripWaiting": "Fetching radio stats…",
|
||||||
|
"radioStats_settingsTile": "Radio stats",
|
||||||
|
"radioStats_settingsSubtitle": "Noise floor, RSSI, SNR, and airtime",
|
||||||
|
"translation_title": "Translation",
|
||||||
|
"translation_enableTitle": "Enable translation",
|
||||||
|
"translation_enableSubtitle": "Translate incoming messages and allow pre-send translation.",
|
||||||
|
"translation_composerTitle": "Translate before sending",
|
||||||
|
"translation_composerSubtitle": "Controls the default state of the composer translation icon.",
|
||||||
|
"translation_targetLanguage": "Target language",
|
||||||
|
"translation_useAppLanguage": "Use app language",
|
||||||
|
"translation_downloadedModelLabel": "Downloaded model",
|
||||||
|
"translation_presetModelLabel": "Preset Hugging Face model",
|
||||||
|
"translation_manualUrlLabel": "Manual model URL",
|
||||||
|
"translation_downloadModel": "Download model",
|
||||||
|
"translation_downloading": "Downloading...",
|
||||||
|
"translation_working": "Working...",
|
||||||
|
"translation_stop": "Stop",
|
||||||
|
"translation_mergingChunks": "Merging downloaded chunks into final file...",
|
||||||
|
"translation_downloadedModels": "Downloaded models",
|
||||||
|
"translation_deleteModel": "Delete model",
|
||||||
|
"translation_modelDownloaded": "Translation model downloaded.",
|
||||||
|
"translation_downloadStopped": "Download stopped.",
|
||||||
|
"translation_downloadFailed": "Download failed: {error}",
|
||||||
|
"@translation_downloadFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_enterUrlFirst": "Enter a model URL first.",
|
||||||
|
"scanner_linuxPairingShowPin": "Show PIN",
|
||||||
|
"scanner_linuxPairingHidePin": "Hide PIN",
|
||||||
|
"scanner_linuxPairingPinTitle": "Bluetooth Pairing PIN",
|
||||||
|
"scanner_linuxPairingPinPrompt": "Enter PIN for {deviceName} (leave blank if none).",
|
||||||
|
"@scanner_linuxPairingPinPrompt": {
|
||||||
|
"placeholders": {
|
||||||
|
"deviceName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_messageTranslation": "Message translation",
|
||||||
|
"translation_translateBeforeSending": "Translate before sending",
|
||||||
|
"translation_composerEnabledHint": "Messages will be translated before send.",
|
||||||
|
"translation_composerDisabledHint": "Send messages in the original typed language.",
|
||||||
|
"translation_translateTo": "Translate to {language}",
|
||||||
|
"@translation_translateTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"language": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_translationOptions": "Translation options",
|
||||||
|
"translation_systemLanguage": "System language",
|
||||||
|
"background_serviceTitle": "MeshCore running",
|
||||||
|
"background_serviceText": "Keeping BLE connected",
|
||||||
|
"appSettings_translationModelDeleted": "Deleted {name}",
|
||||||
|
"@appSettings_translationModelDeleted": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
|
||||||
|
"@appSettings_translationModelDeleteFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channels_channelUpdateFailed": "Failed to update channel: {error}",
|
||||||
|
"@channels_channelUpdateFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact_typeChat": "Chat",
|
||||||
|
"contact_typeRepeater": "Repeater",
|
||||||
|
"contact_typeRoom": "Room",
|
||||||
|
"contact_typeSensor": "Sensor",
|
||||||
|
"contact_typeUnknown": "Unknown"
|
||||||
}
|
}
|
||||||
+486
-30
@@ -52,7 +52,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scanner_title": "MeshCore Open",
|
"scanner_title": "MeshCore: Versión abierta",
|
||||||
"scanner_scanning": "Escaneando dispositivos...",
|
"scanner_scanning": "Escaneando dispositivos...",
|
||||||
"scanner_connecting": "Conectando...",
|
"scanner_connecting": "Conectando...",
|
||||||
"scanner_disconnecting": "Desconectando...",
|
"scanner_disconnecting": "Desconectando...",
|
||||||
@@ -104,6 +104,8 @@
|
|||||||
"settings_privacyModeEnabled": "Modo de privacidad activado",
|
"settings_privacyModeEnabled": "Modo de privacidad activado",
|
||||||
"settings_privacyModeDisabled": "Modo de privacidad desactivado",
|
"settings_privacyModeDisabled": "Modo de privacidad desactivado",
|
||||||
"settings_actions": "Acciones",
|
"settings_actions": "Acciones",
|
||||||
|
"settings_deleteAllPaths": "Delete All Paths",
|
||||||
|
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
|
||||||
"settings_sendAdvertisement": "Enviar Anuncio",
|
"settings_sendAdvertisement": "Enviar Anuncio",
|
||||||
"settings_sendAdvertisementSubtitle": "Presencia de transmisión ahora",
|
"settings_sendAdvertisementSubtitle": "Presencia de transmisión ahora",
|
||||||
"settings_advertisementSent": "Anuncio enviado",
|
"settings_advertisementSent": "Anuncio enviado",
|
||||||
@@ -121,7 +123,7 @@
|
|||||||
"settings_appDebugLog": "Registro de Depuración de la App",
|
"settings_appDebugLog": "Registro de Depuración de la App",
|
||||||
"settings_appDebugLogSubtitle": "Mensajes de depuración de la aplicación",
|
"settings_appDebugLogSubtitle": "Mensajes de depuración de la aplicación",
|
||||||
"settings_about": "Acerca de",
|
"settings_about": "Acerca de",
|
||||||
"settings_aboutVersion": "MeshCore Open v{version}",
|
"settings_aboutVersion": "MeshCore Open versión {version}",
|
||||||
"@settings_aboutVersion": {
|
"@settings_aboutVersion": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"version": {
|
"version": {
|
||||||
@@ -164,19 +166,19 @@
|
|||||||
"appSettings_themeDark": "Oscuro",
|
"appSettings_themeDark": "Oscuro",
|
||||||
"appSettings_language": "Idioma",
|
"appSettings_language": "Idioma",
|
||||||
"appSettings_languageSystem": "Predeterminado del sistema",
|
"appSettings_languageSystem": "Predeterminado del sistema",
|
||||||
"appSettings_languageEn": "English",
|
"appSettings_languageEn": "Inglés",
|
||||||
"appSettings_languageFr": "Français",
|
"appSettings_languageFr": "Francés",
|
||||||
"appSettings_languageEs": "Español",
|
"appSettings_languageEs": "Español",
|
||||||
"appSettings_languageDe": "Deutsch",
|
"appSettings_languageDe": "Alemán",
|
||||||
"appSettings_languagePl": "Polski",
|
"appSettings_languagePl": "Polaco",
|
||||||
"appSettings_languageSl": "Slovenščina",
|
"appSettings_languageSl": "Esloveno",
|
||||||
"appSettings_languagePt": "Português",
|
"appSettings_languagePt": "Portugués",
|
||||||
"appSettings_languageIt": "Italiano",
|
"appSettings_languageIt": "Italiano",
|
||||||
"appSettings_languageZh": "中文",
|
"appSettings_languageZh": "Chino",
|
||||||
"appSettings_languageSv": "Svenska",
|
"appSettings_languageSv": "Sueco",
|
||||||
"appSettings_languageNl": "Nederlands",
|
"appSettings_languageNl": "Neerlandés",
|
||||||
"appSettings_languageSk": "Slovenčina",
|
"appSettings_languageSk": "Esloveno",
|
||||||
"appSettings_languageBg": "Български",
|
"appSettings_languageBg": "Bulgaro",
|
||||||
"appSettings_notifications": "Notificaciones",
|
"appSettings_notifications": "Notificaciones",
|
||||||
"appSettings_enableNotifications": "Habilitar Notificaciones",
|
"appSettings_enableNotifications": "Habilitar Notificaciones",
|
||||||
"appSettings_enableNotificationsSubtitle": "Recibir notificaciones para mensajes y anuncios",
|
"appSettings_enableNotificationsSubtitle": "Recibir notificaciones para mensajes y anuncios",
|
||||||
@@ -209,8 +211,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"appSettings_batteryChemistryConnectFirst": "Conéctate a un dispositivo para elegir",
|
"appSettings_batteryChemistryConnectFirst": "Conéctate a un dispositivo para elegir",
|
||||||
"appSettings_batteryNmc": "18650 NMC (3.0-4.2V)",
|
"appSettings_batteryNmc": "18650 NMC (3,0-4,2 V)",
|
||||||
"appSettings_batteryLifepo4": "LiFePO4 (2.6-3.65V)",
|
"appSettings_batteryLifepo4": "LiFePO4 (2.6-3.65 V)",
|
||||||
"appSettings_batteryLipo": "LiPo (3.0-4.2V)",
|
"appSettings_batteryLipo": "LiPo (3.0-4.2V)",
|
||||||
"appSettings_mapDisplay": "Visualización del Mapa",
|
"appSettings_mapDisplay": "Visualización del Mapa",
|
||||||
"appSettings_showRepeaters": "Mostrar Repetidores",
|
"appSettings_showRepeaters": "Mostrar Repetidores",
|
||||||
@@ -285,6 +287,7 @@
|
|||||||
"contacts_newGroup": "Nuevo Grupo",
|
"contacts_newGroup": "Nuevo Grupo",
|
||||||
"contacts_groupName": "Nombre del grupo",
|
"contacts_groupName": "Nombre del grupo",
|
||||||
"contacts_groupNameRequired": "El nombre del grupo es obligatorio",
|
"contacts_groupNameRequired": "El nombre del grupo es obligatorio",
|
||||||
|
"contacts_groupNameReserved": "Este nombre de grupo está reservado",
|
||||||
"contacts_groupAlreadyExists": "El grupo \"{name}\" ya existe",
|
"contacts_groupAlreadyExists": "El grupo \"{name}\" ya existe",
|
||||||
"@contacts_groupAlreadyExists": {
|
"@contacts_groupAlreadyExists": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -297,7 +300,7 @@
|
|||||||
"contacts_noContactsMatchFilter": "No hay contactos que coincidan con tu filtro",
|
"contacts_noContactsMatchFilter": "No hay contactos que coincidan con tu filtro",
|
||||||
"contacts_noMembers": "No miembros",
|
"contacts_noMembers": "No miembros",
|
||||||
"contacts_lastSeenNow": "Última vez que se vio ahora",
|
"contacts_lastSeenNow": "Última vez que se vio ahora",
|
||||||
"contacts_lastSeenMinsAgo": "~ {minutes} min.",
|
"contacts_lastSeenMinsAgo": "~ {minutes} minutos",
|
||||||
"@contacts_lastSeenMinsAgo": {
|
"@contacts_lastSeenMinsAgo": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"minutes": {
|
"minutes": {
|
||||||
@@ -336,11 +339,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_hashtagChannel": "Canal con hashtag",
|
|
||||||
"channels_public": "Público",
|
"channels_public": "Público",
|
||||||
"channels_private": "Privado",
|
"channels_private": "Privado",
|
||||||
"channels_publicChannel": "Canal público",
|
|
||||||
"channels_privateChannel": "Canal privado",
|
|
||||||
"channels_editChannel": "Editar canal",
|
"channels_editChannel": "Editar canal",
|
||||||
"channels_muteChannel": "Silenciar canal",
|
"channels_muteChannel": "Silenciar canal",
|
||||||
"channels_unmuteChannel": "Activar canal",
|
"channels_unmuteChannel": "Activar canal",
|
||||||
@@ -387,6 +387,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_smazCompression": "Compresión SMAZ",
|
"channels_smazCompression": "Compresión SMAZ",
|
||||||
|
"channels_cyr2latCompression": "Compresión Cyr2Lat",
|
||||||
|
"channels_cyr2latCompressionDscr": "Reemplaza algunos caracteres cirílicos con caracteres latinos al enviar.",
|
||||||
|
"channels_cyr2latSettingsHeading": "Configuración de Cyr2Lat",
|
||||||
|
"channels_cyr2latSettingsSubheading": "Lista de sustituciones",
|
||||||
|
"channels_cyr2latSettingsDscr": "Editar la configuración JSON de sustitución de caracteres",
|
||||||
|
"channels_cyr2latSettingsDialogHint": "Mapa JSON de sustituciones",
|
||||||
|
"channels_cyr2latSettingsDialogWrongJSON": "JSON incorrecto: {error}",
|
||||||
|
"settings_cyr2latProfileAdd": "Añadir perfil Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileName": "Nombre del perfil",
|
||||||
|
"settings_cyr2latProfileNameEmpty": "El nombre del perfil no puede estar vacío",
|
||||||
|
"settings_cyr2latProfileAdded": "Perfil añadido correctamente",
|
||||||
|
"settings_cyr2latProfileUpdated": "Perfil actualizado correctamente",
|
||||||
|
"settings_cyr2latProfileEdit": "Editar perfil Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileDelete": "Eliminar perfil Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileDeleted": "Perfil eliminado correctamente",
|
||||||
|
"settings_cyr2latProfileDeleteDscr": "¿Está seguro de que desea eliminar el perfil \"{name}\"?",
|
||||||
"channels_channelUpdated": "Canal \"{name}\" actualizado",
|
"channels_channelUpdated": "Canal \"{name}\" actualizado",
|
||||||
"@channels_channelUpdated": {
|
"@channels_channelUpdated": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -398,7 +414,7 @@
|
|||||||
"channels_publicChannelAdded": "Canal público añadido",
|
"channels_publicChannelAdded": "Canal público añadido",
|
||||||
"channels_sortBy": "Ordenar por",
|
"channels_sortBy": "Ordenar por",
|
||||||
"channels_sortManual": "Manual",
|
"channels_sortManual": "Manual",
|
||||||
"channels_sortAZ": "A-Z",
|
"channels_sortAZ": "De la A a la Z",
|
||||||
"channels_sortLatestMessages": "Últimos mensajes",
|
"channels_sortLatestMessages": "Últimos mensajes",
|
||||||
"channels_sortUnread": "Sin leer",
|
"channels_sortUnread": "Sin leer",
|
||||||
"chat_noMessages": "Aún no hay mensajes",
|
"chat_noMessages": "Aún no hay mensajes",
|
||||||
@@ -462,7 +478,7 @@
|
|||||||
"emojiCategoryObjects": "Objetos",
|
"emojiCategoryObjects": "Objetos",
|
||||||
"gifPicker_title": "Elegir un GIF",
|
"gifPicker_title": "Elegir un GIF",
|
||||||
"gifPicker_searchHint": "Buscar GIFs...",
|
"gifPicker_searchHint": "Buscar GIFs...",
|
||||||
"gifPicker_poweredBy": "Powered by GIPHY",
|
"gifPicker_poweredBy": "Con tecnología de GIPHY",
|
||||||
"gifPicker_noGifsFound": "No se encontraron GIFs",
|
"gifPicker_noGifsFound": "No se encontraron GIFs",
|
||||||
"gifPicker_failedLoad": "No se pudo cargar los GIFs",
|
"gifPicker_failedLoad": "No se pudo cargar los GIFs",
|
||||||
"gifPicker_failedSearch": "No se encontraron GIFs",
|
"gifPicker_failedSearch": "No se encontraron GIFs",
|
||||||
@@ -530,7 +546,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debugFrame_textTypeCli": "CLI",
|
"debugFrame_textTypeCli": "Interfaz de línea de comandos",
|
||||||
"debugFrame_textTypePlain": "Sencillo",
|
"debugFrame_textTypePlain": "Sencillo",
|
||||||
"debugFrame_text": "- Texto: \"{text}\"",
|
"debugFrame_text": "- Texto: \"{text}\"",
|
||||||
"@debugFrame_text": {
|
"@debugFrame_text": {
|
||||||
@@ -549,7 +565,7 @@
|
|||||||
"chat_pathHistoryFull": "El historial de rutas está completo. Eliminar entradas para añadir nuevas.",
|
"chat_pathHistoryFull": "El historial de rutas está completo. Eliminar entradas para añadir nuevas.",
|
||||||
"chat_hopSingular": "salta",
|
"chat_hopSingular": "salta",
|
||||||
"chat_hopPlural": "salta",
|
"chat_hopPlural": "salta",
|
||||||
"chat_hopsCount": "{count} {count, plural, =1{hop} other{hops}}",
|
"chat_hopsCount": "{count} {count, plural, =1{salto} other{saltos}}",
|
||||||
"@chat_hopsCount": {
|
"@chat_hopsCount": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {
|
"count": {
|
||||||
@@ -644,7 +660,7 @@
|
|||||||
"map_repeater": "Repetidor",
|
"map_repeater": "Repetidor",
|
||||||
"map_room": "Habitación",
|
"map_room": "Habitación",
|
||||||
"map_sensor": "Sensor",
|
"map_sensor": "Sensor",
|
||||||
"map_pinDm": "Pin (DM)",
|
"map_pinDm": "Etiqueta (DM)",
|
||||||
"map_pinPrivate": "Bloqueo (Privado)",
|
"map_pinPrivate": "Bloqueo (Privado)",
|
||||||
"map_pinPublic": "Clave (Pública)",
|
"map_pinPublic": "Clave (Pública)",
|
||||||
"map_lastSeen": "Última vez que se vio",
|
"map_lastSeen": "Última vez que se vio",
|
||||||
@@ -880,9 +896,9 @@
|
|||||||
"repeater_managementTools": "Herramientas de Gestión",
|
"repeater_managementTools": "Herramientas de Gestión",
|
||||||
"repeater_status": "Estado",
|
"repeater_status": "Estado",
|
||||||
"repeater_statusSubtitle": "Ver el estado, las estadísticas y los vecinos del repetidor",
|
"repeater_statusSubtitle": "Ver el estado, las estadísticas y los vecinos del repetidor",
|
||||||
"repeater_telemetry": "Telemetry",
|
"repeater_telemetry": "Telemetría",
|
||||||
"repeater_telemetrySubtitle": "Ver la telemetría de los sensores y las estadísticas del sistema",
|
"repeater_telemetrySubtitle": "Ver la telemetría de los sensores y las estadísticas del sistema",
|
||||||
"repeater_cli": "CLI",
|
"repeater_cli": "Interfaz de línea de comandos",
|
||||||
"repeater_cliSubtitle": "Enviar comandos al repetidor",
|
"repeater_cliSubtitle": "Enviar comandos al repetidor",
|
||||||
"repeater_settings": "Configuración",
|
"repeater_settings": "Configuración",
|
||||||
"repeater_settingsSubtitle": "Configurar parámetros del repetidor",
|
"repeater_settingsSubtitle": "Configurar parámetros del repetidor",
|
||||||
@@ -1058,6 +1074,81 @@
|
|||||||
},
|
},
|
||||||
"repeater_confirm": "Confirmar",
|
"repeater_confirm": "Confirmar",
|
||||||
"repeater_settingsSaved": "Guardado de ajustes exitoso",
|
"repeater_settingsSaved": "Guardado de ajustes exitoso",
|
||||||
|
"repeater_rxGain": "Aumento en la ganancia de RX",
|
||||||
|
"repeater_rxGainHelper": "Mayor sensibilidad, mayor consumo de corriente (solo para SX1262/SX1268)",
|
||||||
|
"repeater_refreshRxGain": "Aumenta el rendimiento de RX con la nueva versión.",
|
||||||
|
"repeater_multiAcks": "Múltiples respuestas de confirmación",
|
||||||
|
"repeater_multiAcksSubtitle": "Reconocer mensajes a través de múltiples vías para una mejor entrega.",
|
||||||
|
"repeater_refreshMultiAcks": "Reenviar múltiples confirmaciones",
|
||||||
|
"repeater_networkHealth": "Salud de la red",
|
||||||
|
"repeater_loopDetect": "Detección de bucles",
|
||||||
|
"repeater_loopDetectHelper": "Crea paquetes de \"flujo\" que parezcan bucles de enrutamiento.",
|
||||||
|
"repeater_loopDetectOff": "Fuera",
|
||||||
|
"repeater_loopDetectMinimal": "Mínimo",
|
||||||
|
"repeater_loopDetectModerate": "Moderado",
|
||||||
|
"repeater_loopDetectStrict": "Estrictos",
|
||||||
|
"repeater_dutyCycle": "Ciclo de trabajo",
|
||||||
|
"repeater_dutyCycleHelper": "Porcentaje máximo de tiempo de antena",
|
||||||
|
"repeater_dutyCyclePercent": "{percent}%",
|
||||||
|
"@repeater_dutyCyclePercent": {
|
||||||
|
"placeholders": {
|
||||||
|
"percent": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_ownerInfo": "Información del operador",
|
||||||
|
"repeater_ownerInfoHelper": "Metadatos públicos para este repetidor",
|
||||||
|
"repeater_refreshOwnerInfo": "Actualizar información del operador",
|
||||||
|
"repeater_floodMax": "Máximo número de saltos en caso de inundación",
|
||||||
|
"repeater_floodMaxHelper": "Número máximo de paquetes de flujo que un nodo puede enviar (0-64)",
|
||||||
|
"repeater_advancedSettings": "Avanzado",
|
||||||
|
"repeater_advancedSettingsSubtitle": "Perillas de ajuste para operadores experimentados",
|
||||||
|
"repeater_pathHashMode": "Modo de hash de ruta",
|
||||||
|
"repeater_pathHashModeHelper": "Bytes utilizados para codificar el ID de este repetidor en las etiquetas de ruta/detección de bucles. 0=1 byte (256 IDs, hasta 64 saltos), 1=2 bytes (65.000 IDs, hasta 32 saltos), 2=3 bytes (16 millones de IDs, hasta 21 saltos). Las versiones 1.13 y anteriores de firmware eliminan rutas de múltiples bytes; solo se detectan una vez que la red está activa en la versión 1.14 o posterior.",
|
||||||
|
"repeater_txDelay": "Retraso en Flood, TX",
|
||||||
|
"repeater_txDelayHelper": "Ajuste de retransmisión para el tráfico de inundación, como un multiplicador del tiempo de transmisión del paquete (0-2, valor predeterminado 0.5). Un valor más alto significa menos colisiones, pero una entrega más lenta.",
|
||||||
|
"repeater_directTxDelay": "Retraso directo en TX",
|
||||||
|
"repeater_directTxDelayHelper": "Reenvío de espacios para el tráfico directo (no masivo), como un multiplicador del tiempo de transmisión del paquete (0-2, valor predeterminado 0.3).",
|
||||||
|
"repeater_intThresh": "Límite de interferencia",
|
||||||
|
"repeater_intThreshHelper": "Se establece un umbral para la calibración del nivel de ruido de la radio, de modo que rechaza las interferencias que superen este nivel. 0 deshabilita — solo aumente este valor si observa errores en una banda de frecuencia con mucho ruido.",
|
||||||
|
"repeater_agcResetInterval": "Intervalo de reinicio de AGC",
|
||||||
|
"repeater_agcResetIntervalHelper": "¿Con qué frecuencia se debe restablecer el control automático de ganancia del radio para recuperarse de un estado de ganancia bloqueada? Se puede restablecer cada pocos segundos, o cada 4 segundos. Desactivar la función de restablecimiento periódico.",
|
||||||
|
"repeater_actionsTitle": "Acciones",
|
||||||
|
"repeater_sendAdvert": "Enviar anuncio sobre inundaciones",
|
||||||
|
"repeater_sendAdvertSubtitle": "Transmite un anuncio sobre inundaciones a través de la red.",
|
||||||
|
"repeater_sendAdvertZeroHop": "Enviar anuncio sin intermediarios",
|
||||||
|
"repeater_sendAdvertZeroHopSubtitle": "Transmite un anuncio de un solo salto (sin retransmisiones).",
|
||||||
|
"repeater_clockSync": "Sincronizar reloj ahora",
|
||||||
|
"repeater_clockSyncSubtitle": "Envía la hora de tu teléfono al repetidor.",
|
||||||
|
"repeater_actionSucceeded": "{action} succeeded",
|
||||||
|
"@repeater_actionSucceeded": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_actionFailed": "{action} failed: {error}",
|
||||||
|
"@repeater_actionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_settingsSavedRebootNeeded": "Configuración guardada — reinicie el repetidor para aplicar los cambios.",
|
||||||
|
"repeater_settingsPartialFailure": "Algunas configuraciones no se pudieron aplicar: {failures}",
|
||||||
|
"@repeater_settingsPartialFailure": {
|
||||||
|
"placeholders": {
|
||||||
|
"failures": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repeater_errorSavingSettings": "Error al guardar la configuración: {error}",
|
"repeater_errorSavingSettings": "Error al guardar la configuración: {error}",
|
||||||
"@repeater_errorSavingSettings": {
|
"@repeater_errorSavingSettings": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1069,11 +1160,9 @@
|
|||||||
"repeater_refreshBasicSettings": "Actualizar Configuración Básica",
|
"repeater_refreshBasicSettings": "Actualizar Configuración Básica",
|
||||||
"repeater_refreshRadioSettings": "Actualizar Ajustes de Radio",
|
"repeater_refreshRadioSettings": "Actualizar Ajustes de Radio",
|
||||||
"repeater_refreshTxPower": "Actualizar TX de energía",
|
"repeater_refreshTxPower": "Actualizar TX de energía",
|
||||||
"repeater_refreshLocationSettings": "Actualizar Configuración de Ubicación",
|
|
||||||
"repeater_refreshPacketForwarding": "Actualizar Enrutamiento de Paquetes",
|
"repeater_refreshPacketForwarding": "Actualizar Enrutamiento de Paquetes",
|
||||||
"repeater_refreshGuestAccess": "Actualizar Acceso Invitados",
|
"repeater_refreshGuestAccess": "Actualizar Acceso Invitados",
|
||||||
"repeater_refreshPrivacyMode": "Actualizar Modo Privacidad",
|
"repeater_refreshPrivacyMode": "Actualizar Modo Privacidad",
|
||||||
"repeater_refreshAdvertisementSettings": "Actualizar Configuración de Anuncios",
|
|
||||||
"repeater_refreshed": "{label} actualizado",
|
"repeater_refreshed": "{label} actualizado",
|
||||||
"@repeater_refreshed": {
|
"@repeater_refreshed": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1346,7 +1435,7 @@
|
|||||||
"listFilter_sortBy": "Ordenar por",
|
"listFilter_sortBy": "Ordenar por",
|
||||||
"listFilter_latestMessages": "Últimos mensajes",
|
"listFilter_latestMessages": "Últimos mensajes",
|
||||||
"listFilter_heardRecently": "Escuchado recientemente",
|
"listFilter_heardRecently": "Escuchado recientemente",
|
||||||
"listFilter_az": "A-Z",
|
"listFilter_az": "De la A a la Z",
|
||||||
"listFilter_filters": "Filtros",
|
"listFilter_filters": "Filtros",
|
||||||
"listFilter_all": "Todas",
|
"listFilter_all": "Todas",
|
||||||
"listFilter_users": "Usuarios",
|
"listFilter_users": "Usuarios",
|
||||||
@@ -1887,5 +1976,372 @@
|
|||||||
"usbStatus_searching": "Buscando dispositivos USB...",
|
"usbStatus_searching": "Buscando dispositivos USB...",
|
||||||
"usbStatus_notConnected": "Seleccione un dispositivo USB",
|
"usbStatus_notConnected": "Seleccione un dispositivo USB",
|
||||||
"usbConnectionFailed": "Error al conectar mediante USB: {error}",
|
"usbConnectionFailed": "Error al conectar mediante USB: {error}",
|
||||||
"usbErrorConnectTimedOut": "La conexión ha caducado. Asegúrese de que el dispositivo tenga el firmware USB Companion."
|
"usbErrorConnectTimedOut": "La conexión ha caducado. Asegúrese de que el dispositivo tenga el firmware USB Companion.",
|
||||||
|
"@tcpStatus_connectingTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"endpoint": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tcpConnectionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tcpScreenTitle": "Establecer conexión a través de TCP",
|
||||||
|
"connectionChoiceTcpLabel": "TCP",
|
||||||
|
"tcpHostHint": "192.168.40.10",
|
||||||
|
"tcpHostLabel": "Dirección IP",
|
||||||
|
"tcpPortLabel": "Puerto",
|
||||||
|
"tcpPortHint": "5000",
|
||||||
|
"tcpStatus_notConnected": "Ingrese la dirección final y conecte.",
|
||||||
|
"tcpStatus_connectingTo": "Conectándose a {endpoint}...",
|
||||||
|
"tcpErrorHostRequired": "Se requiere la dirección IP.",
|
||||||
|
"tcpErrorPortInvalid": "El puerto debe estar entre 1 y 65535.",
|
||||||
|
"tcpErrorUnsupported": "El protocolo de transporte TCP no está soportado en esta plataforma.",
|
||||||
|
"tcpErrorTimedOut": "La conexión TCP ha caducado.",
|
||||||
|
"tcpConnectionFailed": "Error en la conexión TCP: {error}",
|
||||||
|
"map_showDiscoveryContacts": "Mostrar Contactos de Descubrimiento",
|
||||||
|
"map_setAsMyLocation": "Establecer mi ubicación",
|
||||||
|
"@path_routeWeight": {
|
||||||
|
"placeholders": {
|
||||||
|
"weight": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings_privacySubtitle": "Controlar qué información se comparte.",
|
||||||
|
"settings_allowByContact": "Permitir por banderas de contacto",
|
||||||
|
"settings_denyAll": "Denegar todo",
|
||||||
|
"settings_telemetryBaseMode": "Modo base de telemetría",
|
||||||
|
"settings_telemetryEnvironmentMode": "Modo de entorno de telemetría",
|
||||||
|
"settings_advertLocationSubtitle": "Incluir ubicación en anuncio",
|
||||||
|
"contact_info": "Información de contacto",
|
||||||
|
"settings_privacySettingsDescription": "Elige qué información comparte tu dispositivo con otros.",
|
||||||
|
"settings_allowAll": "Permitir todo",
|
||||||
|
"settings_privacy": "Configuración de privacidad",
|
||||||
|
"contact_settings": "Configuración de contacto",
|
||||||
|
"settings_telemetryLocationMode": "Modo de ubicación de telemetría",
|
||||||
|
"contact_teleBase": "Base de Telemetría",
|
||||||
|
"contact_teleLoc": "Ubicación de telemetría",
|
||||||
|
"settings_advertLocation": "Ubicación de anuncio",
|
||||||
|
"contact_teleLocSubtitle": "Permitir el intercambio de datos de ubicación",
|
||||||
|
"contact_clearChat": "Borrar chat",
|
||||||
|
"contact_telemetry": "Telemetría",
|
||||||
|
"contact_lastSeen": "Visto por última vez",
|
||||||
|
"contact_teleBaseSubtitle": "Permitir el intercambio de nivel de batería y telemetría básica",
|
||||||
|
"contact_teleEnv": "Entorno de Telemetría",
|
||||||
|
"contact_teleEnvSubtitle": "Permitir el intercambio de datos de sensores de entorno",
|
||||||
|
"appSettings_initialRouteWeight": "Peso inicial de la ruta",
|
||||||
|
"appSettings_maxRouteWeight": "Peso máximo permitido para la ruta",
|
||||||
|
"appSettings_initialRouteWeightSubtitle": "Peso inicial para rutas recién descubiertas",
|
||||||
|
"appSettings_maxRouteWeightSubtitle": "Peso máximo que una ruta puede acumular gracias a entregas exitosas.",
|
||||||
|
"appSettings_routeWeightSuccessIncrement": "Incremento de peso para el éxito",
|
||||||
|
"appSettings_routeWeightSuccessIncrementSubtitle": "Peso añadido a una ruta después de una entrega exitosa.",
|
||||||
|
"appSettings_routeWeightFailureDecrement": "Reducción del peso asociado al fallo",
|
||||||
|
"appSettings_routeWeightFailureDecrementSubtitle": "Peso retirado de un camino después de un intento de entrega fallido.",
|
||||||
|
"appSettings_maxMessageRetries": "Número máximo de reintentos de envío de mensajes",
|
||||||
|
"appSettings_maxMessageRetriesSubtitle": "Número de intentos de reintento antes de marcar un mensaje como fallido.",
|
||||||
|
"path_routeWeight": "{weight}/{max}",
|
||||||
|
"settings_telemetryModeUpdated": "Modo de telemetría actualizado",
|
||||||
|
"settings_multiAck": "Múltiples respuestas de confirmación",
|
||||||
|
"map_showOverlaps": "Superposiciones de tecla repetidora",
|
||||||
|
"map_runTraceWithReturnPath": "Volver atrás por el mismo camino.",
|
||||||
|
"@radioStats_noiseFloor": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastRssi": {
|
||||||
|
"placeholders": {
|
||||||
|
"rssiDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastSnr": {
|
||||||
|
"placeholders": {
|
||||||
|
"snr": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_txAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_rxAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_stripNoise": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_jumpToOldestUnread": "Salta a los mensajes más antiguos sin leer",
|
||||||
|
"chat_sendCooldown": "Por favor, espere un momento antes de reenviar.",
|
||||||
|
"appSettings_languageHu": "Húngaro",
|
||||||
|
"appSettings_jumpToOldestUnreadSubtitle": "Cuando abras una conversación con mensajes sin leer, desplázate hacia el primer mensaje sin leer en lugar del más reciente.",
|
||||||
|
"appSettings_languageJa": "Japonés",
|
||||||
|
"appSettings_languageKo": "Coreano",
|
||||||
|
"radioStats_tooltip": "Estadísticas de radio y malla",
|
||||||
|
"radioStats_screenTitle": "Estadísticas de radio",
|
||||||
|
"radioStats_notConnected": "Conéctese a un dispositivo para visualizar estadísticas de radio.",
|
||||||
|
"radioStats_firmwareTooOld": "Las estadísticas de radio requieren un firmware compatible v8 o posterior.",
|
||||||
|
"radioStats_waiting": "Esperando datos…",
|
||||||
|
"radioStats_noiseFloor": "Nivel de ruido: {noiseDbm} dBm",
|
||||||
|
"radioStats_lastRssi": "Último RSSI: {rssiDbm} dBm",
|
||||||
|
"radioStats_lastSnr": "Último SNR: {snr} dB",
|
||||||
|
"radioStats_txAir": "Tiempo de emisión en Texas (total): {seconds} s",
|
||||||
|
"radioStats_rxAir": "Tiempo de transmisión de RX (total): {seconds} s",
|
||||||
|
"radioStats_chartCaption": "Nivel de ruido (dBm) en muestras recientes.",
|
||||||
|
"radioStats_stripNoise": "Nivel de ruido: {noiseDbm} dBm",
|
||||||
|
"radioStats_stripWaiting": "Obteniendo estadísticas de la radio…",
|
||||||
|
"radioStats_settingsTile": "Estadísticas de radio",
|
||||||
|
"radioStats_settingsSubtitle": "Nivel de ruido, RSSI, SNR y tiempo de transmisión",
|
||||||
|
"@translation_downloadFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_title": "Traducción",
|
||||||
|
"translation_enableSubtitle": "Traducir los mensajes entrantes y permitir la traducción previa al envío.",
|
||||||
|
"translation_enableTitle": "Habilitar la traducción",
|
||||||
|
"translation_composerTitle": "Traducir antes de enviar",
|
||||||
|
"translation_composerSubtitle": "Controla el estado predeterminado del icono de traducción del compositor.",
|
||||||
|
"translation_targetLanguage": "Idioma de destino",
|
||||||
|
"translation_useAppLanguage": "Utilizar el idioma de la aplicación",
|
||||||
|
"translation_downloadedModelLabel": "Modelo descargado",
|
||||||
|
"translation_presetModelLabel": "Modelo predefinido de Hugging Face",
|
||||||
|
"translation_manualUrlLabel": "URL del modelo manual",
|
||||||
|
"translation_downloadModel": "Descargar el modelo",
|
||||||
|
"translation_downloading": "Descargando...",
|
||||||
|
"translation_working": "Trabajando...",
|
||||||
|
"translation_stop": "¡Detente!",
|
||||||
|
"translation_mergingChunks": "Combinando los fragmentos descargados en el archivo final...",
|
||||||
|
"translation_downloadedModels": "Modelos descargados",
|
||||||
|
"translation_deleteModel": "Eliminar modelo",
|
||||||
|
"translation_modelDownloaded": "Modelo de traducción descargado.",
|
||||||
|
"translation_downloadStopped": "La descarga se ha detenido.",
|
||||||
|
"translation_downloadFailed": "No se pudo descargar: {error}",
|
||||||
|
"translation_enterUrlFirst": "Primero, introduzca la URL del modelo.",
|
||||||
|
"@scanner_linuxPairingPinPrompt": {
|
||||||
|
"placeholders": {
|
||||||
|
"deviceName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scanner_linuxPairingPinPrompt": "Introduzca el código PIN para {deviceName} (deje en blanco si no hay ninguno).",
|
||||||
|
"scanner_linuxPairingShowPin": "Mostrar código PIN",
|
||||||
|
"scanner_linuxPairingPinTitle": "PIN para emparejar dispositivos Bluetooth",
|
||||||
|
"scanner_linuxPairingHidePin": "Ocultar PIN",
|
||||||
|
"@translation_translateTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"language": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_composerDisabledHint": "Envía mensajes utilizando el lenguaje escrito original.",
|
||||||
|
"translation_composerEnabledHint": "Los mensajes serán traducidos antes de ser enviados.",
|
||||||
|
"translation_messageTranslation": "Traducción del mensaje",
|
||||||
|
"translation_translateBeforeSending": "Traducir antes de enviar",
|
||||||
|
"translation_translateTo": "Traducir a {language}",
|
||||||
|
"translation_translationOptions": "Opciones de traducción",
|
||||||
|
"translation_systemLanguage": "Idioma del sistema",
|
||||||
|
"repeater_cliQuickDiscovery": "Descubrir Vecinos",
|
||||||
|
"repeater_cliQuickClockSync": "Sincronización del reloj",
|
||||||
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Enviar automáticamente la función de \"sincronización de reloj\" después de un inicio de sesión exitoso.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Sincronización del reloj después de iniciar sesión",
|
||||||
|
"repeater_guest": "Información sobre repetidores",
|
||||||
|
"chat_sendMessage": "Enviar mensaje",
|
||||||
|
"repeater_guestTools": "Herramientas para invitados",
|
||||||
|
"room_guest": "Información del servidor",
|
||||||
|
"repeater_getCategory": "Obtener valores",
|
||||||
|
"repeater_powerMgmt": "Gestión de la energía",
|
||||||
|
"repeater_sensors": "Sensores",
|
||||||
|
"repeater_cliHelpPowerOff": "Apaga el dispositivo. (no se espera respuesta)",
|
||||||
|
"repeater_cliHelpClkReboot": "Restablece el reloj a una fecha conocida y reinicia el dispositivo.",
|
||||||
|
"repeater_cliHelpAdvertZeroHop": "Envía un anuncio que no requiere saltos (solo para los vecinos inmediatos).",
|
||||||
|
"repeater_cliHelpStartOta": "Inicia una actualización de firmware por aire en las placas compatibles.",
|
||||||
|
"repeater_cliHelpTime": "Establece la hora del dispositivo en los segundos correspondientes a la época Unix. La hora no puede retroceder.",
|
||||||
|
"repeater_cliHelpBoard": "Muestra el fabricante de la placa base / identificador de hardware.",
|
||||||
|
"repeater_cliHelpDiscoverNeighbors": "Envía una solicitud de descubrimiento de nodos a los vecinos cercanos. (Solo para repetidores)",
|
||||||
|
"repeater_cliHelpPowersaving": "Indica si el modo de ahorro de energía está activado o desactivado.",
|
||||||
|
"repeater_cliHelpPowersavingOnOff": "Activa o desactiva el modo de ahorro de energía (si está disponible).",
|
||||||
|
"repeater_cliHelpErase": "(Solo para dispositivos) Formatea el sistema de archivos del dispositivo. Elimina todas las configuraciones y contactos.",
|
||||||
|
"repeater_cliHelpSetDutyCycle": "Establece el ciclo de transmisión máximo permitido como un porcentaje (1-100). Ajusta internamente el factor de tiempo de aire.",
|
||||||
|
"repeater_cliHelpSetPrvKey": "(Solo para series) Reemplaza la clave privada de identificación del dispositivo. Se requiere reiniciar para aplicar. Genera una nueva clave pública.",
|
||||||
|
"repeater_cliHelpSetRadioRxGain": "(Solo para SX126x) Activa/desactiva el amplificador de la RX para mejorar la sensibilidad a corrientes más altas.",
|
||||||
|
"repeater_cliHelpSetOwnerInfo": "Define la cadena de información de contacto del propietario que se incluye en los anuncios. Utilice '|' para indicar nuevas líneas.",
|
||||||
|
"repeater_cliHelpSetPathHashMode": "Establece el modo de hash de la ruta. 0 = antiguo, 1 = estándar, 2 = estricto. Afecta la forma en que se comparan las rutas.",
|
||||||
|
"repeater_cliHelpSetLoopDetect": "Establece la sensibilidad para la detección de bucles de enrutamiento: apagado, mínimo, moderado o estricto.",
|
||||||
|
"repeater_cliHelpSetFreq": "(Solo para la configuración de frecuencia) Establece rápidamente la frecuencia deseada. Se requiere reiniciar. Se recomienda utilizar la opción \"configurar radio\" para obtener todos los parámetros de la radio.",
|
||||||
|
"repeater_cliHelpSetBridgeChannel": "(Solo para el puente ESPNow) Establece el canal de WiFi (1-14) que utiliza el puente.",
|
||||||
|
"repeater_cliHelpGetName": "Muestra el nombre del nodo configurado.",
|
||||||
|
"repeater_cliHelpGetRole": "Muestra el rol del firmware (Repetidor, Servidor de habitación, etc.).",
|
||||||
|
"repeater_cliHelpGetPublicKey": "Muestra la clave pública del dispositivo.",
|
||||||
|
"repeater_cliHelpGetPrvKey": "(Solo para uso en serie) Muestra la clave privada del dispositivo. Trátala como una información confidencial.",
|
||||||
|
"repeater_cliHelpGetRepeat": "Indica si el enrutamiento de paquetes (función de repetidor) está activado o desactivado.",
|
||||||
|
"repeater_cliHelpGetTx": "Muestra la potencia actual en dBm.",
|
||||||
|
"repeater_cliHelpGetFreq": "Muestra la frecuencia de radio configurada en MHz.",
|
||||||
|
"repeater_cliHelpGetRadio": "Muestra todos los parámetros de radio: frecuencia, ancho de banda, factor de dispersión, tasa de codificación.",
|
||||||
|
"repeater_cliHelpGetRadioRxGain": "(Solo para SX126x) Muestra el estado de ganancia amplificada del receptor.",
|
||||||
|
"repeater_cliHelpGetAf": "Muestra el factor de tiempo actual.",
|
||||||
|
"repeater_cliHelpGetDutyCycle": "Muestra el ciclo de trabajo actual permitido como un porcentaje.",
|
||||||
|
"repeater_cliHelpGetIntThresh": "Muestra el umbral de interferencia del canal en dB.",
|
||||||
|
"repeater_cliHelpGetAgcResetInterval": "Muestra el intervalo de reinicio del AGC en segundos.",
|
||||||
|
"repeater_cliHelpGetMultiAcks": "Indica si el modo de confirmación doble está activado (1) o desactivado (0).",
|
||||||
|
"repeater_cliHelpGetAllowReadOnly": "Indica si se permite el acceso de solo lectura para los usuarios invitados.",
|
||||||
|
"repeater_cliHelpGetAdvertInterval": "Muestra el intervalo de publicidad local en minutos.",
|
||||||
|
"repeater_cliHelpGetFloodAdvertInterval": "Muestra el intervalo de publicidad para la emisión de la señal de inundación, expresado en horas.",
|
||||||
|
"repeater_cliHelpGetGuestPassword": "Muestra la contraseña de invitado configurada.",
|
||||||
|
"repeater_cliHelpGetLat": "Muestra la latitud configurada.",
|
||||||
|
"repeater_cliHelpGetLon": "Muestra la longitud configurada.",
|
||||||
|
"repeater_cliHelpGetRxDelay": "Muestra el valor base de rxdelay.",
|
||||||
|
"repeater_cliHelpGetTxDelay": "Muestra el factor de retardo en modo de inundación.",
|
||||||
|
"repeater_cliHelpGetDirectTxDelay": "Muestra el factor de retardo en modo directo.",
|
||||||
|
"repeater_cliHelpGetFloodMax": "Muestra el número máximo de saltos por inundación.",
|
||||||
|
"repeater_cliHelpGetOwnerInfo": "Muestra la cadena de información de contacto del propietario.",
|
||||||
|
"repeater_cliHelpGetPathHashMode": "Muestra el modo de hash de ruta (0/1/2).",
|
||||||
|
"repeater_cliHelpGetLoopDetect": "Muestra la sensibilidad en la detección de bucles.",
|
||||||
|
"repeater_cliHelpGetAcl": "(Solo para series) Enumera las entradas de control de acceso en un repetidor.",
|
||||||
|
"repeater_cliHelpGetBridgeEnabled": "Indica si el puente está habilitado.",
|
||||||
|
"repeater_cliHelpGetBridgeDelay": "Muestra el retardo del puente en milisegundos.",
|
||||||
|
"repeater_cliHelpGetBridgeSource": "Indica si el puente está enviando o recibiendo paquetes RX o TX.",
|
||||||
|
"repeater_cliHelpGetBridgeBaud": "(Solo puente RS232) Muestra la velocidad de transmisión del puente.",
|
||||||
|
"repeater_cliHelpGetBridgeChannel": "(Solo para el puente ESPNow) Muestra el canal WiFi del puente.",
|
||||||
|
"repeater_cliHelpGetBridgeSecret": "(Solo para el puente ESPNow) Muestra el secreto compartido por el puente.",
|
||||||
|
"repeater_cliHelpGetBootloaderVer": "(Solo NRF52) Muestra la versión del cargador.",
|
||||||
|
"repeater_cliHelpGetAdcMultiplier": "Muestra el multiplicador del ADC (escalado de voltaje de la batería).",
|
||||||
|
"repeater_cliHelpGetPwrMgtSupport": "Indica si el sistema cuenta con funciones de gestión de energía.",
|
||||||
|
"repeater_cliHelpGetPwrMgtSource": "Indica la fuente de energía actual: externa o batería.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootReason": "Muestra las razones más recientes de reinicio y apagado.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootMv": "Muestra el voltaje de la batería al encender el sistema en milivoltios (mV).",
|
||||||
|
"repeater_cliHelpSensorGet": "Lee una configuración de sensor personalizada mediante una tecla.",
|
||||||
|
"repeater_cliHelpSensorSet": "Crea una configuración personalizada para un sensor.",
|
||||||
|
"repeater_cliHelpSensorList": "Muestra todas las configuraciones de sensores personalizadas, paginadas a partir de un índice de inicio opcional.",
|
||||||
|
"repeater_cliHelpRegionDefault": "Muestra el ámbito predeterminado actual.",
|
||||||
|
"repeater_cliHelpRegionDefaultSet": "Establece el ámbito regional predeterminado. Utilice \"<null>\" para restablecer a la configuración predeterminada.",
|
||||||
|
"repeater_cliHelpRegionListAllowed": "Enumera las regiones que permiten el paso de vehículos debido a inundaciones.",
|
||||||
|
"repeater_cliHelpRegionListDenied": "Enumera las regiones que prohíben el tráfico debido a las inundaciones.",
|
||||||
|
"repeater_cliHelpStatsPackets": "(Solo para series) Muestra estadísticas a nivel de paquetes.",
|
||||||
|
"repeater_cliHelpStatsRadio": "(Solo para transmisiones en serie) Muestra estadísticas de radio.",
|
||||||
|
"repeater_cliHelpStatsCore": "(Solo para series) Muestra estadísticas clave del firmware.",
|
||||||
|
"common_done": "Done",
|
||||||
|
"background_serviceTitle": "MeshCore running",
|
||||||
|
"background_serviceText": "Keeping BLE connected",
|
||||||
|
"appSettings_translationModelDeleted": "Deleted {name}",
|
||||||
|
"@appSettings_translationModelDeleted": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
|
||||||
|
"@appSettings_translationModelDeleteFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channels_channelUpdateFailed": "Failed to update channel: {error}",
|
||||||
|
"@channels_channelUpdateFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map_type": "Type",
|
||||||
|
"map_path": "Path",
|
||||||
|
"map_location": "Location",
|
||||||
|
"map_estLocation": "Est. Location",
|
||||||
|
"map_publicKey": "Public Key",
|
||||||
|
"map_publicKeyPrefixHint": "e.g. ab12",
|
||||||
|
"contact_typeChat": "Chat",
|
||||||
|
"contact_typeRepeater": "Repeater",
|
||||||
|
"contact_typeRoom": "Room",
|
||||||
|
"contact_typeSensor": "Sensor",
|
||||||
|
"contact_typeUnknown": "Unknown",
|
||||||
|
"channels_via": "via {path}",
|
||||||
|
"chat_score": "Score",
|
||||||
|
"map_sharedAt": "Compartido",
|
||||||
|
"@losBlockedSpotChip": {
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@losSelectedObstructionDetails": {
|
||||||
|
"placeholders": {
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromA": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromB": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"losBlockedSpotsTitle": "Espacios ocupados",
|
||||||
|
"losBlockedSpotsHint": "Seleccione un punto bloqueado para resaltarlo en el mapa.",
|
||||||
|
"losSelectedObstructionTitle": "Obstrucción seleccionada",
|
||||||
|
"losSelectedObstructionDetails": "Bloqueado por {obstruction} a una altura de {heightUnit}, a {distanceFromA} metros de A y a {distanceFromB} metros de B ({distanceUnit}).",
|
||||||
|
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
|
||||||
|
"settings_companionDebugLog": "Registro de depuración asociado",
|
||||||
|
"chat_newMessages": "Nuevos mensajes",
|
||||||
|
"settings_companionDebugLogSubtitle": "Comandos, respuestas y datos brutos para protocolos BLE/TCP/USB",
|
||||||
|
"chat_markAsUnread": "Marcar como no leído",
|
||||||
|
"repeater_chanUtil": "Utilización del canal",
|
||||||
|
"dialog_connectCompanion": "Conéctate a un compañero para acceder a las funciones de repetidor y servidor de sala.",
|
||||||
|
"dialog_disconnectedTitle": "Desconectado",
|
||||||
|
"dialog_disconnectedMessage": "Te has desconectado de tu compañero.",
|
||||||
|
"contact_connectCompanion": "Conéctate a un compañero para acceder a las funciones del repetidor y del servidor de la sala."
|
||||||
}
|
}
|
||||||
|
|||||||
+515
-52
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
"@@locale": "fr",
|
"@@locale": "fr",
|
||||||
"appTitle": "MeshCore Open",
|
"appTitle": "MeshCore Open",
|
||||||
"nav_contacts": "Contacts",
|
"nav_contacts": "Coordonnées",
|
||||||
"nav_channels": "Canaux",
|
"nav_channels": "Canaux",
|
||||||
"nav_map": "Carte",
|
"nav_map": "Carte",
|
||||||
"common_cancel": "Annuler",
|
"common_cancel": "Annuler",
|
||||||
@@ -104,6 +104,8 @@
|
|||||||
"settings_privacyModeEnabled": "Mode de confidentialité activé",
|
"settings_privacyModeEnabled": "Mode de confidentialité activé",
|
||||||
"settings_privacyModeDisabled": "Mode de confidentialité désactivé",
|
"settings_privacyModeDisabled": "Mode de confidentialité désactivé",
|
||||||
"settings_actions": "Actions",
|
"settings_actions": "Actions",
|
||||||
|
"settings_deleteAllPaths": "Delete All Paths",
|
||||||
|
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
|
||||||
"settings_sendAdvertisement": "S'annoncer",
|
"settings_sendAdvertisement": "S'annoncer",
|
||||||
"settings_sendAdvertisementSubtitle": "Présence diffusée maintenant",
|
"settings_sendAdvertisementSubtitle": "Présence diffusée maintenant",
|
||||||
"settings_advertisementSent": "Annonce envoyée",
|
"settings_advertisementSent": "Annonce envoyée",
|
||||||
@@ -121,7 +123,7 @@
|
|||||||
"settings_appDebugLog": "Journal de débogage de l'application",
|
"settings_appDebugLog": "Journal de débogage de l'application",
|
||||||
"settings_appDebugLogSubtitle": "Messages de débogage de l'application",
|
"settings_appDebugLogSubtitle": "Messages de débogage de l'application",
|
||||||
"settings_about": "À propos",
|
"settings_about": "À propos",
|
||||||
"settings_aboutVersion": "MeshCore Open v{version}",
|
"settings_aboutVersion": "MeshCore Open {version}",
|
||||||
"@settings_aboutVersion": {
|
"@settings_aboutVersion": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"version": {
|
"version": {
|
||||||
@@ -132,7 +134,7 @@
|
|||||||
"settings_aboutLegalese": "Projet MeshCore Open Source 2026",
|
"settings_aboutLegalese": "Projet MeshCore Open Source 2026",
|
||||||
"settings_aboutDescription": "Un client Flutter open source pour les appareils de réseau mesh MeshCore LoRa.",
|
"settings_aboutDescription": "Un client Flutter open source pour les appareils de réseau mesh MeshCore LoRa.",
|
||||||
"settings_infoName": "Nom",
|
"settings_infoName": "Nom",
|
||||||
"settings_infoId": "ID",
|
"settings_infoId": "Numéro d'identification",
|
||||||
"settings_infoStatus": "État",
|
"settings_infoStatus": "État",
|
||||||
"settings_infoBattery": "Batterie",
|
"settings_infoBattery": "Batterie",
|
||||||
"settings_infoPublicKey": "Clé Publique",
|
"settings_infoPublicKey": "Clé Publique",
|
||||||
@@ -143,8 +145,8 @@
|
|||||||
"settings_frequencyHelper": "300,0 - 2 500,0",
|
"settings_frequencyHelper": "300,0 - 2 500,0",
|
||||||
"settings_frequencyInvalid": "Fréquence invalide (300-2500 MHz)",
|
"settings_frequencyInvalid": "Fréquence invalide (300-2500 MHz)",
|
||||||
"settings_bandwidth": "Bande passante",
|
"settings_bandwidth": "Bande passante",
|
||||||
"settings_spreadingFactor": "Facteur de répartition",
|
"settings_spreadingFactor": "Facteur de répartition (SF)",
|
||||||
"settings_codingRate": "Taux de codage",
|
"settings_codingRate": "Taux de codage (CR)",
|
||||||
"settings_txPower": "TX Puissance (dBm)",
|
"settings_txPower": "TX Puissance (dBm)",
|
||||||
"settings_txPowerHelper": "0 - 22",
|
"settings_txPowerHelper": "0 - 22",
|
||||||
"settings_txPowerInvalid": "Puissance TX invalide (0-22 dBm)",
|
"settings_txPowerInvalid": "Puissance TX invalide (0-22 dBm)",
|
||||||
@@ -164,19 +166,19 @@
|
|||||||
"appSettings_themeDark": "Sombre",
|
"appSettings_themeDark": "Sombre",
|
||||||
"appSettings_language": "Langue",
|
"appSettings_language": "Langue",
|
||||||
"appSettings_languageSystem": "Par défaut du système",
|
"appSettings_languageSystem": "Par défaut du système",
|
||||||
"appSettings_languageEn": "English",
|
"appSettings_languageEn": "Anglais",
|
||||||
"appSettings_languageFr": "Français",
|
"appSettings_languageFr": "Français",
|
||||||
"appSettings_languageEs": "Español",
|
"appSettings_languageEs": "Espagnol",
|
||||||
"appSettings_languageDe": "Deutsch",
|
"appSettings_languageDe": "Allemand",
|
||||||
"appSettings_languagePl": "Polski",
|
"appSettings_languagePl": "Polonais",
|
||||||
"appSettings_languageSl": "Slovenščina",
|
"appSettings_languageSl": "Sloveno",
|
||||||
"appSettings_languagePt": "Português",
|
"appSettings_languagePt": "Portugais",
|
||||||
"appSettings_languageIt": "Italiano",
|
"appSettings_languageIt": "Italien",
|
||||||
"appSettings_languageZh": "中文",
|
"appSettings_languageZh": "Chinois",
|
||||||
"appSettings_languageSv": "Svenska",
|
"appSettings_languageSv": "Suédois",
|
||||||
"appSettings_languageNl": "Nederlands",
|
"appSettings_languageNl": "Néerlandais",
|
||||||
"appSettings_languageSk": "Slovenčina",
|
"appSettings_languageSk": "Slovène",
|
||||||
"appSettings_languageBg": "Български",
|
"appSettings_languageBg": "Bulgare",
|
||||||
"appSettings_notifications": "Notifications",
|
"appSettings_notifications": "Notifications",
|
||||||
"appSettings_enableNotifications": "Activer les Notifications",
|
"appSettings_enableNotifications": "Activer les Notifications",
|
||||||
"appSettings_enableNotificationsSubtitle": "Recevoir des notifications pour les messages et les annonces",
|
"appSettings_enableNotificationsSubtitle": "Recevoir des notifications pour les messages et les annonces",
|
||||||
@@ -254,7 +256,7 @@
|
|||||||
"appSettings_appDebugLoggingSubtitle": "Enregistrez les messages de débogage de l'application Log pour le dépannage.",
|
"appSettings_appDebugLoggingSubtitle": "Enregistrez les messages de débogage de l'application Log pour le dépannage.",
|
||||||
"appSettings_appDebugLoggingEnabled": "Journalisation de débogage de l'application activée",
|
"appSettings_appDebugLoggingEnabled": "Journalisation de débogage de l'application activée",
|
||||||
"appSettings_appDebugLoggingDisabled": "Le débogage de l'application est désactivé.",
|
"appSettings_appDebugLoggingDisabled": "Le débogage de l'application est désactivé.",
|
||||||
"contacts_title": "Contacts",
|
"contacts_title": "Coordonnées",
|
||||||
"contacts_noContacts": "Aucun contact trouvé.",
|
"contacts_noContacts": "Aucun contact trouvé.",
|
||||||
"contacts_contactsWillAppear": "Les contacts apparaîtront lorsque les appareils font leur annonce.",
|
"contacts_contactsWillAppear": "Les contacts apparaîtront lorsque les appareils font leur annonce.",
|
||||||
"contacts_searchContacts": "Rechercher des contacts...",
|
"contacts_searchContacts": "Rechercher des contacts...",
|
||||||
@@ -285,6 +287,7 @@
|
|||||||
"contacts_newGroup": "Nouveau Groupe",
|
"contacts_newGroup": "Nouveau Groupe",
|
||||||
"contacts_groupName": "Nom du groupe",
|
"contacts_groupName": "Nom du groupe",
|
||||||
"contacts_groupNameRequired": "Le nom du groupe est obligatoire.",
|
"contacts_groupNameRequired": "Le nom du groupe est obligatoire.",
|
||||||
|
"contacts_groupNameReserved": "Ce nom de groupe est réservé",
|
||||||
"contacts_groupAlreadyExists": "Le groupe \"{name}\" existe déjà.",
|
"contacts_groupAlreadyExists": "Le groupe \"{name}\" existe déjà.",
|
||||||
"@contacts_groupAlreadyExists": {
|
"@contacts_groupAlreadyExists": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -297,7 +300,7 @@
|
|||||||
"contacts_noContactsMatchFilter": "Aucun contact ne correspond à votre filtre.",
|
"contacts_noContactsMatchFilter": "Aucun contact ne correspond à votre filtre.",
|
||||||
"contacts_noMembers": "Aucun membre",
|
"contacts_noMembers": "Aucun membre",
|
||||||
"contacts_lastSeenNow": "Vu maintenant",
|
"contacts_lastSeenNow": "Vu maintenant",
|
||||||
"contacts_lastSeenMinsAgo": "~ {minutes} min.",
|
"contacts_lastSeenMinsAgo": "~ {minutes} minutes.",
|
||||||
"@contacts_lastSeenMinsAgo": {
|
"@contacts_lastSeenMinsAgo": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"minutes": {
|
"minutes": {
|
||||||
@@ -336,11 +339,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_hashtagChannel": "Canal avec hashtag",
|
|
||||||
"channels_public": "Public",
|
"channels_public": "Public",
|
||||||
"channels_private": "Privé",
|
"channels_private": "Privé",
|
||||||
"channels_publicChannel": "Canal public",
|
|
||||||
"channels_privateChannel": "Canal privé",
|
|
||||||
"channels_editChannel": "Modifier le canal",
|
"channels_editChannel": "Modifier le canal",
|
||||||
"channels_muteChannel": "Désactiver les notifications du canal",
|
"channels_muteChannel": "Désactiver les notifications du canal",
|
||||||
"channels_unmuteChannel": "Réactiver les notifications du canal",
|
"channels_unmuteChannel": "Réactiver les notifications du canal",
|
||||||
@@ -366,7 +366,7 @@
|
|||||||
"channels_channelName": "Nom du canal",
|
"channels_channelName": "Nom du canal",
|
||||||
"channels_usePublicChannel": "Utiliser le canal public",
|
"channels_usePublicChannel": "Utiliser le canal public",
|
||||||
"channels_standardPublicPsk": "PSK public standard",
|
"channels_standardPublicPsk": "PSK public standard",
|
||||||
"channels_pskHex": "PSK (Hex)",
|
"channels_pskHex": "PSK (Hexadécimal)",
|
||||||
"channels_generateRandomPsk": "Générer une clé de modulation PSK aléatoire",
|
"channels_generateRandomPsk": "Générer une clé de modulation PSK aléatoire",
|
||||||
"channels_enterChannelName": "Veuillez entrer un nom de canal",
|
"channels_enterChannelName": "Veuillez entrer un nom de canal",
|
||||||
"channels_pskMustBe32Hex": "Le PKS doit être composé de 32 caractères hexadécimaux.",
|
"channels_pskMustBe32Hex": "Le PKS doit être composé de 32 caractères hexadécimaux.",
|
||||||
@@ -387,6 +387,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_smazCompression": "Compression SMAZ",
|
"channels_smazCompression": "Compression SMAZ",
|
||||||
|
"channels_cyr2latCompression": "Compression Cyr2Lat",
|
||||||
|
"channels_cyr2latCompressionDscr": "Remplace certains caractères cyrilliques par des caractères latins lors de l'envoi.",
|
||||||
|
"channels_cyr2latSettingsHeading": "Paramètres Cyr2Lat",
|
||||||
|
"channels_cyr2latSettingsSubheading": "Liste des remplacements",
|
||||||
|
"channels_cyr2latSettingsDscr": "Modifier la configuration JSON des remplacements de caractères",
|
||||||
|
"channels_cyr2latSettingsDialogHint": "Tableau de remplacement JSON",
|
||||||
|
"channels_cyr2latSettingsDialogWrongJSON": "JSON incorrect : {error}",
|
||||||
|
"settings_cyr2latProfileAdd": "Ajouter un profil Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileName": "Nom du profil",
|
||||||
|
"settings_cyr2latProfileNameEmpty": "Le nom du profil ne peut pas être vide",
|
||||||
|
"settings_cyr2latProfileAdded": "Profil ajouté avec succès",
|
||||||
|
"settings_cyr2latProfileUpdated": "Profil mis à jour avec succès",
|
||||||
|
"settings_cyr2latProfileEdit": "Modifier le profil Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileDelete": "Supprimer le profil Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileDeleted": "Profil supprimé avec succès",
|
||||||
|
"settings_cyr2latProfileDeleteDscr": "Êtes-vous sûr de vouloir supprimer le profil \"{name}\"?",
|
||||||
"channels_channelUpdated": "Le canal \"{name}\" a été mis à jour",
|
"channels_channelUpdated": "Le canal \"{name}\" a été mis à jour",
|
||||||
"@channels_channelUpdated": {
|
"@channels_channelUpdated": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -495,7 +511,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debugFrame_textMessageHeader": "Message :",
|
"debugFrame_textMessageHeader": "Message :",
|
||||||
"debugFrame_destinationPubKey": "- Destination PubKey: {pubKey}",
|
"debugFrame_destinationPubKey": "- Clé publique de destination : {pubKey}",
|
||||||
"@debugFrame_destinationPubKey": {
|
"@debugFrame_destinationPubKey": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"pubKey": {
|
"pubKey": {
|
||||||
@@ -566,7 +582,7 @@
|
|||||||
"chat_clearPath": "Effacer le chemin",
|
"chat_clearPath": "Effacer le chemin",
|
||||||
"chat_clearPathSubtitle": "Forcer la redécouverte lors de la prochaine envoi",
|
"chat_clearPathSubtitle": "Forcer la redécouverte lors de la prochaine envoi",
|
||||||
"chat_pathCleared": "Le chemin est dégagé. Le prochain message redécouvrira le tracé.",
|
"chat_pathCleared": "Le chemin est dégagé. Le prochain message redécouvrira le tracé.",
|
||||||
"chat_floodModeSubtitle": "Utiliser le commutateur de routage dans la barre d'application",
|
"chat_floodModeSubtitle": "Désactive l'apprentissage du chemin (à éviter). Utiliser le commutateur de routage dans la barre d'application pour rebasculer en mode auto par la suite.",
|
||||||
"chat_floodModeEnabled": "Le mode envoi à tout le réseau est activé. Changer via l'icône de routage dans la barre d'outils.",
|
"chat_floodModeEnabled": "Le mode envoi à tout le réseau est activé. Changer via l'icône de routage dans la barre d'outils.",
|
||||||
"chat_fullPath": "Chemin complet",
|
"chat_fullPath": "Chemin complet",
|
||||||
"chat_pathDetailsNotAvailable": "Les détails du chemin ne sont pas encore disponibles. Essayez d'envoyer un message pour rafraîchir.",
|
"chat_pathDetailsNotAvailable": "Les détails du chemin ne sont pas encore disponibles. Essayez d'envoyer un message pour rafraîchir.",
|
||||||
@@ -640,9 +656,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"map_chat": "Chat",
|
"map_chat": "Conversation",
|
||||||
"map_repeater": "Répéteur",
|
"map_repeater": "Répéteur",
|
||||||
"map_room": "Salle",
|
"map_room": "Room Server",
|
||||||
"map_sensor": "Capteur",
|
"map_sensor": "Capteur",
|
||||||
"map_pinDm": "Clé (DM)",
|
"map_pinDm": "Clé (DM)",
|
||||||
"map_pinPrivate": "Verrouiller (Privé)",
|
"map_pinPrivate": "Verrouiller (Privé)",
|
||||||
@@ -681,7 +697,7 @@
|
|||||||
"map_showSharedMarkers": "Afficher les marqueurs partagés",
|
"map_showSharedMarkers": "Afficher les marqueurs partagés",
|
||||||
"map_lastSeenTime": "Dernière fois vu",
|
"map_lastSeenTime": "Dernière fois vu",
|
||||||
"map_sharedPin": "Clé partagée",
|
"map_sharedPin": "Clé partagée",
|
||||||
"map_joinRoom": "Rejoindre la salle",
|
"map_joinRoom": "Rejoindre le room server",
|
||||||
"map_manageRepeater": "Gérer le répéteur",
|
"map_manageRepeater": "Gérer le répéteur",
|
||||||
"mapCache_title": "Cache de Carte Hors Ligne",
|
"mapCache_title": "Cache de Carte Hors Ligne",
|
||||||
"mapCache_selectAreaFirst": "Sélectionner une zone pour la mise en cache en premier",
|
"mapCache_selectAreaFirst": "Sélectionner une zone pour la mise en cache en premier",
|
||||||
@@ -864,7 +880,7 @@
|
|||||||
"path_labelHexPrefixes": "Préfixes hexadécimaux",
|
"path_labelHexPrefixes": "Préfixes hexadécimaux",
|
||||||
"path_helperMaxHops": "Max 64 sauts. Chaque préfixe fait 2 caractères hexadécimaux (1 octet)",
|
"path_helperMaxHops": "Max 64 sauts. Chaque préfixe fait 2 caractères hexadécimaux (1 octet)",
|
||||||
"path_selectFromContacts": "Sélectionner à partir des contacts :",
|
"path_selectFromContacts": "Sélectionner à partir des contacts :",
|
||||||
"path_noRepeatersFound": "Aucun répéteur ou serveur de salle n'a été trouvé.",
|
"path_noRepeatersFound": "Aucun répéteur ou room server n'a été trouvé.",
|
||||||
"path_customPathsRequire": "Les chemins personnalisés nécessitent des sauts intermédiaires qui peuvent transmettre des messages.",
|
"path_customPathsRequire": "Les chemins personnalisés nécessitent des sauts intermédiaires qui peuvent transmettre des messages.",
|
||||||
"path_invalidHexPrefixes": "Préfixes hexadécimaux invalides : {prefixes}",
|
"path_invalidHexPrefixes": "Préfixes hexadécimaux invalides : {prefixes}",
|
||||||
"@path_invalidHexPrefixes": {
|
"@path_invalidHexPrefixes": {
|
||||||
@@ -993,17 +1009,17 @@
|
|||||||
"repeater_frequencyMhz": "Fréquence (MHz)",
|
"repeater_frequencyMhz": "Fréquence (MHz)",
|
||||||
"repeater_frequencyHelper": "300-2500 MHz",
|
"repeater_frequencyHelper": "300-2500 MHz",
|
||||||
"repeater_txPower": "TX Puissance",
|
"repeater_txPower": "TX Puissance",
|
||||||
"repeater_txPowerHelper": "1-30 dBm",
|
"repeater_txPowerHelper": "1 à 30 dBm",
|
||||||
"repeater_bandwidth": "Bande passante",
|
"repeater_bandwidth": "Bande passante",
|
||||||
"repeater_spreadingFactor": "Facteur de répartition",
|
"repeater_spreadingFactor": "Facteur de répartition (SF)",
|
||||||
"repeater_codingRate": "Taux de codage",
|
"repeater_codingRate": "Taux de codage (CR)",
|
||||||
"repeater_locationSettings": "Paramètres de localisation",
|
"repeater_locationSettings": "Paramètres de localisation",
|
||||||
"repeater_latitude": "Latitude",
|
"repeater_latitude": "Latitude",
|
||||||
"repeater_latitudeHelper": "Degrés décimaux (par exemple, 37.7749)",
|
"repeater_latitudeHelper": "Degrés décimaux (par exemple, 37.7749)",
|
||||||
"repeater_longitude": "Longitude",
|
"repeater_longitude": "Longitude",
|
||||||
"repeater_longitudeHelper": "Degrés décimaux (par exemple, -122,4194)",
|
"repeater_longitudeHelper": "Degrés décimaux (par exemple, -122,4194)",
|
||||||
"repeater_features": "Fonctionnalités",
|
"repeater_features": "Fonctionnalités",
|
||||||
"repeater_packetForwarding": "Transfert de paquets",
|
"repeater_packetForwarding": "Mode répéteur",
|
||||||
"repeater_packetForwardingSubtitle": "Activer le répéteur pour transmettre des paquets",
|
"repeater_packetForwardingSubtitle": "Activer le répéteur pour transmettre des paquets",
|
||||||
"repeater_guestAccess": "Accès Invité",
|
"repeater_guestAccess": "Accès Invité",
|
||||||
"repeater_guestAccessSubtitle": "Autoriser l'accès invité en lecture seule",
|
"repeater_guestAccessSubtitle": "Autoriser l'accès invité en lecture seule",
|
||||||
@@ -1058,6 +1074,81 @@
|
|||||||
},
|
},
|
||||||
"repeater_confirm": "Confirmer",
|
"repeater_confirm": "Confirmer",
|
||||||
"repeater_settingsSaved": "Les paramètres ont été enregistrés avec succès.",
|
"repeater_settingsSaved": "Les paramètres ont été enregistrés avec succès.",
|
||||||
|
"repeater_rxGain": "Augmentation du rendement de RX",
|
||||||
|
"repeater_rxGainHelper": "Meilleure sensibilité, consommation de courant plus élevée (uniquement pour les modèles SX1262/SX1268)",
|
||||||
|
"repeater_refreshRxGain": "Renforcer les gains de RX grâce à la mise à jour",
|
||||||
|
"repeater_multiAcks": "Plusieurs accusés de réception",
|
||||||
|
"repeater_multiAcksSubtitle": "Valider les messages via plusieurs chemins pour une meilleure livraison.",
|
||||||
|
"repeater_refreshMultiAcks": "Renvoyer plusieurs accusés de réception",
|
||||||
|
"repeater_networkHealth": "Santé du réseau",
|
||||||
|
"repeater_loopDetect": "Détection de boucles",
|
||||||
|
"repeater_loopDetectHelper": "Envoyer des paquets de données qui semblent former des boucles de routage.",
|
||||||
|
"repeater_loopDetectOff": "Prix",
|
||||||
|
"repeater_loopDetectMinimal": "Minimal",
|
||||||
|
"repeater_loopDetectModerate": "Modéré",
|
||||||
|
"repeater_loopDetectStrict": "Strict",
|
||||||
|
"repeater_dutyCycle": "Cycle de fonctionnement",
|
||||||
|
"repeater_dutyCycleHelper": "Pourcentage maximal de temps d'antenne",
|
||||||
|
"repeater_dutyCyclePercent": "{percent}%",
|
||||||
|
"@repeater_dutyCyclePercent": {
|
||||||
|
"placeholders": {
|
||||||
|
"percent": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_ownerInfo": "Informations sur l'opérateur",
|
||||||
|
"repeater_ownerInfoHelper": "Métadonnées publiques pour cet émetteur",
|
||||||
|
"repeater_refreshOwnerInfo": "Rafraîchir les informations sur l'opérateur",
|
||||||
|
"repeater_floodMax": "Nombre maximal de sauts lors des inondations",
|
||||||
|
"repeater_floodMaxHelper": "Nombre maximal de paquets de données qu'un flux peut transmettre (0-64)",
|
||||||
|
"repeater_advancedSettings": "Avancé",
|
||||||
|
"repeater_advancedSettingsSubtitle": "Molettes de réglage pour les opérateurs expérimentés",
|
||||||
|
"repeater_pathHashMode": "Mode de hachage de chemin",
|
||||||
|
"repeater_pathHashModeHelper": "Octets utilisés pour encoder l'ID de ce routeur dans les balises de détection de flux/boucles. 0 = 1 octet (256 ID, jusqu'à 64 sauts), 1 = 2 octets (65 000 ID, jusqu'à 32 sauts), 2 = 3 octets (16 millions d'ID, jusqu'à 21 sauts). Les versions 1.13 et antérieures utilisent des chemins multi-octets ; à partir de la version 1.14, cela n'est plus nécessaire.",
|
||||||
|
"repeater_txDelay": "Retard dû aux inondations à Texas",
|
||||||
|
"repeater_txDelayHelper": "Rétransmettre l'espacement pour le trafic de secours en cas de inondation, en multipliant le temps d'émission du paquet (0-2, valeur par défaut : 0,5). Une valeur plus élevée signifie moins de collisions, mais une vitesse de transmission plus lente.",
|
||||||
|
"repeater_directTxDelay": "Retard de transmission direct",
|
||||||
|
"repeater_directTxDelayHelper": "Rétransmettre l'espacement pour le trafic direct (non-inondation), en multipliant le temps de transmission des paquets (0-2, valeur par défaut : 0,3).",
|
||||||
|
"repeater_intThresh": "Seuil de perturbation",
|
||||||
|
"repeater_intThreshHelper": "Seuil dépassé pour la calibration du niveau de bruit de la radio, afin de rejeter les interférences supérieures à ce niveau. 0 désactive – ne mettez cette valeur que si vous constatez des erreurs RX dans une bande de fréquences bruyante.",
|
||||||
|
"repeater_agcResetInterval": "Interval de réinitialisation de l'AGC",
|
||||||
|
"repeater_agcResetIntervalHelper": "À quelle fréquence réinitialiser le contrôle automatique du gain de la radio pour revenir à un état normal ? Chaque seconde, ou à chaque multiple de 4. Désactiver la réinitialisation périodique avec 0.",
|
||||||
|
"repeater_actionsTitle": "Actions",
|
||||||
|
"repeater_sendAdvert": "Envoyer une publicité sur les inondations",
|
||||||
|
"repeater_sendAdvertSubtitle": "Diffuser une publicité sur les inondations via le réseau.",
|
||||||
|
"repeater_sendAdvertZeroHop": "Envoyer une publicité sans intermédiaire",
|
||||||
|
"repeater_sendAdvertZeroHopSubtitle": "Diffuser une publicité d'un seul saut (sans relais)",
|
||||||
|
"repeater_clockSync": "Synchroniser l'heure maintenant",
|
||||||
|
"repeater_clockSyncSubtitle": "Envoyez l'heure de votre téléphone au répéteur.",
|
||||||
|
"repeater_actionSucceeded": "{action} a réussi",
|
||||||
|
"@repeater_actionSucceeded": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_actionFailed": "{action} a échoué : {error}",
|
||||||
|
"@repeater_actionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_settingsSavedRebootNeeded": "Paramètres sauvegardés — redémarrer le répéteur pour appliquer les modifications.",
|
||||||
|
"repeater_settingsPartialFailure": "Certaines configurations ont échoué : {failures}",
|
||||||
|
"@repeater_settingsPartialFailure": {
|
||||||
|
"placeholders": {
|
||||||
|
"failures": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repeater_errorSavingSettings": "Erreur lors de la sauvegarde des paramètres : {error}",
|
"repeater_errorSavingSettings": "Erreur lors de la sauvegarde des paramètres : {error}",
|
||||||
"@repeater_errorSavingSettings": {
|
"@repeater_errorSavingSettings": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1069,11 +1160,9 @@
|
|||||||
"repeater_refreshBasicSettings": "Rafraîchir les paramètres de base",
|
"repeater_refreshBasicSettings": "Rafraîchir les paramètres de base",
|
||||||
"repeater_refreshRadioSettings": "Rafraîchir les paramètres Radio",
|
"repeater_refreshRadioSettings": "Rafraîchir les paramètres Radio",
|
||||||
"repeater_refreshTxPower": "Rafraîchir la tension TX",
|
"repeater_refreshTxPower": "Rafraîchir la tension TX",
|
||||||
"repeater_refreshLocationSettings": "Rafraîchir les paramètres de localisation",
|
|
||||||
"repeater_refreshPacketForwarding": "Rafraîchir le routage des paquets",
|
"repeater_refreshPacketForwarding": "Rafraîchir le routage des paquets",
|
||||||
"repeater_refreshGuestAccess": "Rafraîchir l'accès invité",
|
"repeater_refreshGuestAccess": "Rafraîchir l'accès invité",
|
||||||
"repeater_refreshPrivacyMode": "Rafraîchir le Mode Confidentialité",
|
"repeater_refreshPrivacyMode": "Rafraîchir le Mode Confidentialité",
|
||||||
"repeater_refreshAdvertisementSettings": "Rafraîchir les Paramètres des annonces",
|
|
||||||
"repeater_refreshed": "{label} rafraîchi",
|
"repeater_refreshed": "{label} rafraîchi",
|
||||||
"@repeater_refreshed": {
|
"@repeater_refreshed": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1351,7 +1440,7 @@
|
|||||||
"listFilter_all": "Tout",
|
"listFilter_all": "Tout",
|
||||||
"listFilter_users": "Utilisateurs",
|
"listFilter_users": "Utilisateurs",
|
||||||
"listFilter_repeaters": "Répéteurs",
|
"listFilter_repeaters": "Répéteurs",
|
||||||
"listFilter_roomServers": "Room servers",
|
"listFilter_roomServers": "Serveurs de salle",
|
||||||
"listFilter_unreadOnly": "Messages non lus seulement",
|
"listFilter_unreadOnly": "Messages non lus seulement",
|
||||||
"listFilter_newGroup": "Nouveau groupe",
|
"listFilter_newGroup": "Nouveau groupe",
|
||||||
"@neighbors_errorLoading": {
|
"@neighbors_errorLoading": {
|
||||||
@@ -1376,7 +1465,7 @@
|
|||||||
"channels_joinPublicChannelDesc": "Tout le monde peut rejoindre ce canal.",
|
"channels_joinPublicChannelDesc": "Tout le monde peut rejoindre ce canal.",
|
||||||
"channels_joinHashtagChannel": "Rejoindre un Canal Hashtag",
|
"channels_joinHashtagChannel": "Rejoindre un Canal Hashtag",
|
||||||
"channels_joinHashtagChannelDesc": "N'importe qui peut rejoindre les canaux #hashtag.",
|
"channels_joinHashtagChannelDesc": "N'importe qui peut rejoindre les canaux #hashtag.",
|
||||||
"channels_scanQrCode": "Scanner un code QR",
|
"channels_scanQrCode": "Scanner un QR code",
|
||||||
"channels_scanQrCodeComingSoon": "Bientôt disponible",
|
"channels_scanQrCodeComingSoon": "Bientôt disponible",
|
||||||
"channels_enterHashtag": "Entrez le hashtag",
|
"channels_enterHashtag": "Entrez le hashtag",
|
||||||
"channels_hashtagHint": "ex. #equipe",
|
"channels_hashtagHint": "ex. #equipe",
|
||||||
@@ -1458,15 +1547,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common_ok": "OK",
|
"common_ok": "D'accord",
|
||||||
"community_title": "Communauté",
|
"community_title": "Communauté",
|
||||||
"community_create": "Créer une Communauté",
|
"community_create": "Créer une Communauté",
|
||||||
"community_createDesc": "Créer une nouvelle communauté et la partager via QR code.",
|
"community_createDesc": "Créer une nouvelle communauté et la partager via QR code.",
|
||||||
"community_join": "Rejoindre",
|
"community_join": "Rejoindre",
|
||||||
"community_joinTitle": "Rejoindre la communauté",
|
"community_joinTitle": "Rejoindre la communauté",
|
||||||
"community_joinConfirmation": "Souhaitez-vous rejoindre la communauté \"{name}\" ?",
|
"community_joinConfirmation": "Souhaitez-vous rejoindre la communauté \"{name}\" ?",
|
||||||
"community_scanQr": "Scanner la communauté QR",
|
"community_scanQr": "Scanner un QR code de communauté",
|
||||||
"community_scanInstructions": "Pointez l'appareil photo vers un code QR communautaire.",
|
"community_scanInstructions": "Pointez l'appareil photo vers un QR code de communauté.",
|
||||||
"community_showQr": "Afficher le QR Code",
|
"community_showQr": "Afficher le QR Code",
|
||||||
"community_publicChannel": "Communauté Publique",
|
"community_publicChannel": "Communauté Publique",
|
||||||
"community_hashtagChannel": "Hashtag Communauté",
|
"community_hashtagChannel": "Hashtag Communauté",
|
||||||
@@ -1477,13 +1566,13 @@
|
|||||||
"community_qrTitle": "Partager Communauté",
|
"community_qrTitle": "Partager Communauté",
|
||||||
"community_qrInstructions": "Scanner ce QR code pour rejoindre {name}",
|
"community_qrInstructions": "Scanner ce QR code pour rejoindre {name}",
|
||||||
"community_hashtagPrivacyHint": "Les canaux hashtag de la communauté ne sont accessibles qu'aux membres de la communauté",
|
"community_hashtagPrivacyHint": "Les canaux hashtag de la communauté ne sont accessibles qu'aux membres de la communauté",
|
||||||
"community_invalidQrCode": "Code QR de communauté non valide",
|
"community_invalidQrCode": "QR code de communauté non valide",
|
||||||
"community_alreadyMember": "Déjà membre",
|
"community_alreadyMember": "Déjà membre",
|
||||||
"community_alreadyMemberMessage": "Vous êtes déjà membre de \"{name}\".",
|
"community_alreadyMemberMessage": "Vous êtes déjà membre de \"{name}\".",
|
||||||
"community_addPublicChannel": "Ajouter un Canal Public de la Communauté",
|
"community_addPublicChannel": "Ajouter un Canal Public de la Communauté",
|
||||||
"community_addPublicChannelHint": "Ajouter automatiquement le canal public pour cette communauté",
|
"community_addPublicChannelHint": "Ajouter automatiquement le canal public pour cette communauté",
|
||||||
"community_noCommunities": "Aucun groupe n'a été rejoint pour le moment.",
|
"community_noCommunities": "Aucun groupe n'a été rejoint pour le moment.",
|
||||||
"community_scanOrCreate": "Scanner un code QR ou créer une communauté pour commencer",
|
"community_scanOrCreate": "Scanner un QR code ou créer une communauté pour commencer",
|
||||||
"community_manageCommunities": "Gérer les Communautés",
|
"community_manageCommunities": "Gérer les Communautés",
|
||||||
"community_delete": "Quitter la communauté",
|
"community_delete": "Quitter la communauté",
|
||||||
"community_deleteConfirm": "Quitter \"{name}\" ?",
|
"community_deleteConfirm": "Quitter \"{name}\" ?",
|
||||||
@@ -1533,10 +1622,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"community_regenerateSecret": "Régénérer le secret",
|
"community_regenerateSecret": "Régénérer le secret",
|
||||||
"community_regenerateSecretConfirm": "Régénérer la clé secrète pour \"{name}\" ? Tous les membres devront scanner le nouveau code QR pour continuer à communiquer.",
|
"community_regenerateSecretConfirm": "Régénérer la clé secrète pour \"{name}\" ? Tous les membres devront scanner le nouveau QR code pour continuer à communiquer.",
|
||||||
"community_regenerate": "Régénérer",
|
"community_regenerate": "Régénérer",
|
||||||
"community_secretRegenerated": "Mot de passe secret régénéré pour \"{name}\"",
|
"community_secretRegenerated": "Mot de passe secret régénéré pour \"{name}\"",
|
||||||
"community_scanToUpdateSecret": "Scanner le nouveau code QR pour mettre à jour le mot de passe pour \"{name}\"",
|
"community_scanToUpdateSecret": "Scanner le nouveau QR code pour mettre à jour le mot de passe pour \"{name}\"",
|
||||||
"community_updateSecret": "Mettre à jour le secret",
|
"community_updateSecret": "Mettre à jour le secret",
|
||||||
"community_secretUpdated": "Modification secrète mise à jour pour \"{name}\"",
|
"community_secretUpdated": "Modification secrète mise à jour pour \"{name}\"",
|
||||||
"@contacts_pathTraceTo": {
|
"@contacts_pathTraceTo": {
|
||||||
@@ -1553,11 +1642,11 @@
|
|||||||
"contacts_pathTrace": "Traçage de chemin",
|
"contacts_pathTrace": "Traçage de chemin",
|
||||||
"contacts_repeaterPathTrace": "Tracer le chemin vers le répéteur",
|
"contacts_repeaterPathTrace": "Tracer le chemin vers le répéteur",
|
||||||
"contacts_repeaterPing": "Pinguer le répéteur",
|
"contacts_repeaterPing": "Pinguer le répéteur",
|
||||||
"contacts_roomPathTrace": "Traçage du chemin vers le serveur de la salle",
|
"contacts_roomPathTrace": "Traçage du chemin vers le room server",
|
||||||
"contacts_chatTraceRoute": "Tracer le chemin",
|
"contacts_chatTraceRoute": "Tracer le chemin",
|
||||||
"contacts_pathTraceTo": "Tracer l'itinéraire vers {name}",
|
"contacts_pathTraceTo": "Tracer l'itinéraire vers {name}",
|
||||||
"contacts_ping": "Ping",
|
"contacts_ping": "Ping",
|
||||||
"contacts_roomPing": "Pinguer le serveur de la salle",
|
"contacts_roomPing": "Pinguer le room server",
|
||||||
"contacts_invalidAdvertFormat": "Données de contact non valides",
|
"contacts_invalidAdvertFormat": "Données de contact non valides",
|
||||||
"appSettings_languageUk": "Ukrainien",
|
"appSettings_languageUk": "Ukrainien",
|
||||||
"appSettings_languageRu": "Russe",
|
"appSettings_languageRu": "Russe",
|
||||||
@@ -1582,12 +1671,12 @@
|
|||||||
"notification_newNodesCount": "{count} {count, plural, =1{nouveau nœud} other{nouveaux nœuds}}",
|
"notification_newNodesCount": "{count} {count, plural, =1{nouveau nœud} other{nouveaux nœuds}}",
|
||||||
"notification_newTypeDiscovered": "Nouveau {contactType} découvert",
|
"notification_newTypeDiscovered": "Nouveau {contactType} découvert",
|
||||||
"notification_receivedNewMessage": "Nouveau message reçu",
|
"notification_receivedNewMessage": "Nouveau message reçu",
|
||||||
"settings_gpxExportRepeaters": "Exporter les répéteurs / serveur de salle au format GPX",
|
"settings_gpxExportRepeaters": "Exporter les répéteurs / room servers au format GPX",
|
||||||
"settings_gpxExportRepeatersSubtitle": "Exporte les répéteurs / roomserver avec une localisation vers un fichier GPX.",
|
"settings_gpxExportRepeatersSubtitle": "Exporte les répéteurs / roomserver avec une localisation vers un fichier GPX.",
|
||||||
"settings_gpxExportNoContacts": "Aucun contact à exporter.",
|
"settings_gpxExportNoContacts": "Aucun contact à exporter.",
|
||||||
"settings_gpxExportNotAvailable": "Non pris en charge sur votre appareil/Système d'exploitation",
|
"settings_gpxExportNotAvailable": "Non pris en charge sur votre appareil/Système d'exploitation",
|
||||||
"settings_gpxExportError": "Une erreur s'est produite lors de l'exportation.",
|
"settings_gpxExportError": "Une erreur s'est produite lors de l'exportation.",
|
||||||
"settings_gpxExportRepeatersRoom": "Emplacements des serveurs de répéteur et de salle",
|
"settings_gpxExportRepeatersRoom": "Emplacements des répéteurs et room servers",
|
||||||
"settings_gpxExportContacts": "Exporter les compagnons au format GPX",
|
"settings_gpxExportContacts": "Exporter les compagnons au format GPX",
|
||||||
"settings_gpxExportAll": "Exporter tous les contacts au format GPX",
|
"settings_gpxExportAll": "Exporter tous les contacts au format GPX",
|
||||||
"settings_gpxExportAllSubtitle": "Exporte tous les contacts avec une localisation vers un fichier GPX.",
|
"settings_gpxExportAllSubtitle": "Exporte tous les contacts avec une localisation vers un fichier GPX.",
|
||||||
@@ -1799,15 +1888,15 @@
|
|||||||
"contacts_unread": "Non lu",
|
"contacts_unread": "Non lu",
|
||||||
"contacts_searchFavorites": "Rechercher {number}{str} Favoris...",
|
"contacts_searchFavorites": "Rechercher {number}{str} Favoris...",
|
||||||
"contacts_searchUsers": "Rechercher {number}{str} utilisateurs...",
|
"contacts_searchUsers": "Rechercher {number}{str} utilisateurs...",
|
||||||
"contacts_searchRoomServers": "Rechercher {number}{str} serveurs de salle...",
|
"contacts_searchRoomServers": "Rechercher {number}{str} room server...",
|
||||||
"contacts_searchRepeaters": "Rechercher {number}{str} Répéteurs...",
|
"contacts_searchRepeaters": "Rechercher {number}{str} Répéteurs...",
|
||||||
"contacts_searchContactsNoNumber": "Rechercher des contacts...",
|
"contacts_searchContactsNoNumber": "Rechercher des contacts...",
|
||||||
"settings_contactSettings": "Paramètres de contact",
|
"settings_contactSettings": "Paramètres de contact",
|
||||||
"settings_contactSettingsSubtitle": "Paramètres pour l'ajout de contacts",
|
"settings_contactSettingsSubtitle": "Paramètres pour l'ajout de contacts",
|
||||||
"contactsSettings_autoAddRepeatersTitle": "Ajouter automatiquement les répéteurs",
|
"contactsSettings_autoAddRepeatersTitle": "Ajouter automatiquement les répéteurs",
|
||||||
"contactsSettings_autoAddRepeatersSubtitle": "Autoriser le compagnon à ajouter automatiquement les répéteurs découverts",
|
"contactsSettings_autoAddRepeatersSubtitle": "Autoriser le compagnon à ajouter automatiquement les répéteurs découverts",
|
||||||
"contactsSettings_autoAddRoomServersTitle": "Ajouter automatiquement les serveurs de salle",
|
"contactsSettings_autoAddRoomServersTitle": "Ajouter automatiquement les room servers",
|
||||||
"contactsSettings_autoAddRoomServersSubtitle": "Autoriser le compagnon à ajouter automatiquement les serveurs de salles découverts",
|
"contactsSettings_autoAddRoomServersSubtitle": "Autoriser le compagnon à ajouter automatiquement les room servers découverts",
|
||||||
"contactsSettings_otherTitle": "Autres paramètres liés aux contacts",
|
"contactsSettings_otherTitle": "Autres paramètres liés aux contacts",
|
||||||
"contactsSettings_title": "Paramètres des contacts",
|
"contactsSettings_title": "Paramètres des contacts",
|
||||||
"contactsSettings_autoAddUsersTitle": "Ajouter automatiquement les utilisateurs",
|
"contactsSettings_autoAddUsersTitle": "Ajouter automatiquement les utilisateurs",
|
||||||
@@ -1859,5 +1948,379 @@
|
|||||||
"usbConnectionFailed": "Échec de la connexion USB : {error}",
|
"usbConnectionFailed": "Échec de la connexion USB : {error}",
|
||||||
"usbStatus_connecting": "Connexion au périphérique USB...",
|
"usbStatus_connecting": "Connexion au périphérique USB...",
|
||||||
"usbStatus_searching": "Recherche de périphériques USB...",
|
"usbStatus_searching": "Recherche de périphériques USB...",
|
||||||
"usbErrorConnectTimedOut": "La connexion a expiré. Assurez-vous que l'appareil dispose du firmware USB Companion."
|
"usbErrorConnectTimedOut": "La connexion a expiré. Assurez-vous que l'appareil dispose du firmware USB Companion.",
|
||||||
|
"@tcpStatus_connectingTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"endpoint": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tcpConnectionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tcpHostLabel": "Adresse IP",
|
||||||
|
"connectionChoiceTcpLabel": "TCP",
|
||||||
|
"tcpScreenTitle": "Établir une connexion via TCP",
|
||||||
|
"tcpHostHint": "192.168.40.10",
|
||||||
|
"tcpPortLabel": "Port",
|
||||||
|
"tcpPortHint": "5000",
|
||||||
|
"tcpStatus_notConnected": "Entrez l'adresse de destination et connectez-vous.",
|
||||||
|
"tcpStatus_connectingTo": "Connexion à {endpoint}...",
|
||||||
|
"tcpErrorHostRequired": "Une adresse IP est obligatoire.",
|
||||||
|
"tcpErrorPortInvalid": "La taille du port doit être comprise entre 1 et 65535.",
|
||||||
|
"tcpErrorUnsupported": "Le protocole TCP n'est pas pris en charge sur cette plateforme.",
|
||||||
|
"tcpErrorTimedOut": "La connexion TCP a expiré.",
|
||||||
|
"tcpConnectionFailed": "Échec de la connexion TCP : {error}",
|
||||||
|
"map_showDiscoveryContacts": "Afficher les contacts de découverte",
|
||||||
|
"map_setAsMyLocation": "Définir comme ma localisation",
|
||||||
|
"@path_routeWeight": {
|
||||||
|
"placeholders": {
|
||||||
|
"weight": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings_privacy": "Paramètres de confidentialité",
|
||||||
|
"settings_privacySubtitle": "Contrôlez les informations partagées",
|
||||||
|
"settings_telemetryLocationMode": "Mode d'emplacement de télémétrie",
|
||||||
|
"settings_telemetryEnvironmentMode": "Mode d'environnement de télémétrie",
|
||||||
|
"settings_advertLocation": "Emplacement de l'annonce",
|
||||||
|
"settings_advertLocationSubtitle": "Inclure l'emplacement dans l'annonce",
|
||||||
|
"settings_denyAll": "Refuser tout",
|
||||||
|
"settings_allowByContact": "Autoriser par drapeaux de contact",
|
||||||
|
"settings_privacySettingsDescription": "Choisissez les informations que votre appareil partage avec les autres.",
|
||||||
|
"settings_allowAll": "Autoriser tout",
|
||||||
|
"contact_info": "Informations de contact",
|
||||||
|
"settings_telemetryBaseMode": "Mode de base Télémétrie",
|
||||||
|
"contact_teleBase": "Base de télémétrie",
|
||||||
|
"contact_teleLoc": "Emplacement de télémétrie",
|
||||||
|
"contact_teleLocSubtitle": "Autoriser le partage des données de localisation",
|
||||||
|
"contact_teleEnv": "Environnement Télémétrie",
|
||||||
|
"contact_teleEnvSubtitle": "Autoriser le partage des données des capteurs d'environnement",
|
||||||
|
"contact_telemetry": "Télémétrie",
|
||||||
|
"contact_settings": "Paramètres de contact",
|
||||||
|
"contact_lastSeen": "Dernière fois vu",
|
||||||
|
"contact_clearChat": "Effacer la conversation",
|
||||||
|
"contact_teleBaseSubtitle": "Autoriser le partage du niveau de batterie et de la télémétrie de base",
|
||||||
|
"appSettings_maxRouteWeightSubtitle": "Poids maximal qu'un itinéraire peut accumuler grâce à des livraisons réussies.",
|
||||||
|
"appSettings_initialRouteWeight": "Poids initial de l'itinéraire",
|
||||||
|
"appSettings_maxRouteWeight": "Poids maximal autorisé pour le trajet",
|
||||||
|
"appSettings_initialRouteWeightSubtitle": "Poids de départ pour les nouveaux chemins découverts",
|
||||||
|
"appSettings_routeWeightSuccessIncrement": "Augmentation du poids de réussite",
|
||||||
|
"appSettings_routeWeightSuccessIncrementSubtitle": "Poids ajouté à un itinéraire après une livraison réussie.",
|
||||||
|
"appSettings_routeWeightFailureDecrement": "Réduction du poids de pénalité",
|
||||||
|
"appSettings_routeWeightFailureDecrementSubtitle": "Poids retiré d'un itinéraire après une tentative de livraison infructueuse.",
|
||||||
|
"appSettings_maxMessageRetries": "Nombre maximal de tentatives de récupération de messages",
|
||||||
|
"appSettings_maxMessageRetriesSubtitle": "Nombre de tentatives de relance avant de marquer un message comme ayant échoué.",
|
||||||
|
"path_routeWeight": "{weight}/{max}",
|
||||||
|
"settings_telemetryModeUpdated": "Le mode télémétrie a été mis à jour",
|
||||||
|
"map_showOverlaps": "Chevauchement de la touche répétitive",
|
||||||
|
"map_runTraceWithReturnPath": "Revenir sur le même chemin.",
|
||||||
|
"@radioStats_noiseFloor": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastRssi": {
|
||||||
|
"placeholders": {
|
||||||
|
"rssiDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastSnr": {
|
||||||
|
"placeholders": {
|
||||||
|
"snr": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_txAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_rxAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_stripNoise": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chat_sendCooldown": "Veuillez patienter un instant avant de réessayer.",
|
||||||
|
"appSettings_jumpToOldestUnread": "Accéder au message le plus ancien non lu",
|
||||||
|
"appSettings_languageHu": "Hongrois",
|
||||||
|
"appSettings_jumpToOldestUnreadSubtitle": "Lorsque vous ouvrez une conversation contenant des messages non lus, faites défiler la page jusqu'au premier message non lu, plutôt que jusqu'au dernier.",
|
||||||
|
"appSettings_languageJa": "Japonais",
|
||||||
|
"appSettings_languageKo": "Coréen",
|
||||||
|
"radioStats_tooltip": "Statistiques des radios et des réseaux sans fil",
|
||||||
|
"radioStats_screenTitle": "Statistiques de radio",
|
||||||
|
"radioStats_notConnected": "Connectez-vous à un appareil pour visualiser les statistiques de la radio.",
|
||||||
|
"radioStats_firmwareTooOld": "Les statistiques radio nécessitent un firmware compatible v8 ou une version ultérieure.",
|
||||||
|
"radioStats_waiting": "En attente des données…",
|
||||||
|
"radioStats_noiseFloor": "Niveau de bruit : {noiseDbm} dBm",
|
||||||
|
"radioStats_lastRssi": "Dernier RSSI : {rssiDbm} dBm",
|
||||||
|
"radioStats_lastSnr": "Dernier SNR : {snr} dB",
|
||||||
|
"radioStats_txAir": "Temps d'antenne à la télévision du Texas (total) : {seconds} s",
|
||||||
|
"radioStats_rxAir": "Temps d'utilisation de l'appareil RX (total) : {seconds} s",
|
||||||
|
"radioStats_chartCaption": "Niveau de bruit (dBm) sur les échantillons récents.",
|
||||||
|
"radioStats_stripNoise": "Niveau de bruit : {noiseDbm} dBm",
|
||||||
|
"radioStats_stripWaiting": "Récupération des statistiques de la radio…",
|
||||||
|
"radioStats_settingsTile": "Statistiques de radio",
|
||||||
|
"radioStats_settingsSubtitle": "Niveau de bruit, RSSI, rapport signal/bruit (SNR) et temps d'antenne",
|
||||||
|
"@translation_downloadFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_composerTitle": "Traduire avant d'envoyer",
|
||||||
|
"translation_enableTitle": "Activer la traduction",
|
||||||
|
"translation_title": "Traduction",
|
||||||
|
"translation_enableSubtitle": "Traduire les messages entrants et permettre la traduction avant l'envoi.",
|
||||||
|
"translation_composerSubtitle": "Contrôle l'état par défaut de l'icône de traduction du composant.",
|
||||||
|
"translation_targetLanguage": "Langue cible",
|
||||||
|
"translation_useAppLanguage": "Utiliser la langue de l'application",
|
||||||
|
"translation_downloadedModelLabel": "Modèle téléchargé",
|
||||||
|
"translation_presetModelLabel": "Modèle Hugging Face préconfiguré",
|
||||||
|
"translation_manualUrlLabel": "URL du modèle manuel",
|
||||||
|
"translation_downloadModel": "Télécharger le modèle",
|
||||||
|
"translation_downloading": "Téléchargement...",
|
||||||
|
"translation_working": "Au travail...",
|
||||||
|
"translation_stop": "Arrêtez",
|
||||||
|
"translation_mergingChunks": "Fusion des fragments téléchargés dans le fichier final...",
|
||||||
|
"translation_downloadedModels": "Modèles téléchargés",
|
||||||
|
"translation_deleteModel": "Supprimer le modèle",
|
||||||
|
"translation_modelDownloaded": "Modèle de traduction téléchargé.",
|
||||||
|
"translation_downloadStopped": "Le téléchargement a été interrompu.",
|
||||||
|
"translation_downloadFailed": "Échec du téléchargement : {error}",
|
||||||
|
"translation_enterUrlFirst": "Entrez d'abord l'URL du modèle.",
|
||||||
|
"@scanner_linuxPairingPinPrompt": {
|
||||||
|
"placeholders": {
|
||||||
|
"deviceName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@translation_translateTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"language": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_composerEnabledHint": "Les messages seront traduits avant d'être envoyés.",
|
||||||
|
"translation_translateBeforeSending": "Traduire avant d'envoyer",
|
||||||
|
"translation_composerDisabledHint": "Envoyez des messages dans la langue originale, telle que vous l'avez tapée.",
|
||||||
|
"translation_messageTranslation": "Traduction du message",
|
||||||
|
"translation_translateTo": "Traduire en {language}",
|
||||||
|
"translation_translationOptions": "Options de traduction",
|
||||||
|
"translation_systemLanguage": "Langue du système",
|
||||||
|
"scanner_linuxPairingPinTitle": "Code PIN pour la connexion Bluetooth",
|
||||||
|
"scanner_linuxPairingHidePin": "Masquer le code PIN",
|
||||||
|
"scanner_linuxPairingPinPrompt": "Entrez le code PIN pour {deviceName} (laissez vide si nécessaire).",
|
||||||
|
"scanner_linuxPairingShowPin": "Afficher le code PIN",
|
||||||
|
"repeater_cliQuickClockSync": "Synchronisation de l'horloge",
|
||||||
|
"repeater_cliQuickDiscovery": "Découvrir les voisins",
|
||||||
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Envoyer automatiquement une notification \"synchronisation de l'heure\" après une connexion réussie.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Synchronisation de l'horloge après la connexion",
|
||||||
|
"repeater_guestTools": "Outils pour les invités",
|
||||||
|
"chat_sendMessage": "Envoyer un message",
|
||||||
|
"room_guest": "Informations sur le serveur",
|
||||||
|
"repeater_guest": "Informations sur les répéteurs",
|
||||||
|
"@notification_messagesCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_getCategory": "Obtenir des valeurs",
|
||||||
|
"repeater_powerMgmt": "Gestion de l'énergie",
|
||||||
|
"repeater_sensors": "Capteurs",
|
||||||
|
"repeater_cliHelpPowerOff": "Éteint l'appareil. (aucune réponse n'est attendue)",
|
||||||
|
"repeater_cliHelpClkReboot": "Réinitialise l'horloge à une époque connue et redémarre l'appareil.",
|
||||||
|
"repeater_cliHelpAdvertZeroHop": "Envoie une publicité sans intermédiaire (uniquement aux voisins immédiats).",
|
||||||
|
"repeater_cliHelpStartOta": "Démarre une mise à jour du firmware par voie radio sur les cartes prises en charge.",
|
||||||
|
"repeater_cliHelpTime": "Définit l'heure de l'appareil sur les secondes de l'époque Unix spécifiée. L'heure ne peut pas reculer.",
|
||||||
|
"repeater_cliHelpBoard": "Indique le fabricant du panneau / l'identifiant du matériel.",
|
||||||
|
"repeater_cliHelpDiscoverNeighbors": "Envoie une requête de découverte de nœuds aux voisins proches. (Uniquement pour les répéteurs)",
|
||||||
|
"repeater_cliHelpPowersaving": "Indique si le mode d'économie d'énergie est activé ou désactivé.",
|
||||||
|
"repeater_cliHelpPowersavingOnOff": "Active ou désactive le mode d'économie d'énergie (si pris en charge).",
|
||||||
|
"repeater_cliHelpErase": "(Uniquement pour les appareils) Formate le système de fichiers de l'appareil. Efface tous les paramètres et les contacts.",
|
||||||
|
"repeater_cliHelpSetDutyCycle": "Définit le cycle de transmission maximal autorisé en pourcentage (de 1 à 100). Ajuste automatiquement le facteur de temps d'émission.",
|
||||||
|
"repeater_cliHelpSetPrvKey": "(Uniquement pour les séries) Remplace la clé privée d'identification de l'appareil. Un redémarrage est nécessaire pour appliquer. Génère une nouvelle clé publique.",
|
||||||
|
"repeater_cliHelpSetRadioRxGain": "(Uniquement pour les SX126x) Active le gain RX amplifié pour une meilleure sensibilité à des courants plus élevés.",
|
||||||
|
"repeater_cliHelpSetOwnerInfo": "Définit la chaîne d'informations de contact du propriétaire, qui figure dans les annonces. Utilisez '|' pour les sauts de ligne.",
|
||||||
|
"repeater_cliHelpSetPathHashMode": "Définit le mode de hachage de chemin. 0 = mode ancien, 1 = mode standard, 2 = mode strict. Influence la façon dont les chemins de routage sont correspondus.",
|
||||||
|
"repeater_cliHelpSetLoopDetect": "Définit la sensibilité de la détection des boucles de routage : désactivée, minimale, modérée ou stricte.",
|
||||||
|
"repeater_cliHelpSetFreq": "(Uniquement pour les modèles série) Permet de régler rapidement la fréquence. Redémarrage nécessaire. Il est préférable d'utiliser l'option \"réglage radio\" pour configurer tous les paramètres radio.",
|
||||||
|
"repeater_cliHelpSetBridgeChannel": "(Uniquement pour le pont ESPNow) Définit le canal WiFi (de 1 à 14) utilisé par le pont.",
|
||||||
|
"repeater_cliHelpGetName": "Affiche le nom du nœud configuré.",
|
||||||
|
"repeater_cliHelpGetRole": "Indique le rôle du firmware (répéteur, serveur de pièce, etc.).",
|
||||||
|
"repeater_cliHelpGetPublicKey": "Affiche la clé publique du dispositif.",
|
||||||
|
"repeater_cliHelpGetPrvKey": "(Uniquement pour les séries) Affiche la clé privée de l'appareil. Traitez-la comme une information confidentielle.",
|
||||||
|
"repeater_cliHelpGetRepeat": "Indique si le transfert de paquets (en tant que routeur) est activé ou désactivé.",
|
||||||
|
"repeater_cliHelpGetTx": "Indique la puissance actuelle en dBm.",
|
||||||
|
"repeater_cliHelpGetFreq": "Affiche la fréquence radio configurée en MHz.",
|
||||||
|
"repeater_cliHelpGetRadio": "Affiche tous les paramètres radio : fréquence, largeur de bande, facteur de dispersion, taux de codage.",
|
||||||
|
"repeater_cliHelpGetRadioRxGain": "(Uniquement pour les SX126x) Affiche l'état du gain amplifié de la réception.",
|
||||||
|
"repeater_cliHelpGetAf": "Indique le facteur de temps d'antenne actuel.",
|
||||||
|
"repeater_cliHelpGetDutyCycle": "Affiche le cycle de fonctionnement actuel autorisé en pourcentage.",
|
||||||
|
"repeater_cliHelpGetIntThresh": "Indique le seuil d'interférence du canal en dB.",
|
||||||
|
"repeater_cliHelpGetAgcResetInterval": "Indique l'intervalle de réinitialisation de l'AGC en secondes.",
|
||||||
|
"repeater_cliHelpGetMultiAcks": "Indique si le mode \"double ACK\" est activé (1) ou désactivé (0).",
|
||||||
|
"repeater_cliHelpGetAllowReadOnly": "Indique si l'accès en lecture seule pour les invités est autorisé.",
|
||||||
|
"repeater_cliHelpGetAdvertInterval": "Indique l'intervalle publicitaire local en minutes.",
|
||||||
|
"repeater_cliHelpGetFloodAdvertInterval": "Indique l'intervalle publicitaire pour la diffusion de l'annonce en heures.",
|
||||||
|
"repeater_cliHelpGetGuestPassword": "Affiche le mot de passe invité configuré.",
|
||||||
|
"repeater_cliHelpGetLat": "Affiche la latitude configurée.",
|
||||||
|
"repeater_cliHelpGetLon": "Affiche la longitude configurée.",
|
||||||
|
"repeater_cliHelpGetRxDelay": "Affiche la valeur de base de rxdelay.",
|
||||||
|
"repeater_cliHelpGetTxDelay": "Indique le facteur de délai de transmission en mode inondation.",
|
||||||
|
"repeater_cliHelpGetDirectTxDelay": "Indique le facteur de délai direct.",
|
||||||
|
"repeater_cliHelpGetFloodMax": "Indique le nombre maximal de fois où le niveau de l'eau a atteint son point culminant.",
|
||||||
|
"repeater_cliHelpGetOwnerInfo": "Affiche la chaîne d'informations de contact du propriétaire.",
|
||||||
|
"repeater_cliHelpGetPathHashMode": "Affiche le mode \"hash de chemin\" (0/1/2).",
|
||||||
|
"repeater_cliHelpGetLoopDetect": "Illustre la sensibilité à la détection des boucles.",
|
||||||
|
"repeater_cliHelpGetAcl": "(Uniquement pour les séries) Liste les entrées de contrôle d'accès sur un répéteur.",
|
||||||
|
"repeater_cliHelpGetBridgeEnabled": "Indique si le pont est activé.",
|
||||||
|
"repeater_cliHelpGetBridgeDelay": "Indique le délai du pont en millisecondes.",
|
||||||
|
"repeater_cliHelpGetBridgeSource": "Indique si le pont transmet des paquets RX ou TX.",
|
||||||
|
"repeater_cliHelpGetBridgeBaud": "(Uniquement pour le pont RS232) Affiche la vitesse de communication du pont.",
|
||||||
|
"repeater_cliHelpGetBridgeChannel": "(Uniquement pour le pont ESPNow) Affiche le canal WiFi du pont.",
|
||||||
|
"repeater_cliHelpGetBridgeSecret": "(Uniquement pour le pont ESPNow) Affiche la clé partagée du pont.",
|
||||||
|
"repeater_cliHelpGetBootloaderVer": "(Uniquement pour le NRF52) Affiche la version du chargeur initial.",
|
||||||
|
"repeater_cliHelpGetAdcMultiplier": "Affiche le multiplicateur de l'ADC (mise à l'échelle de la tension de la batterie).",
|
||||||
|
"repeater_cliHelpGetPwrMgtSupport": "Indique si le système dispose de fonctionnalités de gestion de l'alimentation.",
|
||||||
|
"repeater_cliHelpGetPwrMgtSource": "Indique la source d'alimentation actuelle : externe ou batterie.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootReason": "Indique les raisons les plus récentes de réinitialisation et d'arrêt.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootMv": "Affiche la tension de la batterie au démarrage, en millivolts (mV).",
|
||||||
|
"repeater_cliHelpSensorGet": "Lit une valeur de configuration personnalisée pour un capteur en utilisant une touche.",
|
||||||
|
"repeater_cliHelpSensorSet": "Crée une configuration personnalisée pour un capteur.",
|
||||||
|
"repeater_cliHelpSensorList": "Affiche toutes les configurations de capteurs personnalisées, avec une pagination à partir d'un index de départ optionnel.",
|
||||||
|
"repeater_cliHelpRegionDefault": "Affiche la portée régionale par défaut actuelle.",
|
||||||
|
"repeater_cliHelpRegionDefaultSet": "Définit la portée régionale par défaut. Utilisez \"<null>\" pour la réinitialiser.",
|
||||||
|
"repeater_cliHelpRegionListAllowed": "Énumère les régions autorisant la circulation des véhicules en cas de inondation.",
|
||||||
|
"repeater_cliHelpRegionListDenied": "Liste des régions qui interdisent la circulation en cas de inondation.",
|
||||||
|
"repeater_cliHelpStatsPackets": "(Uniquement pour les séries) Affiche des statistiques au niveau des paquets.",
|
||||||
|
"repeater_cliHelpStatsRadio": "(Uniquement pour les séries) Affiche les statistiques de la radio.",
|
||||||
|
"repeater_cliHelpStatsCore": "(Uniquement pour les séries) Affiche les statistiques du micrologicem intégré.",
|
||||||
|
"common_done": "Done",
|
||||||
|
"background_serviceTitle": "MeshCore running",
|
||||||
|
"background_serviceText": "Keeping BLE connected",
|
||||||
|
"appSettings_translationModelDeleted": "Deleted {name}",
|
||||||
|
"@appSettings_translationModelDeleted": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
|
||||||
|
"@appSettings_translationModelDeleteFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channels_channelUpdateFailed": "Failed to update channel: {error}",
|
||||||
|
"@channels_channelUpdateFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map_type": "Type",
|
||||||
|
"map_path": "Path",
|
||||||
|
"map_location": "Location",
|
||||||
|
"map_estLocation": "Est. Location",
|
||||||
|
"map_publicKey": "Public Key",
|
||||||
|
"map_publicKeyPrefixHint": "e.g. ab12",
|
||||||
|
"contact_typeChat": "Chat",
|
||||||
|
"contact_typeRepeater": "Repeater",
|
||||||
|
"contact_typeRoom": "Room",
|
||||||
|
"contact_typeSensor": "Sensor",
|
||||||
|
"contact_typeUnknown": "Unknown",
|
||||||
|
"channels_via": "via {path}",
|
||||||
|
"chat_score": "Score",
|
||||||
|
"settings_multiAck": "Plusieurs accusés de réception",
|
||||||
|
"map_sharedAt": "Partagé",
|
||||||
|
"@losBlockedSpotChip": {
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@losSelectedObstructionDetails": {
|
||||||
|
"placeholders": {
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromA": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromB": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"losSelectedObstructionTitle": "Obstruction sélectionnée",
|
||||||
|
"losBlockedSpotsTitle": "Places occupés",
|
||||||
|
"losBlockedSpotsHint": "Sélectionnez un emplacement bloqué pour le mettre en évidence sur la carte.",
|
||||||
|
"losSelectedObstructionDetails": "Bloqué par {obstruction}, à une hauteur de {heightUnit}, à une distance de {distanceFromA} par rapport à A et à une distance de {distanceFromB} par rapport à B ({distanceUnit}).",
|
||||||
|
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
|
||||||
|
"settings_companionDebugLog": "Journal de débogage associé",
|
||||||
|
"chat_markAsUnread": "Signaler comme non lu",
|
||||||
|
"chat_newMessages": "Nouveaux messages",
|
||||||
|
"settings_companionDebugLogSubtitle": "Commandes, réponses et données brutes pour les protocoles BLE/TCP/USB",
|
||||||
|
"repeater_chanUtil": "Utilisation du canal",
|
||||||
|
"dialog_connectCompanion": "Connectez-vous à un compagnon pour accéder aux fonctionnalités de répéteur et de serveur de salle.",
|
||||||
|
"dialog_disconnectedTitle": "Déconnecté",
|
||||||
|
"dialog_disconnectedMessage": "Vous avez été déconnecté de votre compagnon.",
|
||||||
|
"contact_connectCompanion": "Connectez-vous à un compagnon pour accéder aux fonctionnalités du répéteur et du serveur de salle."
|
||||||
}
|
}
|
||||||
|
|||||||
+2357
File diff suppressed because it is too large
Load Diff
+483
-27
@@ -104,6 +104,8 @@
|
|||||||
"settings_privacyModeEnabled": "Modalità privacy abilitata",
|
"settings_privacyModeEnabled": "Modalità privacy abilitata",
|
||||||
"settings_privacyModeDisabled": "Modalità privacy disabilitata",
|
"settings_privacyModeDisabled": "Modalità privacy disabilitata",
|
||||||
"settings_actions": "Azioni",
|
"settings_actions": "Azioni",
|
||||||
|
"settings_deleteAllPaths": "Delete All Paths",
|
||||||
|
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
|
||||||
"settings_sendAdvertisement": "Invia Annuncio",
|
"settings_sendAdvertisement": "Invia Annuncio",
|
||||||
"settings_sendAdvertisementSubtitle": "Presenza trasmessa ora",
|
"settings_sendAdvertisementSubtitle": "Presenza trasmessa ora",
|
||||||
"settings_advertisementSent": "Annuncio inviato",
|
"settings_advertisementSent": "Annuncio inviato",
|
||||||
@@ -121,7 +123,7 @@
|
|||||||
"settings_appDebugLog": "Log di Debug dell'App",
|
"settings_appDebugLog": "Log di Debug dell'App",
|
||||||
"settings_appDebugLogSubtitle": "Messaggi di debug dell'applicazione",
|
"settings_appDebugLogSubtitle": "Messaggi di debug dell'applicazione",
|
||||||
"settings_about": "Informazioni",
|
"settings_about": "Informazioni",
|
||||||
"settings_aboutVersion": "MeshCore Open v{version}",
|
"settings_aboutVersion": "MeshCore Open versione {version}",
|
||||||
"@settings_aboutVersion": {
|
"@settings_aboutVersion": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"version": {
|
"version": {
|
||||||
@@ -164,19 +166,19 @@
|
|||||||
"appSettings_themeDark": "Scuro",
|
"appSettings_themeDark": "Scuro",
|
||||||
"appSettings_language": "Lingua",
|
"appSettings_language": "Lingua",
|
||||||
"appSettings_languageSystem": "Predefinito di sistema",
|
"appSettings_languageSystem": "Predefinito di sistema",
|
||||||
"appSettings_languageEn": "English",
|
"appSettings_languageEn": "Inglese",
|
||||||
"appSettings_languageFr": "Français",
|
"appSettings_languageFr": "Francese",
|
||||||
"appSettings_languageEs": "Español",
|
"appSettings_languageEs": "Spagnolo",
|
||||||
"appSettings_languageDe": "Deutsch",
|
"appSettings_languageDe": "Tedesco",
|
||||||
"appSettings_languagePl": "Polski",
|
"appSettings_languagePl": "Polacco",
|
||||||
"appSettings_languageSl": "Slovenščina",
|
"appSettings_languageSl": "Sloveno",
|
||||||
"appSettings_languagePt": "Português",
|
"appSettings_languagePt": "Portoghese",
|
||||||
"appSettings_languageIt": "Italiano",
|
"appSettings_languageIt": "Italiano",
|
||||||
"appSettings_languageZh": "中文",
|
"appSettings_languageZh": "Cinese",
|
||||||
"appSettings_languageSv": "Svenska",
|
"appSettings_languageSv": "Svedese",
|
||||||
"appSettings_languageNl": "Nederlands",
|
"appSettings_languageNl": "Olandese",
|
||||||
"appSettings_languageSk": "Slovenčina",
|
"appSettings_languageSk": "Sloveno",
|
||||||
"appSettings_languageBg": "Български",
|
"appSettings_languageBg": "Bulgaro",
|
||||||
"appSettings_notifications": "Notifiche",
|
"appSettings_notifications": "Notifiche",
|
||||||
"appSettings_enableNotifications": "Abilita Notifiche",
|
"appSettings_enableNotifications": "Abilita Notifiche",
|
||||||
"appSettings_enableNotificationsSubtitle": "Ricevi notifiche per messaggi e annunci",
|
"appSettings_enableNotificationsSubtitle": "Ricevi notifiche per messaggi e annunci",
|
||||||
@@ -285,6 +287,7 @@
|
|||||||
"contacts_newGroup": "Nuovo Gruppo",
|
"contacts_newGroup": "Nuovo Gruppo",
|
||||||
"contacts_groupName": "Nome gruppo",
|
"contacts_groupName": "Nome gruppo",
|
||||||
"contacts_groupNameRequired": "Il nome del gruppo è obbligatorio.",
|
"contacts_groupNameRequired": "Il nome del gruppo è obbligatorio.",
|
||||||
|
"contacts_groupNameReserved": "Questo nome del gruppo è riservato",
|
||||||
"contacts_groupAlreadyExists": "Il gruppo \"{name}\" esiste già.",
|
"contacts_groupAlreadyExists": "Il gruppo \"{name}\" esiste già.",
|
||||||
"@contacts_groupAlreadyExists": {
|
"@contacts_groupAlreadyExists": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -336,11 +339,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_hashtagChannel": "Canale hashtag",
|
|
||||||
"channels_public": "Pubblico",
|
"channels_public": "Pubblico",
|
||||||
"channels_private": "Privato",
|
"channels_private": "Privato",
|
||||||
"channels_publicChannel": "Canale pubblico",
|
|
||||||
"channels_privateChannel": "Canale privato",
|
|
||||||
"channels_editChannel": "Modifica canale",
|
"channels_editChannel": "Modifica canale",
|
||||||
"channels_muteChannel": "Silenzia canale",
|
"channels_muteChannel": "Silenzia canale",
|
||||||
"channels_unmuteChannel": "Attiva notifiche canale",
|
"channels_unmuteChannel": "Attiva notifiche canale",
|
||||||
@@ -366,7 +366,7 @@
|
|||||||
"channels_channelName": "Nome canale",
|
"channels_channelName": "Nome canale",
|
||||||
"channels_usePublicChannel": "Utilizza il canale pubblico",
|
"channels_usePublicChannel": "Utilizza il canale pubblico",
|
||||||
"channels_standardPublicPsk": "PSK pubblico standard",
|
"channels_standardPublicPsk": "PSK pubblico standard",
|
||||||
"channels_pskHex": "PSK (Hex)",
|
"channels_pskHex": "PSK (esadecimale)",
|
||||||
"channels_generateRandomPsk": "Genera una chiave di permutazione casuale",
|
"channels_generateRandomPsk": "Genera una chiave di permutazione casuale",
|
||||||
"channels_enterChannelName": "Inserisci un nome per il canale",
|
"channels_enterChannelName": "Inserisci un nome per il canale",
|
||||||
"channels_pskMustBe32Hex": "PSK deve essere composto da 32 caratteri esadecimali.",
|
"channels_pskMustBe32Hex": "PSK deve essere composto da 32 caratteri esadecimali.",
|
||||||
@@ -387,6 +387,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_smazCompression": "Compressione SMAZ",
|
"channels_smazCompression": "Compressione SMAZ",
|
||||||
|
"channels_cyr2latCompression": "Compressione Cyr2Lat",
|
||||||
|
"channels_cyr2latCompressionDscr": "Sostituisce alcuni caratteri cirillici con caratteri latini durante l'invio.",
|
||||||
|
"channels_cyr2latSettingsHeading": "Impostazioni Cyr2Lat",
|
||||||
|
"channels_cyr2latSettingsSubheading": "Elenco delle sostituzioni",
|
||||||
|
"channels_cyr2latSettingsDscr": "Modifica la configurazione JSON delle sostituzioni dei caratteri",
|
||||||
|
"channels_cyr2latSettingsDialogHint": "Mappa JSON delle sostituzioni",
|
||||||
|
"channels_cyr2latSettingsDialogWrongJSON": "JSON non corretto: {error}",
|
||||||
|
"settings_cyr2latProfileAdd": "Aggiungi profilo Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileName": "Nome profilo",
|
||||||
|
"settings_cyr2latProfileNameEmpty": "Il nome del profilo non può essere vuoto",
|
||||||
|
"settings_cyr2latProfileAdded": "Profilo aggiunto con successo",
|
||||||
|
"settings_cyr2latProfileUpdated": "Profilo aggiornato con successo",
|
||||||
|
"settings_cyr2latProfileEdit": "Modifica profilo Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileDelete": "Elimina profilo Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileDeleted": "Profilo eliminato con successo",
|
||||||
|
"settings_cyr2latProfileDeleteDscr": "Sei sicuro di voler eliminare il profilo \"{name}\"?",
|
||||||
"channels_channelUpdated": "Canale \"{name}\" aggiornato",
|
"channels_channelUpdated": "Canale \"{name}\" aggiornato",
|
||||||
"@channels_channelUpdated": {
|
"@channels_channelUpdated": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -398,7 +414,7 @@
|
|||||||
"channels_publicChannelAdded": "Canale pubblico aggiunto",
|
"channels_publicChannelAdded": "Canale pubblico aggiunto",
|
||||||
"channels_sortBy": "Ordina per",
|
"channels_sortBy": "Ordina per",
|
||||||
"channels_sortManual": "Manuale",
|
"channels_sortManual": "Manuale",
|
||||||
"channels_sortAZ": "A-Z",
|
"channels_sortAZ": "D-Z",
|
||||||
"channels_sortLatestMessages": "Ultimi messaggi",
|
"channels_sortLatestMessages": "Ultimi messaggi",
|
||||||
"channels_sortUnread": "Non letto",
|
"channels_sortUnread": "Non letto",
|
||||||
"chat_noMessages": "Nessun messaggio ancora",
|
"chat_noMessages": "Nessun messaggio ancora",
|
||||||
@@ -455,7 +471,7 @@
|
|||||||
"chat_sendGif": "Invia GIF",
|
"chat_sendGif": "Invia GIF",
|
||||||
"chat_reply": "Rispondi",
|
"chat_reply": "Rispondi",
|
||||||
"chat_addReaction": "Aggiungi Reazione",
|
"chat_addReaction": "Aggiungi Reazione",
|
||||||
"chat_me": "Me",
|
"chat_me": "Io",
|
||||||
"emojiCategorySmileys": "Emoji",
|
"emojiCategorySmileys": "Emoji",
|
||||||
"emojiCategoryGestures": "Gesti",
|
"emojiCategoryGestures": "Gesti",
|
||||||
"emojiCategoryHearts": "Cuori",
|
"emojiCategoryHearts": "Cuori",
|
||||||
@@ -503,7 +519,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debugFrame_timestamp": "- Timestamp: {timestamp}",
|
"debugFrame_timestamp": "- Marca temporale: {timestamp}",
|
||||||
"@debugFrame_timestamp": {
|
"@debugFrame_timestamp": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"timestamp": {
|
"timestamp": {
|
||||||
@@ -880,7 +896,7 @@
|
|||||||
"repeater_managementTools": "Strumenti di Gestione",
|
"repeater_managementTools": "Strumenti di Gestione",
|
||||||
"repeater_status": "Stato",
|
"repeater_status": "Stato",
|
||||||
"repeater_statusSubtitle": "Visualizza lo stato, le statistiche e i vicini del ripetitore",
|
"repeater_statusSubtitle": "Visualizza lo stato, le statistiche e i vicini del ripetitore",
|
||||||
"repeater_telemetry": "Telemetry",
|
"repeater_telemetry": "Telemetria",
|
||||||
"repeater_telemetrySubtitle": "Visualizza i dati di telemetria dei sensori e le statistiche di sistema",
|
"repeater_telemetrySubtitle": "Visualizza i dati di telemetria dei sensori e le statistiche di sistema",
|
||||||
"repeater_cli": "CLI",
|
"repeater_cli": "CLI",
|
||||||
"repeater_cliSubtitle": "Invia comandi al ripetitore",
|
"repeater_cliSubtitle": "Invia comandi al ripetitore",
|
||||||
@@ -995,7 +1011,7 @@
|
|||||||
"repeater_txPower": "TX Potenza",
|
"repeater_txPower": "TX Potenza",
|
||||||
"repeater_txPowerHelper": "1-30 dBm",
|
"repeater_txPowerHelper": "1-30 dBm",
|
||||||
"repeater_bandwidth": "Larghezza di banda",
|
"repeater_bandwidth": "Larghezza di banda",
|
||||||
"repeater_spreadingFactor": "Spreading Factor",
|
"repeater_spreadingFactor": "Fattore di propagazione",
|
||||||
"repeater_codingRate": "Tasso di Codifica",
|
"repeater_codingRate": "Tasso di Codifica",
|
||||||
"repeater_locationSettings": "Impostazioni Luogo",
|
"repeater_locationSettings": "Impostazioni Luogo",
|
||||||
"repeater_latitude": "Latitudine",
|
"repeater_latitude": "Latitudine",
|
||||||
@@ -1058,6 +1074,81 @@
|
|||||||
},
|
},
|
||||||
"repeater_confirm": "Conferma",
|
"repeater_confirm": "Conferma",
|
||||||
"repeater_settingsSaved": "Impostazioni salvate con successo",
|
"repeater_settingsSaved": "Impostazioni salvate con successo",
|
||||||
|
"repeater_rxGain": "Aumento del guadagno RX",
|
||||||
|
"repeater_rxGainHelper": "Maggiore sensibilità, maggiore assorbimento di corrente (solo per SX1262/SX1268)",
|
||||||
|
"repeater_refreshRxGain": "Rafforza l'effetto di RX",
|
||||||
|
"repeater_multiAcks": "ACK multipli",
|
||||||
|
"repeater_multiAcksSubtitle": "Riconoscere i messaggi attraverso percorsi multipli per una migliore consegna.",
|
||||||
|
"repeater_refreshMultiAcks": "Riaffermare più ACK",
|
||||||
|
"repeater_networkHealth": "Salute della rete",
|
||||||
|
"repeater_loopDetect": "Rilevamento di cicli",
|
||||||
|
"repeater_loopDetectHelper": "Crea pacchetti di dati che simulano loop di routing.",
|
||||||
|
"repeater_loopDetectOff": "Offerte",
|
||||||
|
"repeater_loopDetectMinimal": "Essenziale",
|
||||||
|
"repeater_loopDetectModerate": "Moderato",
|
||||||
|
"repeater_loopDetectStrict": "Rigido",
|
||||||
|
"repeater_dutyCycle": "Ciclo di lavoro",
|
||||||
|
"repeater_dutyCycleHelper": "Percentuale massima di utilizzo dello spazio pubblicitario",
|
||||||
|
"repeater_dutyCyclePercent": "{percent}%",
|
||||||
|
"@repeater_dutyCyclePercent": {
|
||||||
|
"placeholders": {
|
||||||
|
"percent": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_ownerInfo": "Informazioni sull'operatore",
|
||||||
|
"repeater_ownerInfoHelper": "Metadati pubblici per questo ripetitore",
|
||||||
|
"repeater_refreshOwnerInfo": "Aggiorna le informazioni sull'operatore",
|
||||||
|
"repeater_floodMax": "Massimo numero di salti in caso di inondazione",
|
||||||
|
"repeater_floodMaxHelper": "Numero massimo di pacchetti che un flusso può attraversare (0-64)",
|
||||||
|
"repeater_advancedSettings": "Avanzato",
|
||||||
|
"repeater_advancedSettingsSubtitle": "Manopole di regolazione per operatori esperti",
|
||||||
|
"repeater_pathHashMode": "Modalità di hashing del percorso",
|
||||||
|
"repeater_pathHashModeHelper": "Byte utilizzati per codificare l'ID di questo ripetitore nei tag per il rilevamento del percorso/loop. 0=1 byte (256 ID, fino a 64 salti), 1=2 byte (65.000 ID, fino a 32 salti), 2=3 byte (16 milioni di ID, fino a 21 salti). Le versioni 1.13 e precedenti utilizzano percorsi multi-byte: è necessario attivare la rete prima di utilizzare questa funzionalità (a partire dalla versione 1.14).",
|
||||||
|
"repeater_txDelay": "Ritardo a Flood, TX",
|
||||||
|
"repeater_txDelayHelper": "Riassegnare lo spazio tra i pacchetti per gestire il traffico intenso, come un moltiplicatore del tempo di trasmissione (da 0 a 2, valore predefinito 0,5). Un valore più alto significa meno collisioni, ma una trasmissione più lenta.",
|
||||||
|
"repeater_directTxDelay": "Ritardo diretto TX",
|
||||||
|
"repeater_directTxDelayHelper": "Riassegnare lo spazio per il traffico diretto (non di massa), come un moltiplicatore del tempo di trasmissione del pacchetto (da 0 a 2, valore predefinito 0,3).",
|
||||||
|
"repeater_intThresh": "Soglia di interferenza",
|
||||||
|
"repeater_intThreshHelper": "Il limite è stato impostato per la calibrazione del livello di rumore del ricevitore, in modo che esso rifiuti i segnali di interferenza superiori a questo livello. 0 disabilita – aumentalo solo se si verificano errori nel ricevitore in una banda di frequenza rumorosa.",
|
||||||
|
"repeater_agcResetInterval": "Intervallo di ripristino di AGC",
|
||||||
|
"repeater_agcResetIntervalHelper": "Con quale frequenza è necessario resettare il controllo automatico del guadagno per ripristinare il funzionamento dopo un'interruzione. Impostare su secondi, ridotti a multipli di 4. Disattivare la reimpostazione periodica.",
|
||||||
|
"repeater_actionsTitle": "Azioni",
|
||||||
|
"repeater_sendAdvert": "Inviare annuncio relativo alle inondazioni",
|
||||||
|
"repeater_sendAdvertSubtitle": "Trasmettere un annuncio pubblicitario relativo alle inondazioni attraverso la rete.",
|
||||||
|
"repeater_sendAdvertZeroHop": "Inviare un annuncio senza intermediari",
|
||||||
|
"repeater_sendAdvertZeroHopSubtitle": "Trasmettere un annuncio a un solo hop (senza ripetitori)",
|
||||||
|
"repeater_clockSync": "Sincronizza l'orologio ora",
|
||||||
|
"repeater_clockSyncSubtitle": "Imposta l'ora del tuo telefono sul ripetitore.",
|
||||||
|
"repeater_actionSucceeded": "{action} ha avuto successo",
|
||||||
|
"@repeater_actionSucceeded": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_actionFailed": "{action} failed: {error}",
|
||||||
|
"@repeater_actionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_settingsSavedRebootNeeded": "Impostazioni salvate — riavviare il ripetitore per applicare le modifiche",
|
||||||
|
"repeater_settingsPartialFailure": "Alcune impostazioni non sono state salvate: {failures}",
|
||||||
|
"@repeater_settingsPartialFailure": {
|
||||||
|
"placeholders": {
|
||||||
|
"failures": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repeater_errorSavingSettings": "Errore durante il salvataggio delle impostazioni: {error}",
|
"repeater_errorSavingSettings": "Errore durante il salvataggio delle impostazioni: {error}",
|
||||||
"@repeater_errorSavingSettings": {
|
"@repeater_errorSavingSettings": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1069,11 +1160,9 @@
|
|||||||
"repeater_refreshBasicSettings": "Aggiorna Impostazioni Base",
|
"repeater_refreshBasicSettings": "Aggiorna Impostazioni Base",
|
||||||
"repeater_refreshRadioSettings": "Aggiorna le Impostazioni Radio",
|
"repeater_refreshRadioSettings": "Aggiorna le Impostazioni Radio",
|
||||||
"repeater_refreshTxPower": "Aggiorna TX potenza",
|
"repeater_refreshTxPower": "Aggiorna TX potenza",
|
||||||
"repeater_refreshLocationSettings": "Aggiorna le Impostazioni della Posizione",
|
|
||||||
"repeater_refreshPacketForwarding": "Aggiorna il inoltro pacchetti",
|
"repeater_refreshPacketForwarding": "Aggiorna il inoltro pacchetti",
|
||||||
"repeater_refreshGuestAccess": "Aggiorna Accesso Ospite",
|
"repeater_refreshGuestAccess": "Aggiorna Accesso Ospite",
|
||||||
"repeater_refreshPrivacyMode": "Aggiorna Modalità Privacy",
|
"repeater_refreshPrivacyMode": "Aggiorna Modalità Privacy",
|
||||||
"repeater_refreshAdvertisementSettings": "Aggiorna le Impostazioni dell'Annuncio",
|
|
||||||
"repeater_refreshed": "{label} aggiornato",
|
"repeater_refreshed": "{label} aggiornato",
|
||||||
"@repeater_refreshed": {
|
"@repeater_refreshed": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1346,7 +1435,7 @@
|
|||||||
"listFilter_sortBy": "Ordina per",
|
"listFilter_sortBy": "Ordina per",
|
||||||
"listFilter_latestMessages": "Ultimi messaggi",
|
"listFilter_latestMessages": "Ultimi messaggi",
|
||||||
"listFilter_heardRecently": "Sentito di recente",
|
"listFilter_heardRecently": "Sentito di recente",
|
||||||
"listFilter_az": "A-Z",
|
"listFilter_az": "D-Z",
|
||||||
"listFilter_filters": "Filtri",
|
"listFilter_filters": "Filtri",
|
||||||
"listFilter_all": "Tutti",
|
"listFilter_all": "Tutti",
|
||||||
"listFilter_users": "Utenti",
|
"listFilter_users": "Utenti",
|
||||||
@@ -1458,7 +1547,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common_ok": "OK",
|
"common_ok": "Va bene",
|
||||||
"community_title": "Comunità",
|
"community_title": "Comunità",
|
||||||
"community_create": "Crea Comunità",
|
"community_create": "Crea Comunità",
|
||||||
"community_createDesc": "Crea una nuova comunità e condividila tramite codice QR.",
|
"community_createDesc": "Crea una nuova comunità e condividila tramite codice QR.",
|
||||||
@@ -1859,5 +1948,372 @@
|
|||||||
"usbConnectionFailed": "Errore nella connessione USB: {error}",
|
"usbConnectionFailed": "Errore nella connessione USB: {error}",
|
||||||
"usbStatus_notConnected": "Seleziona un dispositivo USB",
|
"usbStatus_notConnected": "Seleziona un dispositivo USB",
|
||||||
"usbStatus_connecting": "Connessione al dispositivo USB...",
|
"usbStatus_connecting": "Connessione al dispositivo USB...",
|
||||||
"usbErrorConnectTimedOut": "La connessione è scaduta. Assicurarsi che il dispositivo abbia il firmware USB Companion."
|
"usbErrorConnectTimedOut": "La connessione è scaduta. Assicurarsi che il dispositivo abbia il firmware USB Companion.",
|
||||||
|
"@tcpStatus_connectingTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"endpoint": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tcpConnectionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tcpHostLabel": "Indirizzo IP",
|
||||||
|
"tcpHostHint": "192.168.40.10",
|
||||||
|
"connectionChoiceTcpLabel": "TCP",
|
||||||
|
"tcpScreenTitle": "Stabilire una connessione tramite TCP",
|
||||||
|
"tcpPortLabel": "Porta",
|
||||||
|
"tcpPortHint": "5000",
|
||||||
|
"tcpStatus_notConnected": "Inserisci l'endpoint e connettiti.",
|
||||||
|
"tcpStatus_connectingTo": "Connessione a {endpoint}...",
|
||||||
|
"tcpErrorHostRequired": "È necessario fornire un indirizzo IP.",
|
||||||
|
"tcpErrorPortInvalid": "La dimensione della porta deve essere compresa tra 1 e 65535.",
|
||||||
|
"tcpErrorUnsupported": "Il protocollo TCP non è supportato su questa piattaforma.",
|
||||||
|
"tcpErrorTimedOut": "La connessione TCP è scaduta.",
|
||||||
|
"tcpConnectionFailed": "Impossibile stabilire la connessione TCP: {error}",
|
||||||
|
"map_showDiscoveryContacts": "Mostra Contatti di Discovery",
|
||||||
|
"map_setAsMyLocation": "Imposta come la mia posizione",
|
||||||
|
"@path_routeWeight": {
|
||||||
|
"placeholders": {
|
||||||
|
"weight": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings_privacySettingsDescription": "Scegli le informazioni che il tuo dispositivo condivide con gli altri.",
|
||||||
|
"settings_allowByContact": "Consenti in base ai flag di contatto",
|
||||||
|
"settings_telemetryLocationMode": "Modalità di posizionamento telemetrico",
|
||||||
|
"settings_telemetryEnvironmentMode": "Modalità di ambiente di telemetria",
|
||||||
|
"settings_advertLocation": "Posizione dell'annuncio",
|
||||||
|
"settings_advertLocationSubtitle": "Includi la posizione nell'annuncio",
|
||||||
|
"settings_privacy": "Impostazioni sulla privacy",
|
||||||
|
"settings_denyAll": "Negare tutto",
|
||||||
|
"settings_privacySubtitle": "Controlla le informazioni che vengono condivise.",
|
||||||
|
"settings_allowAll": "Consenti tutto",
|
||||||
|
"contact_info": "Informazioni di Contatto",
|
||||||
|
"settings_telemetryBaseMode": "Modalità di base di telemetria",
|
||||||
|
"contact_teleBase": "Base di telemetria",
|
||||||
|
"contact_teleLoc": "Posizione telemetria",
|
||||||
|
"contact_teleLocSubtitle": "Consenti la condivisione dei dati di posizione",
|
||||||
|
"contact_clearChat": "Cancella chat",
|
||||||
|
"contact_telemetry": "Telemetria",
|
||||||
|
"contact_settings": "Impostazioni di contatto",
|
||||||
|
"contact_lastSeen": "Ultimo accesso",
|
||||||
|
"contact_teleBaseSubtitle": "Consenti la condivisione del livello della batteria e della telemetria di base",
|
||||||
|
"contact_teleEnvSubtitle": "Consenti la condivisione dei dati del sensore ambientale",
|
||||||
|
"contact_teleEnv": "Ambiente di telemetria",
|
||||||
|
"appSettings_initialRouteWeight": "Peso iniziale del percorso",
|
||||||
|
"appSettings_initialRouteWeightSubtitle": "Peso di partenza per nuovi percorsi",
|
||||||
|
"appSettings_maxRouteWeightSubtitle": "Il peso massimo che un percorso può accumulare grazie a consegne di successo.",
|
||||||
|
"appSettings_maxRouteWeight": "Massimo peso consentito per il percorso",
|
||||||
|
"appSettings_routeWeightSuccessIncrement": "Aumento del peso del successo",
|
||||||
|
"appSettings_routeWeightSuccessIncrementSubtitle": "Peso aggiunto a un percorso dopo una consegna riuscita.",
|
||||||
|
"appSettings_routeWeightFailureDecrement": "Riduzione del peso associato al fallimento",
|
||||||
|
"appSettings_routeWeightFailureDecrementSubtitle": "Peso rimosso da un percorso dopo un tentativo di consegna fallito.",
|
||||||
|
"appSettings_maxMessageRetries": "Numero massimo di tentativi di invio del messaggio",
|
||||||
|
"appSettings_maxMessageRetriesSubtitle": "Numero di tentativi di riprova prima di considerare un messaggio come fallito.",
|
||||||
|
"path_routeWeight": "{weight}/{max}",
|
||||||
|
"settings_telemetryModeUpdated": "Modalità telemetria aggiornata",
|
||||||
|
"settings_multiAck": "ACK multipli",
|
||||||
|
"map_showOverlaps": "Sovrapposizioni della chiave ripetitore",
|
||||||
|
"map_runTraceWithReturnPath": "Tornare indietro sullo stesso percorso",
|
||||||
|
"@radioStats_noiseFloor": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastRssi": {
|
||||||
|
"placeholders": {
|
||||||
|
"rssiDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastSnr": {
|
||||||
|
"placeholders": {
|
||||||
|
"snr": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_txAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_rxAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_stripNoise": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_jumpToOldestUnreadSubtitle": "Quando si apre una chat con messaggi non letti, scorrete verso l'alto fino al primo messaggio non letto, invece che al più recente.",
|
||||||
|
"chat_sendCooldown": "Si prega di attendere un momento prima di inviare nuovamente.",
|
||||||
|
"appSettings_jumpToOldestUnread": "Vai al messaggio più vecchio non letto",
|
||||||
|
"appSettings_languageHu": "Ungherese",
|
||||||
|
"appSettings_languageJa": "Giapponese",
|
||||||
|
"appSettings_languageKo": "Coreano",
|
||||||
|
"radioStats_tooltip": "Statistiche per radio e reti",
|
||||||
|
"radioStats_screenTitle": "Statistiche radio",
|
||||||
|
"radioStats_notConnected": "Connettiti a un dispositivo per visualizzare le statistiche radio.",
|
||||||
|
"radioStats_firmwareTooOld": "Le statistiche radio richiedono il firmware versione 8 o successiva.",
|
||||||
|
"radioStats_noiseFloor": "Livello di rumore: {noiseDbm} dBm",
|
||||||
|
"radioStats_waiting": "In attesa dei dati…",
|
||||||
|
"radioStats_lastRssi": "Ultimo valore RSSI: {rssiDbm} dBm",
|
||||||
|
"radioStats_lastSnr": "Ultimo SNR: {snr} dB",
|
||||||
|
"radioStats_txAir": "Tempo di trasmissione in diretta (totale): {seconds} s",
|
||||||
|
"radioStats_rxAir": "Tempo di trasmissione RX (totale): {seconds} s",
|
||||||
|
"radioStats_chartCaption": "Livello di rumore (dBm) misurato su campioni recenti.",
|
||||||
|
"radioStats_stripNoise": "Livello di rumore: {noiseDbm} dBm",
|
||||||
|
"radioStats_stripWaiting": "Recupero delle statistiche radio…",
|
||||||
|
"radioStats_settingsTile": "Statistiche radio",
|
||||||
|
"radioStats_settingsSubtitle": "Livello di rumore, RSSI, rapporto segnale/rumore (SNR) e tempo di trasmissione",
|
||||||
|
"@translation_downloadFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_composerTitle": "Tradurre prima di inviare",
|
||||||
|
"translation_enableSubtitle": "Tradurre i messaggi in arrivo e consentire la traduzione preventiva prima dell'invio.",
|
||||||
|
"translation_enableTitle": "Abilitare la traduzione",
|
||||||
|
"translation_title": "Traduzione",
|
||||||
|
"translation_composerSubtitle": "Controlla lo stato predefinito dell'icona di traduzione del compositore.",
|
||||||
|
"translation_targetLanguage": "Lingua di destinazione",
|
||||||
|
"translation_useAppLanguage": "Utilizza la lingua dell'app",
|
||||||
|
"translation_downloadedModelLabel": "Modello scaricato",
|
||||||
|
"translation_presetModelLabel": "Modello predefinito di Hugging Face",
|
||||||
|
"translation_manualUrlLabel": "URL del modello manuale",
|
||||||
|
"translation_downloadModel": "Scarica il modello",
|
||||||
|
"translation_downloading": "Inizio download...",
|
||||||
|
"translation_working": "Lavoro...",
|
||||||
|
"translation_stop": "Smetta",
|
||||||
|
"translation_downloadedModels": "Modelli scaricati",
|
||||||
|
"translation_mergingChunks": "Unione dei frammenti scaricati in un unico file...",
|
||||||
|
"translation_deleteModel": "Elimina modello",
|
||||||
|
"translation_modelDownloaded": "Modello di traduzione scaricato.",
|
||||||
|
"translation_downloadStopped": "Il download è stato interrotto.",
|
||||||
|
"translation_downloadFailed": "Download fallito: {error}",
|
||||||
|
"translation_enterUrlFirst": "Inserite innanzitutto l'URL del modello.",
|
||||||
|
"@scanner_linuxPairingPinPrompt": {
|
||||||
|
"placeholders": {
|
||||||
|
"deviceName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@translation_translateTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"language": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_messageTranslation": "Traduzione del messaggio",
|
||||||
|
"translation_translateBeforeSending": "Tradurre prima di inviare",
|
||||||
|
"translation_composerDisabledHint": "Invia messaggi utilizzando la lingua originale, scritta.",
|
||||||
|
"translation_composerEnabledHint": "I messaggi verranno tradotti prima di essere inviati.",
|
||||||
|
"translation_translateTo": "Tradurre in {language}",
|
||||||
|
"translation_translationOptions": "Opzioni di traduzione",
|
||||||
|
"translation_systemLanguage": "Lingua del sistema",
|
||||||
|
"scanner_linuxPairingPinPrompt": "Inserire il codice PIN per {deviceName} (lasciare vuoto se non presente).",
|
||||||
|
"scanner_linuxPairingShowPin": "Mostra PIN",
|
||||||
|
"scanner_linuxPairingPinTitle": "PIN per l'accoppiamento Bluetooth",
|
||||||
|
"scanner_linuxPairingHidePin": "Nascondi il PIN",
|
||||||
|
"repeater_cliQuickClockSync": "Sincronizzazione dell'orologio",
|
||||||
|
"repeater_cliQuickDiscovery": "Scopri i Vicini",
|
||||||
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Invia automaticamente il comando \"sincronizzazione dell'orologio\" dopo un login riuscito.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Sincronizzazione dell'orologio dopo il login",
|
||||||
|
"repeater_guest": "Informazioni sul ripetitore",
|
||||||
|
"repeater_guestTools": "Strumenti per gli ospiti",
|
||||||
|
"chat_sendMessage": "Invia messaggio",
|
||||||
|
"room_guest": "Informazioni sul server",
|
||||||
|
"repeater_getCategory": "Ottenere valori",
|
||||||
|
"repeater_powerMgmt": "Gestione dell'energia",
|
||||||
|
"repeater_sensors": "Sensori",
|
||||||
|
"repeater_cliHelpPowerOff": "Disattiva il dispositivo. (non ci si aspetta alcuna risposta)",
|
||||||
|
"repeater_cliHelpClkReboot": "Riporta l'orologio a un'epoca nota e riavvia il dispositivo.",
|
||||||
|
"repeater_cliHelpAdvertZeroHop": "Invia un annuncio che raggiunge solo i vicini immediati (senza passaggi intermedi).",
|
||||||
|
"repeater_cliHelpStartOta": "Avvia un aggiornamento del firmware tramite la trasmissione radio su schede supportate.",
|
||||||
|
"repeater_cliHelpTime": "Imposta l'orario del dispositivo sui secondi dell'epoca Unix specificati. L'orario non può andare indietro.",
|
||||||
|
"repeater_cliHelpBoard": "Indica il produttore della scheda e l'identificatore hardware.",
|
||||||
|
"repeater_cliHelpDiscoverNeighbors": "Invia una richiesta di scoperta di nodi ai vicini. (Solo per ripetitori)",
|
||||||
|
"repeater_cliHelpPowersaving": "Indica se la modalità di risparmio energetico è attiva o disattivata.",
|
||||||
|
"repeater_cliHelpPowersavingOnOff": "Abilita o disabilita la modalità di risparmio energetico (se supportata).",
|
||||||
|
"repeater_cliHelpErase": "(Solo per sistemi di serializzazione) Formatta il file system del dispositivo. Elimina tutte le impostazioni e i contatti.",
|
||||||
|
"repeater_cliHelpSetDutyCycle": "Imposta il ciclo di trasmissione massimo consentito in percentuale (da 1 a 100). Regola internamente il fattore di tempo di trasmissione.",
|
||||||
|
"repeater_cliHelpSetPrvKey": "(Solo per serie) Sostituisce la chiave privata di identificazione del dispositivo. È necessario riavviare il dispositivo per applicare la modifica. Genera una nuova chiave pubblica.",
|
||||||
|
"repeater_cliHelpSetRadioRxGain": "(Solo per SX126x) Permette di attivare un guadagno RX potenziato per una maggiore sensibilità a correnti più elevate.",
|
||||||
|
"repeater_cliHelpSetOwnerInfo": "Definisce la stringa contenente le informazioni di contatto del proprietario, presente negli annunci. Utilizzare '|' per i newline.",
|
||||||
|
"repeater_cliHelpSetPathHashMode": "Imposta la modalità di hashing del percorso. 0 = modalità legacy, 1 = modalità standard, 2 = modalità rigorosa. Influisce su come vengono abbinati i percorsi di routing.",
|
||||||
|
"repeater_cliHelpSetLoopDetect": "Imposta il livello di sensibilità per il rilevamento dei loop di routing: disattivato, minimo, moderato o rigoroso.",
|
||||||
|
"repeater_cliHelpSetFreq": "(Solo per la funzione di regolazione della frequenza) Imposta rapidamente la frequenza desiderata. È necessario riavviare il dispositivo. Si consiglia di utilizzare la funzione \"imposta radio\" per impostare tutti i parametri radio.",
|
||||||
|
"repeater_cliHelpSetBridgeChannel": "(Solo per il bridge ESPNow) Imposta il canale Wi-Fi (da 1 a 14) utilizzato dal bridge.",
|
||||||
|
"repeater_cliHelpGetName": "Mostra il nome del nodo configurato.",
|
||||||
|
"repeater_cliHelpGetRole": "Indica il ruolo del firmware (ripetitore, server per stanza, ecc.).",
|
||||||
|
"repeater_cliHelpGetPublicKey": "Mostra la chiave pubblica del dispositivo.",
|
||||||
|
"repeater_cliHelpGetPrvKey": "(Solo per serie) Visualizza la chiave privata del dispositivo. Trattala come una informazione riservata.",
|
||||||
|
"repeater_cliHelpGetRepeat": "Indica se la funzione di inoltro dei pacchetti (funzione di ripetitore) è attiva o disattivata.",
|
||||||
|
"repeater_cliHelpGetTx": "Mostra la potenza attuale in dBm.",
|
||||||
|
"repeater_cliHelpGetFreq": "Mostra la frequenza radio configurata in MHz.",
|
||||||
|
"repeater_cliHelpGetRadio": "Visualizza tutti i parametri radio: frequenza, larghezza di banda, fattore di spreading, tasso di codifica.",
|
||||||
|
"repeater_cliHelpGetRadioRxGain": "(Solo per i moduli SX126x) Mostra lo stato del guadagno potenziato del RX.",
|
||||||
|
"repeater_cliHelpGetAf": "Mostra il fattore di trasmissione attuale.",
|
||||||
|
"repeater_cliHelpGetDutyCycle": "Mostra il ciclo di lavoro attuale consentito in percentuale.",
|
||||||
|
"repeater_cliHelpGetIntThresh": "Mostra il limite di interferenza del canale in dB.",
|
||||||
|
"repeater_cliHelpGetAgcResetInterval": "Indica l'intervallo di reset dell'AGC in secondi.",
|
||||||
|
"repeater_cliHelpGetMultiAcks": "Indica se la modalità \"ACK doppio\" è attiva (1) o disattivata (0).",
|
||||||
|
"repeater_cliHelpGetAllowReadOnly": "Indica se è consentito l'accesso in sola lettura per gli ospiti.",
|
||||||
|
"repeater_cliHelpGetAdvertInterval": "Indica l'intervallo pubblicitario locale in minuti.",
|
||||||
|
"repeater_cliHelpGetFloodAdvertInterval": "Indica l'intervallo pubblicitario per la trasmissione del servizio di allerta alluvioni, espresso in ore.",
|
||||||
|
"repeater_cliHelpGetGuestPassword": "Visualizza la password del guest configurata.",
|
||||||
|
"repeater_cliHelpGetLat": "Mostra la latitudine configurata.",
|
||||||
|
"repeater_cliHelpGetLon": "Mostra la longitudine impostata.",
|
||||||
|
"repeater_cliHelpGetRxDelay": "Mostra il valore base di rxdelay.",
|
||||||
|
"repeater_cliHelpGetTxDelay": "Mostra il fattore di ritardo in modalità di allarme.",
|
||||||
|
"repeater_cliHelpGetDirectTxDelay": "Mostra il fattore di ritardo in modalità diretta.",
|
||||||
|
"repeater_cliHelpGetFloodMax": "Mostra il numero massimo di salti dovuto all'inondazione.",
|
||||||
|
"repeater_cliHelpGetOwnerInfo": "Visualizza la stringa contenente le informazioni di contatto del proprietario.",
|
||||||
|
"repeater_cliHelpGetPathHashMode": "Mostra la modalità \"hash del percorso\" (0/1/2).",
|
||||||
|
"repeater_cliHelpGetLoopDetect": "Indica la sensibilità alla rilevazione di loop.",
|
||||||
|
"repeater_cliHelpGetAcl": "(Solo per serie) Elenca le voci di controllo degli accessi su un ripetitore.",
|
||||||
|
"repeater_cliHelpGetBridgeEnabled": "Indica se il ponte è attivo.",
|
||||||
|
"repeater_cliHelpGetBridgeDelay": "Mostra il ritardo del ponte in millisecondi.",
|
||||||
|
"repeater_cliHelpGetBridgeSource": "Indica se il bridge sta inviando pacchetti RX o TX.",
|
||||||
|
"repeater_cliHelpGetBridgeBaud": "(Solo per l'adattatore RS232) Visualizza la velocità di trasmissione del bridge.",
|
||||||
|
"repeater_cliHelpGetBridgeChannel": "(Solo per il bridge ESPNow) Visualizza il canale WiFi del bridge.",
|
||||||
|
"repeater_cliHelpGetBridgeSecret": "(Solo per il bridge ESPNow) Visualizza la chiave segreta condivisa.",
|
||||||
|
"repeater_cliHelpGetBootloaderVer": "(Solo per NRF52) Visualizza la versione del bootloader.",
|
||||||
|
"repeater_cliHelpGetAdcMultiplier": "Mostra il moltiplicatore ADC (adattamento della tensione della batteria).",
|
||||||
|
"repeater_cliHelpGetPwrMgtSupport": "Indica se il sistema dispone di funzionalità di gestione dell'energia.",
|
||||||
|
"repeater_cliHelpGetPwrMgtSource": "Indica la fonte di alimentazione attuale: esterna o batteria.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootReason": "Mostra le ragioni più recenti per il ripristino e lo spegnimento.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootMv": "Mostra la tensione della batteria al momento dell'accensione, misurata in millivolt (mV).",
|
||||||
|
"repeater_cliHelpSensorGet": "Legge un valore di configurazione personalizzato per un sensore tramite un tasto.",
|
||||||
|
"repeater_cliHelpSensorSet": "Definisce una configurazione personalizzata per un sensore.",
|
||||||
|
"repeater_cliHelpSensorList": "Elenca tutte le impostazioni personalizzate dei sensori, organizzate in pagine a partire da un indice di inizio opzionale.",
|
||||||
|
"repeater_cliHelpRegionDefault": "Mostra l'ambito predefinito corrente.",
|
||||||
|
"repeater_cliHelpRegionDefaultSet": "Definisce l'ambito regionale predefinito. Utilizzare \"<null>\" per cancellare.",
|
||||||
|
"repeater_cliHelpRegionListAllowed": "Elenca le regioni che consentono il transito di veicoli in caso di allagamenti.",
|
||||||
|
"repeater_cliHelpRegionListDenied": "Elenca le regioni che vietano il transito in caso di alluvioni.",
|
||||||
|
"repeater_cliHelpStatsPackets": "(Solo per la visualizzazione dei dati seriali) Mostra statistiche a livello di pacchetto.",
|
||||||
|
"repeater_cliHelpStatsRadio": "(Solo per serie TV) Visualizza statistiche relative alla trasmissione radiofonica.",
|
||||||
|
"repeater_cliHelpStatsCore": "(Solo per serie) Visualizza le statistiche del firmware di base.",
|
||||||
|
"common_done": "Done",
|
||||||
|
"background_serviceTitle": "MeshCore running",
|
||||||
|
"background_serviceText": "Keeping BLE connected",
|
||||||
|
"appSettings_translationModelDeleted": "Deleted {name}",
|
||||||
|
"@appSettings_translationModelDeleted": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
|
||||||
|
"@appSettings_translationModelDeleteFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channels_channelUpdateFailed": "Failed to update channel: {error}",
|
||||||
|
"@channels_channelUpdateFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map_type": "Type",
|
||||||
|
"map_path": "Path",
|
||||||
|
"map_location": "Location",
|
||||||
|
"map_estLocation": "Est. Location",
|
||||||
|
"map_publicKey": "Public Key",
|
||||||
|
"map_publicKeyPrefixHint": "e.g. ab12",
|
||||||
|
"contact_typeChat": "Chat",
|
||||||
|
"contact_typeRepeater": "Repeater",
|
||||||
|
"contact_typeRoom": "Room",
|
||||||
|
"contact_typeSensor": "Sensor",
|
||||||
|
"contact_typeUnknown": "Unknown",
|
||||||
|
"channels_via": "via {path}",
|
||||||
|
"chat_score": "Score",
|
||||||
|
"map_sharedAt": "Condiviso",
|
||||||
|
"@losBlockedSpotChip": {
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@losSelectedObstructionDetails": {
|
||||||
|
"placeholders": {
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromA": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromB": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"losSelectedObstructionTitle": "Ostacolo selezionato",
|
||||||
|
"losBlockedSpotsHint": "Tocca un punto bloccato sulla mappa per evidenziarlo.",
|
||||||
|
"losBlockedSpotsTitle": "Posti occupati",
|
||||||
|
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
|
||||||
|
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).",
|
||||||
|
"settings_companionDebugLogSubtitle": "Comandi, risposte e dati grezzi per protocolli BLE/TCP/USB",
|
||||||
|
"settings_companionDebugLog": "Registro di debug per il supporto",
|
||||||
|
"chat_newMessages": "Nuovi messaggi",
|
||||||
|
"chat_markAsUnread": "Segna come non letto",
|
||||||
|
"repeater_chanUtil": "Utilizzo del canale",
|
||||||
|
"dialog_connectCompanion": "Connettiti a un dispositivo companion per accedere alle funzionalità di ripetitore e server stanza.",
|
||||||
|
"dialog_disconnectedTitle": "Disconnesso",
|
||||||
|
"dialog_disconnectedMessage": "Sei stato disconnesso dal tuo compagno.",
|
||||||
|
"contact_connectCompanion": "Connettiti a un companion per accedere alle funzioni del repeater e del server di stanza."
|
||||||
}
|
}
|
||||||
|
|||||||
+2357
File diff suppressed because it is too large
Load Diff
+2357
File diff suppressed because it is too large
Load Diff
+1709
-45
File diff suppressed because it is too large
Load Diff
+1049
-48
File diff suppressed because it is too large
Load Diff
+1065
-59
File diff suppressed because it is too large
Load Diff
+1004
-27
File diff suppressed because it is too large
Load Diff
+1050
-50
File diff suppressed because it is too large
Load Diff
+1080
-72
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1050
-46
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1112
-116
File diff suppressed because it is too large
Load Diff
+1292
-276
File diff suppressed because it is too large
Load Diff
+1046
-50
File diff suppressed because it is too large
Load Diff
+1026
-25
File diff suppressed because it is too large
Load Diff
+1048
-53
File diff suppressed because it is too large
Load Diff
+1042
-50
File diff suppressed because it is too large
Load Diff
+1037
-51
File diff suppressed because it is too large
Load Diff
+1198
-201
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+553
-97
@@ -76,7 +76,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scanner_stop": "Stoppen",
|
"scanner_stop": "Stoppen",
|
||||||
"scanner_scan": "Scan",
|
"scanner_scan": "Scannen",
|
||||||
"device_quickSwitch": "Snelle overschakeling",
|
"device_quickSwitch": "Snelle overschakeling",
|
||||||
"device_meshcore": "MeshCore",
|
"device_meshcore": "MeshCore",
|
||||||
"settings_title": "Instellingen",
|
"settings_title": "Instellingen",
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
"settings_appSettings": "App Instellingen",
|
"settings_appSettings": "App Instellingen",
|
||||||
"settings_appSettingsSubtitle": "Notificaties, berichten en kaartinstellingen",
|
"settings_appSettingsSubtitle": "Notificaties, berichten en kaartinstellingen",
|
||||||
"settings_nodeSettings": "Node Instellingen",
|
"settings_nodeSettings": "Node Instellingen",
|
||||||
"settings_nodeName": "Node Naam",
|
"settings_nodeName": "Nodenaam",
|
||||||
"settings_nodeNameNotSet": "Niet ingesteld",
|
"settings_nodeNameNotSet": "Niet ingesteld",
|
||||||
"settings_nodeNameHint": "Voer nodenaam in",
|
"settings_nodeNameHint": "Voer nodenaam in",
|
||||||
"settings_nodeNameUpdated": "Naam bijgewerkt",
|
"settings_nodeNameUpdated": "Naam bijgewerkt",
|
||||||
@@ -98,30 +98,32 @@
|
|||||||
"settings_locationInvalid": "Ongeldige breedtegraad of lengtegraad.",
|
"settings_locationInvalid": "Ongeldige breedtegraad of lengtegraad.",
|
||||||
"settings_latitude": "Breedtegraad",
|
"settings_latitude": "Breedtegraad",
|
||||||
"settings_longitude": "Lengtegraad",
|
"settings_longitude": "Lengtegraad",
|
||||||
"settings_privacyMode": "Privacy Mode",
|
"settings_privacyMode": "Privacy-modus",
|
||||||
"settings_privacyModeSubtitle": "Naam/locatie verbergen in advertenties",
|
"settings_privacyModeSubtitle": "Naam/locatie verbergen in advertenties",
|
||||||
"settings_privacyModeToggle": "Schakel privacy modus in om je naam en locatie in advertenties te verbergen.",
|
"settings_privacyModeToggle": "Schakel privacy modus in om je naam en locatie in advertenties te verbergen.",
|
||||||
"settings_privacyModeEnabled": "Privacy modus is ingeschakeld",
|
"settings_privacyModeEnabled": "Privacy modus is ingeschakeld",
|
||||||
"settings_privacyModeDisabled": "Privacy modus is uitgeschakeld",
|
"settings_privacyModeDisabled": "Privacy modus is uitgeschakeld",
|
||||||
"settings_actions": "Acties",
|
"settings_actions": "Acties",
|
||||||
|
"settings_deleteAllPaths": "Delete All Paths",
|
||||||
|
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
|
||||||
"settings_sendAdvertisement": "Verzend Advertentie",
|
"settings_sendAdvertisement": "Verzend Advertentie",
|
||||||
"settings_sendAdvertisementSubtitle": "Nu aanwezigheid uitzenden",
|
"settings_sendAdvertisementSubtitle": "Nu aanwezigheid uitzenden",
|
||||||
"settings_advertisementSent": "Advertentie verzonden",
|
"settings_advertisementSent": "Advertentie verzonden",
|
||||||
"settings_syncTime": "Synchronisatie Tijd",
|
"settings_syncTime": "Tijd Synchroniseren",
|
||||||
"settings_syncTimeSubtitle": "Stel de apparaatklok in op de tijd van de telefoon.",
|
"settings_syncTimeSubtitle": "Stel de apparaatklok in op de tijd van de telefoon.",
|
||||||
"settings_timeSynchronized": "Tijdsynchronisatie",
|
"settings_timeSynchronized": "Tijdsynchronisatie",
|
||||||
"settings_refreshContacts": "Contacten vernieuwen",
|
"settings_refreshContacts": "Contacten vernieuwen",
|
||||||
"settings_refreshContactsSubtitle": "Contactlijst opnieuw laden van het apparaat",
|
"settings_refreshContactsSubtitle": "Contactlijst opnieuw laden van het apparaat",
|
||||||
"settings_rebootDevice": "Apparaat opnieuw opstarten",
|
"settings_rebootDevice": "Apparaat opnieuw opstarten",
|
||||||
"settings_rebootDeviceSubtitle": "Herstart het MeshCore apparaat",
|
"settings_rebootDeviceSubtitle": "Herstart het MeshCore-apparaat",
|
||||||
"settings_rebootDeviceConfirm": "Ben je er zeker van dat je het apparaat opnieuw wilt opstarten? Je wordt losgekoppeld.",
|
"settings_rebootDeviceConfirm": "Ben je er zeker van dat je het apparaat opnieuw wilt opstarten? Je wordt losgekoppeld.",
|
||||||
"settings_debug": "Debug",
|
"settings_debug": "Foutopsporing",
|
||||||
"settings_bleDebugLog": "BLE Debug Log",
|
"settings_bleDebugLog": "BLE Debug Log",
|
||||||
"settings_bleDebugLogSubtitle": "BLE commando's, antwoorden en ruwe data",
|
"settings_bleDebugLogSubtitle": "BLE commando's, antwoorden en ruwe data",
|
||||||
"settings_appDebugLog": "App Debug Log",
|
"settings_appDebugLog": "Debuglog van de app",
|
||||||
"settings_appDebugLogSubtitle": "Toepassingsdebugberichten",
|
"settings_appDebugLogSubtitle": "Toepassingsdebugberichten",
|
||||||
"settings_about": "Over",
|
"settings_about": "Over",
|
||||||
"settings_aboutVersion": "MeshCore Open v{version}",
|
"settings_aboutVersion": "MeshCore Open versie {version}",
|
||||||
"@settings_aboutVersion": {
|
"@settings_aboutVersion": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"version": {
|
"version": {
|
||||||
@@ -138,14 +140,14 @@
|
|||||||
"settings_infoPublicKey": "Openbare Sleutel",
|
"settings_infoPublicKey": "Openbare Sleutel",
|
||||||
"settings_infoContactsCount": "Aantal Contacten",
|
"settings_infoContactsCount": "Aantal Contacten",
|
||||||
"settings_infoChannelCount": "Aantal Kanalen",
|
"settings_infoChannelCount": "Aantal Kanalen",
|
||||||
"settings_presets": "Presets",
|
"settings_presets": "Voorgeprogrammeerde instellingen",
|
||||||
"settings_frequency": "Frequentie (MHz)",
|
"settings_frequency": "Frequentie (MHz)",
|
||||||
"settings_frequencyHelper": "300,0 - 2500,0",
|
"settings_frequencyHelper": "300,0 - 2500,0",
|
||||||
"settings_frequencyInvalid": "Ongeldige frequentie (300-2500 MHz)",
|
"settings_frequencyInvalid": "Ongeldige frequentie (300-2500 MHz)",
|
||||||
"settings_bandwidth": "Bandbreedte",
|
"settings_bandwidth": "Bandbreedte",
|
||||||
"settings_spreadingFactor": "Spreadsnelheid",
|
"settings_spreadingFactor": "Spreadsnelheid",
|
||||||
"settings_codingRate": "Codeertarief",
|
"settings_codingRate": "Codeertarief",
|
||||||
"settings_txPower": "TX Vermogen (dBm)",
|
"settings_txPower": "TX-Vermogen (dBm)",
|
||||||
"settings_txPowerHelper": "0 - 22",
|
"settings_txPowerHelper": "0 - 22",
|
||||||
"settings_txPowerInvalid": "Ongeldige TX-vermogen (0-22 dBm)",
|
"settings_txPowerInvalid": "Ongeldige TX-vermogen (0-22 dBm)",
|
||||||
"settings_error": "Fout: {message}",
|
"settings_error": "Fout: {message}",
|
||||||
@@ -164,19 +166,19 @@
|
|||||||
"appSettings_themeDark": "Donker",
|
"appSettings_themeDark": "Donker",
|
||||||
"appSettings_language": "Taal",
|
"appSettings_language": "Taal",
|
||||||
"appSettings_languageSystem": "Standaardinstelling",
|
"appSettings_languageSystem": "Standaardinstelling",
|
||||||
"appSettings_languageEn": "English",
|
"appSettings_languageEn": "Engels",
|
||||||
"appSettings_languageFr": "Français",
|
"appSettings_languageFr": "Frans",
|
||||||
"appSettings_languageEs": "Español",
|
"appSettings_languageEs": "Spaans",
|
||||||
"appSettings_languageDe": "Deutsch",
|
"appSettings_languageDe": "Duits",
|
||||||
"appSettings_languagePl": "Polski",
|
"appSettings_languagePl": "Pools",
|
||||||
"appSettings_languageSl": "Slovenščina",
|
"appSettings_languageSl": "Sloveens",
|
||||||
"appSettings_languagePt": "Português",
|
"appSettings_languagePt": "Portugees",
|
||||||
"appSettings_languageIt": "Italiano",
|
"appSettings_languageIt": "Italiaans",
|
||||||
"appSettings_languageZh": "中文",
|
"appSettings_languageZh": "Chinees",
|
||||||
"appSettings_languageSv": "Svenska",
|
"appSettings_languageSv": "Zweeds",
|
||||||
"appSettings_languageNl": "Nederlands",
|
"appSettings_languageNl": "Nederlands",
|
||||||
"appSettings_languageSk": "Slovenčina",
|
"appSettings_languageSk": "Sloveens",
|
||||||
"appSettings_languageBg": "Български",
|
"appSettings_languageBg": "Bulgaars",
|
||||||
"appSettings_notifications": "Notificaties",
|
"appSettings_notifications": "Notificaties",
|
||||||
"appSettings_enableNotifications": "Notificaties inschakelen",
|
"appSettings_enableNotifications": "Notificaties inschakelen",
|
||||||
"appSettings_enableNotificationsSubtitle": "Ontvang meldingen voor berichten en advertenties",
|
"appSettings_enableNotificationsSubtitle": "Ontvang meldingen voor berichten en advertenties",
|
||||||
@@ -232,11 +234,11 @@
|
|||||||
"appSettings_mapTimeFilter": "Filter tijd op kaart",
|
"appSettings_mapTimeFilter": "Filter tijd op kaart",
|
||||||
"appSettings_showNodesDiscoveredWithin": "Toon nodes ontdekt binnen:",
|
"appSettings_showNodesDiscoveredWithin": "Toon nodes ontdekt binnen:",
|
||||||
"appSettings_allTime": "Altijd",
|
"appSettings_allTime": "Altijd",
|
||||||
"appSettings_lastHour": "Laat uur",
|
"appSettings_lastHour": "Afgelopen uur",
|
||||||
"appSettings_last6Hours": "laatste 6 uur",
|
"appSettings_last6Hours": "Afgelopen 6 uur",
|
||||||
"appSettings_last24Hours": "De laatste 24 uur",
|
"appSettings_last24Hours": "Afgelopen 24 uur",
|
||||||
"appSettings_lastWeek": "Laatste week",
|
"appSettings_lastWeek": "Afgelopen week",
|
||||||
"appSettings_offlineMapCache": "Offline Kaarten Cache",
|
"appSettings_offlineMapCache": "Offline Kaartcache",
|
||||||
"appSettings_noAreaSelected": "Geen gebied geselecteerd",
|
"appSettings_noAreaSelected": "Geen gebied geselecteerd",
|
||||||
"appSettings_areaSelectedZoom": "Geselecteerd gebied (zoom {minZoom}-{maxZoom})",
|
"appSettings_areaSelectedZoom": "Geselecteerd gebied (zoom {minZoom}-{maxZoom})",
|
||||||
"@appSettings_areaSelectedZoom": {
|
"@appSettings_areaSelectedZoom": {
|
||||||
@@ -249,7 +251,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"appSettings_debugCard": "Debug",
|
"appSettings_debugCard": "Foutopsporing",
|
||||||
"appSettings_appDebugLogging": "App Debuggen Loggen",
|
"appSettings_appDebugLogging": "App Debuggen Loggen",
|
||||||
"appSettings_appDebugLoggingSubtitle": "Log app debugberichten voor probleemoplossing",
|
"appSettings_appDebugLoggingSubtitle": "Log app debugberichten voor probleemoplossing",
|
||||||
"appSettings_appDebugLoggingEnabled": "App debug logging is ingeschakeld",
|
"appSettings_appDebugLoggingEnabled": "App debug logging is ingeschakeld",
|
||||||
@@ -271,7 +273,7 @@
|
|||||||
},
|
},
|
||||||
"contacts_manageRepeater": "Beheer Repeater",
|
"contacts_manageRepeater": "Beheer Repeater",
|
||||||
"contacts_roomLogin": "Ruimte Inloggen",
|
"contacts_roomLogin": "Ruimte Inloggen",
|
||||||
"contacts_openChat": "Open Chat",
|
"contacts_openChat": "Open gesprek",
|
||||||
"contacts_editGroup": "Groep bewerken",
|
"contacts_editGroup": "Groep bewerken",
|
||||||
"contacts_deleteGroup": "Groep verwijderen",
|
"contacts_deleteGroup": "Groep verwijderen",
|
||||||
"contacts_deleteGroupConfirm": "Verwijder {groupName}?",
|
"contacts_deleteGroupConfirm": "Verwijder {groupName}?",
|
||||||
@@ -285,6 +287,7 @@
|
|||||||
"contacts_newGroup": "Nieuwe Groep",
|
"contacts_newGroup": "Nieuwe Groep",
|
||||||
"contacts_groupName": "Groepnaam",
|
"contacts_groupName": "Groepnaam",
|
||||||
"contacts_groupNameRequired": "De groepnaam is verplicht.",
|
"contacts_groupNameRequired": "De groepnaam is verplicht.",
|
||||||
|
"contacts_groupNameReserved": "Deze groepsnaam is gereserveerd",
|
||||||
"contacts_groupAlreadyExists": "De groep \"{name}\" bestaat al.",
|
"contacts_groupAlreadyExists": "De groep \"{name}\" bestaat al.",
|
||||||
"@contacts_groupAlreadyExists": {
|
"@contacts_groupAlreadyExists": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -336,11 +339,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_hashtagChannel": "Hashtag kanaal",
|
|
||||||
"channels_public": "Openbaar",
|
"channels_public": "Openbaar",
|
||||||
"channels_private": "Privé",
|
"channels_private": "Privé",
|
||||||
"channels_publicChannel": "Open kanaal",
|
|
||||||
"channels_privateChannel": "Private kanaal",
|
|
||||||
"channels_editChannel": "Kanaal bewerken",
|
"channels_editChannel": "Kanaal bewerken",
|
||||||
"channels_muteChannel": "Kanaal dempen",
|
"channels_muteChannel": "Kanaal dempen",
|
||||||
"channels_unmuteChannel": "Kanaal dempen opheffen",
|
"channels_unmuteChannel": "Kanaal dempen opheffen",
|
||||||
@@ -387,6 +387,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_smazCompression": "SMAZ compressie",
|
"channels_smazCompression": "SMAZ compressie",
|
||||||
|
"channels_cyr2latCompression": "Cyr2Lat compressie",
|
||||||
|
"channels_cyr2latCompressionDscr": "Vervangt sommige Cyrillische tekens door Latijnse tekens bij het verzenden.",
|
||||||
|
"channels_cyr2latSettingsHeading": "Instellingen Cyr2Lat",
|
||||||
|
"channels_cyr2latSettingsSubheading": "Lijst met vervangingen",
|
||||||
|
"channels_cyr2latSettingsDscr": "Bewerk de JSON-configuratie voor tekenvervanging",
|
||||||
|
"channels_cyr2latSettingsDialogHint": "JSON-vervangingskaart",
|
||||||
|
"channels_cyr2latSettingsDialogWrongJSON": "Onjuiste JSON: {error}",
|
||||||
|
"settings_cyr2latProfileAdd": "Cyr2Lat-profiel toevoegen",
|
||||||
|
"settings_cyr2latProfileName": "Profielnaam",
|
||||||
|
"settings_cyr2latProfileNameEmpty": "Profielnaam mag niet leeg zijn",
|
||||||
|
"settings_cyr2latProfileAdded": "Profiel succesvol toegevoegd",
|
||||||
|
"settings_cyr2latProfileUpdated": "Profiel succesvol bijgewerkt",
|
||||||
|
"settings_cyr2latProfileEdit": "Cyr2Lat-profiel bewerken",
|
||||||
|
"settings_cyr2latProfileDelete": "Cyr2Lat-profiel verwijderen",
|
||||||
|
"settings_cyr2latProfileDeleted": "Profiel succesvol verwijderd",
|
||||||
|
"settings_cyr2latProfileDeleteDscr": "Weet u zeker dat u het profiel \"{name}\" wilt verwijderen?",
|
||||||
"channels_channelUpdated": "Kanaal \"{name}\" is bijgewerkt",
|
"channels_channelUpdated": "Kanaal \"{name}\" is bijgewerkt",
|
||||||
"@channels_channelUpdated": {
|
"@channels_channelUpdated": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -398,7 +414,7 @@
|
|||||||
"channels_publicChannelAdded": "Open kanaal toegevoegd",
|
"channels_publicChannelAdded": "Open kanaal toegevoegd",
|
||||||
"channels_sortBy": "Sorteren door",
|
"channels_sortBy": "Sorteren door",
|
||||||
"channels_sortManual": "Handmatig",
|
"channels_sortManual": "Handmatig",
|
||||||
"channels_sortAZ": "A-Z",
|
"channels_sortAZ": "Alfabetisch",
|
||||||
"channels_sortLatestMessages": "Recent berichten",
|
"channels_sortLatestMessages": "Recent berichten",
|
||||||
"channels_sortUnread": "Ongelezen",
|
"channels_sortUnread": "Ongelezen",
|
||||||
"chat_noMessages": "Nog geen berichten.",
|
"chat_noMessages": "Nog geen berichten.",
|
||||||
@@ -467,7 +483,7 @@
|
|||||||
"gifPicker_failedLoad": "GIF's konden niet worden geladen",
|
"gifPicker_failedLoad": "GIF's konden niet worden geladen",
|
||||||
"gifPicker_failedSearch": "Zoeken mislukt",
|
"gifPicker_failedSearch": "Zoeken mislukt",
|
||||||
"gifPicker_noInternet": "Geen internetverbinding",
|
"gifPicker_noInternet": "Geen internetverbinding",
|
||||||
"debugLog_appTitle": "App Debug Log",
|
"debugLog_appTitle": "Debuglog van de app",
|
||||||
"debugLog_bleTitle": "BLE Debug Log",
|
"debugLog_bleTitle": "BLE Debug Log",
|
||||||
"debugLog_copyLog": "Kopieer log",
|
"debugLog_copyLog": "Kopieer log",
|
||||||
"debugLog_clearLog": "Log wissen",
|
"debugLog_clearLog": "Log wissen",
|
||||||
@@ -476,7 +492,7 @@
|
|||||||
"debugLog_noEntries": "Nog geen debug logs beschikbaar.",
|
"debugLog_noEntries": "Nog geen debug logs beschikbaar.",
|
||||||
"debugLog_enableInSettings": "Schakel app debug logging in de instellingen",
|
"debugLog_enableInSettings": "Schakel app debug logging in de instellingen",
|
||||||
"debugLog_frames": "Ramen",
|
"debugLog_frames": "Ramen",
|
||||||
"debugLog_rawLogRx": "Raw Log-RX",
|
"debugLog_rawLogRx": "Niet-verwerkt Log-RX",
|
||||||
"debugLog_noBleActivity": "Geen BLE-activiteit nog.",
|
"debugLog_noBleActivity": "Geen BLE-activiteit nog.",
|
||||||
"debugFrame_length": "Frame Lengte: {count} bytes",
|
"debugFrame_length": "Frame Lengte: {count} bytes",
|
||||||
"@debugFrame_length": {
|
"@debugFrame_length": {
|
||||||
@@ -540,7 +556,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debugFrame_hexDump": "Hex Dump:",
|
"debugFrame_hexDump": "Hex-dump:",
|
||||||
"chat_pathManagement": "Beheer van Paden",
|
"chat_pathManagement": "Beheer van Paden",
|
||||||
"chat_routingMode": "Routeerwijze",
|
"chat_routingMode": "Routeerwijze",
|
||||||
"chat_autoUseSavedPath": "Automatisch (gebruik opgeslagen pad)",
|
"chat_autoUseSavedPath": "Automatisch (gebruik opgeslagen pad)",
|
||||||
@@ -598,7 +614,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chat_floodAuto": "Flood (auto)",
|
"chat_floodAuto": "Overstroming (van een auto)",
|
||||||
"chat_direct": "Direct",
|
"chat_direct": "Direct",
|
||||||
"chat_poiShared": "Gedeelde POI",
|
"chat_poiShared": "Gedeelde POI",
|
||||||
"chat_unread": "Nieuw: {count}",
|
"chat_unread": "Nieuw: {count}",
|
||||||
@@ -621,10 +637,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chat_invalidLink": "Ongeldig linkformaat",
|
"chat_invalidLink": "Ongeldig linkformaat",
|
||||||
"map_title": "Node Map",
|
"map_title": "Kaart van de knopen",
|
||||||
"map_noNodesWithLocation": "Geen nodes met locatiegegevens",
|
"map_noNodesWithLocation": "Geen nodes met locatiegegevens",
|
||||||
"map_nodesNeedGps": "Nodes moeten hun GPS-coördinaten delen\nom op de kaart te verschijnen",
|
"map_nodesNeedGps": "Nodes moeten hun GPS-coördinaten delen\nom op de kaart te verschijnen",
|
||||||
"map_nodesCount": "Nodes: {count}",
|
"map_nodesCount": "Knooppunten: {count}",
|
||||||
"@map_nodesCount": {
|
"@map_nodesCount": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {
|
"count": {
|
||||||
@@ -641,7 +657,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"map_chat": "Chat",
|
"map_chat": "Chat",
|
||||||
"map_repeater": "Repeater",
|
"map_repeater": "Herhaald",
|
||||||
"map_room": "Ruimte",
|
"map_room": "Ruimte",
|
||||||
"map_sensor": "Sensor",
|
"map_sensor": "Sensor",
|
||||||
"map_pinDm": "Verzenden als bericht (DM)",
|
"map_pinDm": "Verzenden als bericht (DM)",
|
||||||
@@ -654,7 +670,7 @@
|
|||||||
"map_flags": "Vlaggen",
|
"map_flags": "Vlaggen",
|
||||||
"map_shareMarkerHere": "Deel marker hier",
|
"map_shareMarkerHere": "Deel marker hier",
|
||||||
"map_pinLabel": "Label vastzetten",
|
"map_pinLabel": "Label vastzetten",
|
||||||
"map_label": "Label",
|
"map_label": "Etiket",
|
||||||
"map_pointOfInterest": "Interessepunt",
|
"map_pointOfInterest": "Interessepunt",
|
||||||
"map_sendToContact": "Verzenden naar contact",
|
"map_sendToContact": "Verzenden naar contact",
|
||||||
"map_sendToChannel": "Verzenden naar kanaal",
|
"map_sendToChannel": "Verzenden naar kanaal",
|
||||||
@@ -669,10 +685,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"map_connectToShareMarkers": "Verbind met een apparaat om markers te delen",
|
"map_connectToShareMarkers": "Verbind met een apparaat om markers te delen",
|
||||||
"map_filterNodes": "Filter Nodes",
|
"map_filterNodes": "Filternodes",
|
||||||
"map_nodeTypes": "Nodetypes",
|
"map_nodeTypes": "Nodetypes",
|
||||||
"map_chatNodes": "Chatnodes",
|
"map_chatNodes": "Chatnodes",
|
||||||
"map_repeaters": "Repeaters",
|
"map_repeaters": "Herhaalders",
|
||||||
"map_otherNodes": "Andere Nodes",
|
"map_otherNodes": "Andere Nodes",
|
||||||
"map_keyPrefix": "Prefix sleutel",
|
"map_keyPrefix": "Prefix sleutel",
|
||||||
"map_filterByKeyPrefix": "Filteren op sleutelvoorgemeld",
|
"map_filterByKeyPrefix": "Filteren op sleutelvoorgemeld",
|
||||||
@@ -681,7 +697,7 @@
|
|||||||
"map_showSharedMarkers": "Toon gedeelde markeringen",
|
"map_showSharedMarkers": "Toon gedeelde markeringen",
|
||||||
"map_lastSeenTime": "Laatste Bekeken Tijd",
|
"map_lastSeenTime": "Laatste Bekeken Tijd",
|
||||||
"map_sharedPin": "Gedeelde pin",
|
"map_sharedPin": "Gedeelde pin",
|
||||||
"map_joinRoom": "Sluit Kamer",
|
"map_joinRoom": "Kamer Toetreden",
|
||||||
"map_manageRepeater": "Beheer Repeater",
|
"map_manageRepeater": "Beheer Repeater",
|
||||||
"mapCache_title": "Offline Kaarten Cache",
|
"mapCache_title": "Offline Kaarten Cache",
|
||||||
"mapCache_selectAreaFirst": "Select een gebied om eerst in de cache op te slaan",
|
"mapCache_selectAreaFirst": "Select een gebied om eerst in de cache op te slaan",
|
||||||
@@ -695,7 +711,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mapCache_downloadAction": "Download",
|
"mapCache_downloadAction": "Downloaden",
|
||||||
"mapCache_cachedTiles": "Opgeslagen {count} tegels",
|
"mapCache_cachedTiles": "Opgeslagen {count} tegels",
|
||||||
"@mapCache_cachedTiles": {
|
"@mapCache_cachedTiles": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -813,7 +829,7 @@
|
|||||||
"login_savePasswordSubtitle": "Het wachtwoord wordt veilig op dit apparaat opgeslagen.",
|
"login_savePasswordSubtitle": "Het wachtwoord wordt veilig op dit apparaat opgeslagen.",
|
||||||
"login_repeaterDescription": "Voer het wachtwoord van de repeater in om instellingen en status te openen.",
|
"login_repeaterDescription": "Voer het wachtwoord van de repeater in om instellingen en status te openen.",
|
||||||
"login_roomDescription": "Voer het wachtwoord van de kamer in om toegang te krijgen tot instellingen en status.",
|
"login_roomDescription": "Voer het wachtwoord van de kamer in om toegang te krijgen tot instellingen en status.",
|
||||||
"login_routing": "Routing",
|
"login_routing": "Routeplanning",
|
||||||
"login_routingMode": "Routeerwijze",
|
"login_routingMode": "Routeerwijze",
|
||||||
"login_autoUseSavedPath": "Automatisch (gebruik opgeslagen pad)",
|
"login_autoUseSavedPath": "Automatisch (gebruik opgeslagen pad)",
|
||||||
"login_forceFloodMode": "Dwing Floodmodus Af",
|
"login_forceFloodMode": "Dwing Floodmodus Af",
|
||||||
@@ -877,10 +893,10 @@
|
|||||||
"path_tooLong": "Pad is te lang. Maximaal 64 sprongen zijn toegestaan.",
|
"path_tooLong": "Pad is te lang. Maximaal 64 sprongen zijn toegestaan.",
|
||||||
"path_setPath": "Stel Pad in",
|
"path_setPath": "Stel Pad in",
|
||||||
"repeater_management": "Beheer Repeaters",
|
"repeater_management": "Beheer Repeaters",
|
||||||
"repeater_managementTools": "Beheerinstrumenten",
|
"repeater_managementTools": "Beheerfuncties",
|
||||||
"repeater_status": "Status",
|
"repeater_status": "Status",
|
||||||
"repeater_statusSubtitle": "Status, statistieken en buren bekijken",
|
"repeater_statusSubtitle": "Status, statistieken en buren bekijken",
|
||||||
"repeater_telemetry": "Telemetry",
|
"repeater_telemetry": "Telemetrie",
|
||||||
"repeater_telemetrySubtitle": "Bekijk telemetrie van sensoren en systeemgegevens",
|
"repeater_telemetrySubtitle": "Bekijk telemetrie van sensoren en systeemgegevens",
|
||||||
"repeater_cli": "CLI",
|
"repeater_cli": "CLI",
|
||||||
"repeater_cliSubtitle": "Verzend commando's naar de repeater",
|
"repeater_cliSubtitle": "Verzend commando's naar de repeater",
|
||||||
@@ -911,8 +927,8 @@
|
|||||||
"repeater_lastRssi": "Laatste RSSI",
|
"repeater_lastRssi": "Laatste RSSI",
|
||||||
"repeater_lastSnr": "Laatste SNR",
|
"repeater_lastSnr": "Laatste SNR",
|
||||||
"repeater_noiseFloor": "Ruisvloer",
|
"repeater_noiseFloor": "Ruisvloer",
|
||||||
"repeater_txAirtime": "TX Airtime",
|
"repeater_txAirtime": "TX-zendtijd",
|
||||||
"repeater_rxAirtime": "RX Airtime",
|
"repeater_rxAirtime": "RX-zendtijd",
|
||||||
"repeater_packetStatistics": "Pakketstatistieken",
|
"repeater_packetStatistics": "Pakketstatistieken",
|
||||||
"repeater_sent": "Verzonden",
|
"repeater_sent": "Verzonden",
|
||||||
"repeater_received": "Ontvangen",
|
"repeater_received": "Ontvangen",
|
||||||
@@ -962,7 +978,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repeater_duplicatesFloodDirect": "Flood: {flood}, Direct: {direct}",
|
"repeater_duplicatesFloodDirect": "Overstroming: {flood}, Direct: {direct}",
|
||||||
"@repeater_duplicatesFloodDirect": {
|
"@repeater_duplicatesFloodDirect": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"flood": {
|
"flood": {
|
||||||
@@ -981,14 +997,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repeater_settingsTitle": "Repeater Instellingen",
|
"repeater_settingsTitle": "Repeaterinstellingen",
|
||||||
"repeater_basicSettings": "Basisinstellingen",
|
"repeater_basicSettings": "Basisinstellingen",
|
||||||
"repeater_repeaterName": "Repeaternaam",
|
"repeater_repeaterName": "Repeaternaam",
|
||||||
"repeater_repeaterNameHelper": "Weergave naam voor deze repeater",
|
"repeater_repeaterNameHelper": "Weergavenaam voor deze repeater",
|
||||||
"repeater_adminPassword": "Admin wachtwoord",
|
"repeater_adminPassword": "Admin wachtwoord",
|
||||||
"repeater_adminPasswordHelper": "Volledige toegangspaswoord",
|
"repeater_adminPasswordHelper": "Wachtwoord administratortoegang",
|
||||||
"repeater_guestPassword": "Wachtwoord Gast",
|
"repeater_guestPassword": "Gast wachtwoord",
|
||||||
"repeater_guestPasswordHelper": "Leesbeheer wachtwoord",
|
"repeater_guestPasswordHelper": "Wachtwoord gasttoegen",
|
||||||
"repeater_radioSettings": "Radio Instellingen",
|
"repeater_radioSettings": "Radio Instellingen",
|
||||||
"repeater_frequencyMhz": "Frequentie (MHz)",
|
"repeater_frequencyMhz": "Frequentie (MHz)",
|
||||||
"repeater_frequencyHelper": "300-2500 MHz",
|
"repeater_frequencyHelper": "300-2500 MHz",
|
||||||
@@ -997,7 +1013,7 @@
|
|||||||
"repeater_bandwidth": "Bandbreedte",
|
"repeater_bandwidth": "Bandbreedte",
|
||||||
"repeater_spreadingFactor": "Spreidingsfactor",
|
"repeater_spreadingFactor": "Spreidingsfactor",
|
||||||
"repeater_codingRate": "Codeertarief",
|
"repeater_codingRate": "Codeertarief",
|
||||||
"repeater_locationSettings": "Locatie Instellingen",
|
"repeater_locationSettings": "Locatie-instellingen",
|
||||||
"repeater_latitude": "Breedtegraad",
|
"repeater_latitude": "Breedtegraad",
|
||||||
"repeater_latitudeHelper": "Graadseconden (bijv. 37.7749)",
|
"repeater_latitudeHelper": "Graadseconden (bijv. 37.7749)",
|
||||||
"repeater_longitude": "Lengtegraad",
|
"repeater_longitude": "Lengtegraad",
|
||||||
@@ -1007,9 +1023,9 @@
|
|||||||
"repeater_packetForwardingSubtitle": "Repeater instellen om pakketten door te sturen",
|
"repeater_packetForwardingSubtitle": "Repeater instellen om pakketten door te sturen",
|
||||||
"repeater_guestAccess": "Toegang voor Gasten",
|
"repeater_guestAccess": "Toegang voor Gasten",
|
||||||
"repeater_guestAccessSubtitle": "Toegestane leesbeheer toegang voor gasten.",
|
"repeater_guestAccessSubtitle": "Toegestane leesbeheer toegang voor gasten.",
|
||||||
"repeater_privacyMode": "Privacy Modus",
|
"repeater_privacyMode": "Privacymodus",
|
||||||
"repeater_privacyModeSubtitle": "Naam/locatie verbergen in advertenties",
|
"repeater_privacyModeSubtitle": "Naam/locatie verbergen in advertenties",
|
||||||
"repeater_advertisementSettings": "Advertentie Instellingen",
|
"repeater_advertisementSettings": "Advertentie-instellingen",
|
||||||
"repeater_localAdvertInterval": "Lokale Advertentie Interval",
|
"repeater_localAdvertInterval": "Lokale Advertentie Interval",
|
||||||
"repeater_localAdvertIntervalMinutes": "{minutes} minuten",
|
"repeater_localAdvertIntervalMinutes": "{minutes} minuten",
|
||||||
"@repeater_localAdvertIntervalMinutes": {
|
"@repeater_localAdvertIntervalMinutes": {
|
||||||
@@ -1058,6 +1074,81 @@
|
|||||||
},
|
},
|
||||||
"repeater_confirm": "Bevestigen",
|
"repeater_confirm": "Bevestigen",
|
||||||
"repeater_settingsSaved": "Instellingen succesvol opgeslagen",
|
"repeater_settingsSaved": "Instellingen succesvol opgeslagen",
|
||||||
|
"repeater_rxGain": "Verhoogde RX-uitgang",
|
||||||
|
"repeater_rxGainHelper": "Hogere gevoeligheid, grotere stroomverbruik (alleen voor SX1262/SX1268)",
|
||||||
|
"repeater_refreshRxGain": "Versterk de reeds bestaande RX-verbetering.",
|
||||||
|
"repeater_multiAcks": "Meerdere bevestigingen",
|
||||||
|
"repeater_multiAcksSubtitle": "Bevestig berichten via verschillende routes voor een betere levering.",
|
||||||
|
"repeater_refreshMultiAcks": "Herhaal meerdere bevestigingen",
|
||||||
|
"repeater_networkHealth": "Netwerkgezondheid",
|
||||||
|
"repeater_loopDetect": "Detectie van beweging",
|
||||||
|
"repeater_loopDetectHelper": "Verzend pakketten die eruitzien als routing-lusjes",
|
||||||
|
"repeater_loopDetectOff": "Af",
|
||||||
|
"repeater_loopDetectMinimal": "Minimaal",
|
||||||
|
"repeater_loopDetectModerate": "Matig",
|
||||||
|
"repeater_loopDetectStrict": "Strikte",
|
||||||
|
"repeater_dutyCycle": "Werkcyclus",
|
||||||
|
"repeater_dutyCycleHelper": "Maximale percentage van de beschikbare uitzendtijd",
|
||||||
|
"repeater_dutyCyclePercent": "{percent}%",
|
||||||
|
"@repeater_dutyCyclePercent": {
|
||||||
|
"placeholders": {
|
||||||
|
"percent": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_ownerInfo": "Informatie over de operator",
|
||||||
|
"repeater_ownerInfoHelper": "Openbare metadata voor deze repeater",
|
||||||
|
"repeater_refreshOwnerInfo": "Herstel informatie over de operator",
|
||||||
|
"repeater_floodMax": "Maximale hoeveelheid hop",
|
||||||
|
"repeater_floodMaxHelper": "Maximale hoeveelheid hop die een pakket kan bevatten (0-64)",
|
||||||
|
"repeater_advancedSettings": "Geavanceerd",
|
||||||
|
"repeater_advancedSettingsSubtitle": "Regelhendels voor ervaren gebruikers",
|
||||||
|
"repeater_pathHashMode": "Hash-modus voor paden",
|
||||||
|
"repeater_pathHashModeHelper": "Bytes die gebruikt worden om de ID van deze repeater te coderen in flood-pad/lusdetectietags. 0=1 byte (256 ID's, tot 64 hops), 1=2 bytes (65.000 ID's, tot 32 hops), 2=3 bytes (16 miljoen ID's, tot 21 hops). Versies 1.13 en ouder gebruiken multi-byte paden – alleen na het activeren van het netwerk.",
|
||||||
|
"repeater_txDelay": "Vertraging bij Flood TX",
|
||||||
|
"repeater_txDelayHelper": "Herzendinterval voor verkeer tijdens overstromingen, als een veelvoud van de tijd die het pakket nodig heeft (0-2, standaard 0.5). Een hoger getal betekent minder botsingen, maar ook een langere leveringstijd.",
|
||||||
|
"repeater_directTxDelay": "Directe vertraging",
|
||||||
|
"repeater_directTxDelayHelper": "De overzending van tijdslots voor directe (niet-massaal) verkeer, als een vermenigvuldigingsfactor van de tijd die een pakket nodig heeft (0-2, standaard 0,3).",
|
||||||
|
"repeater_intThresh": "Grenswaarde voor interferentie",
|
||||||
|
"repeater_intThreshHelper": "De drempelwaarde is ingesteld zodat de radio storingen boven deze waarde kan detecteren en blokkeren. 0 staat voor \"uitgezet\" – alleen verhoog deze waarde als u fouten in een storingrijke band ziet.",
|
||||||
|
"repeater_agcResetInterval": "Interval voor het opnieuw instellen van AGC",
|
||||||
|
"repeater_agcResetIntervalHelper": "Hoe vaak moet u de automatische gainregeling van de radio opnieuw instellen om terug te keren van een situatie waarin de gain vastzit? Elke seconde, of elke 4e seconde. Het uitschakelen van de periodieke reset (0) zorgt ervoor dat de gain niet automatisch wordt aangepast.",
|
||||||
|
"repeater_actionsTitle": "Acties",
|
||||||
|
"repeater_sendAdvert": "Verzend advertentie over overstromingen",
|
||||||
|
"repeater_sendAdvertSubtitle": "Zend een advertentie over overstromingen uit via het netwerk.",
|
||||||
|
"repeater_sendAdvertZeroHop": "Verzend een advertentie zonder tussenliggende stap",
|
||||||
|
"repeater_sendAdvertZeroHopSubtitle": "Zend een advertentie met één enkele verbinding (zonder tussenliggende zenders).",
|
||||||
|
"repeater_clockSync": "Synchroniseer klok nu",
|
||||||
|
"repeater_clockSyncSubtitle": "Stel de tijd van je telefoon in op de repeater.",
|
||||||
|
"repeater_actionSucceeded": "{action} is gelukt",
|
||||||
|
"@repeater_actionSucceeded": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_actionFailed": "{action} failed: {error}",
|
||||||
|
"@repeater_actionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_settingsSavedRebootNeeded": "Instellingen opgeslagen – start de zender opnieuw om de wijzigingen toe te passen.",
|
||||||
|
"repeater_settingsPartialFailure": "Sommige instellingen zijn niet correct uitgevoerd: {failures}",
|
||||||
|
"@repeater_settingsPartialFailure": {
|
||||||
|
"placeholders": {
|
||||||
|
"failures": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repeater_errorSavingSettings": "Fout bij het opslaan van de instellingen: {error}",
|
"repeater_errorSavingSettings": "Fout bij het opslaan van de instellingen: {error}",
|
||||||
"@repeater_errorSavingSettings": {
|
"@repeater_errorSavingSettings": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1069,11 +1160,9 @@
|
|||||||
"repeater_refreshBasicSettings": "Basisinstellingen vernieuwen",
|
"repeater_refreshBasicSettings": "Basisinstellingen vernieuwen",
|
||||||
"repeater_refreshRadioSettings": "Radiozender Instellingen Verversen",
|
"repeater_refreshRadioSettings": "Radiozender Instellingen Verversen",
|
||||||
"repeater_refreshTxPower": "Nieuw laden TX-vermogen",
|
"repeater_refreshTxPower": "Nieuw laden TX-vermogen",
|
||||||
"repeater_refreshLocationSettings": "Instellingen Locatie Vernieuwen",
|
|
||||||
"repeater_refreshPacketForwarding": "Vernieuwen Pakket Doorversturing",
|
"repeater_refreshPacketForwarding": "Vernieuwen Pakket Doorversturing",
|
||||||
"repeater_refreshGuestAccess": "Toegang Gast Vernieuwen",
|
"repeater_refreshGuestAccess": "Toegang Gast Vernieuwen",
|
||||||
"repeater_refreshPrivacyMode": "Privacy Mode vernieuwen",
|
"repeater_refreshPrivacyMode": "Privacymode vernieuwen",
|
||||||
"repeater_refreshAdvertisementSettings": "Instellingen Advertentie Bijwerken",
|
|
||||||
"repeater_refreshed": "{label} is vernieuwd",
|
"repeater_refreshed": "{label} is vernieuwd",
|
||||||
"@repeater_refreshed": {
|
"@repeater_refreshed": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1090,11 +1179,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repeater_cliTitle": "Repeater CLI",
|
"repeater_cliTitle": "CLI-interface voor de repeater",
|
||||||
"repeater_debugNextCommand": "Debug Volgende Commando",
|
"repeater_debugNextCommand": "Debug Volgende Commando",
|
||||||
"repeater_commandHelp": "Help",
|
"repeater_commandHelp": "Help",
|
||||||
"repeater_clearHistory": "Verwijder Geschiedenis",
|
"repeater_clearHistory": "Geschiedenis Verwijderen",
|
||||||
"repeater_noCommandsSent": "Geen commando's verzonden nog.",
|
"repeater_noCommandsSent": "Nog geen commando's verzonden.",
|
||||||
"repeater_typeCommandOrUseQuick": "Typ een opdracht hieronder of gebruik snelle commando's",
|
"repeater_typeCommandOrUseQuick": "Typ een opdracht hieronder of gebruik snelle commando's",
|
||||||
"repeater_enterCommandHint": "Voer bevel in...",
|
"repeater_enterCommandHint": "Voer bevel in...",
|
||||||
"repeater_previousCommand": "Vorige opdracht",
|
"repeater_previousCommand": "Vorige opdracht",
|
||||||
@@ -1109,14 +1198,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repeater_cliQuickGetName": "Haal Naam op",
|
"repeater_cliQuickGetName": "Naam opvragen",
|
||||||
"repeater_cliQuickGetRadio": "Radio ontvangen",
|
"repeater_cliQuickGetRadio": "Radio-instellingen opvragen",
|
||||||
"repeater_cliQuickGetTx": "Krijg TX",
|
"repeater_cliQuickGetTx": "TX opvragen",
|
||||||
"repeater_cliQuickNeighbors": "Buren",
|
"repeater_cliQuickNeighbors": "Buren opvragen",
|
||||||
"repeater_cliQuickVersion": "Versie",
|
"repeater_cliQuickVersion": "Versie opvragen",
|
||||||
"repeater_cliQuickAdvertise": "Advertenties",
|
"repeater_cliQuickAdvertise": "Advertenties opvragen",
|
||||||
"repeater_cliQuickClock": "Tijd",
|
"repeater_cliQuickClock": "Tijd opvragen",
|
||||||
"repeater_cliHelpAdvert": "Verstuurt een advertentiepakket",
|
"repeater_cliHelpAdvert": "Advertentie uitzenden",
|
||||||
"repeater_cliHelpReboot": "Herstart het apparaat. (let op, je krijgt mogelijk een 'Timeout', wat normaal is)",
|
"repeater_cliHelpReboot": "Herstart het apparaat. (let op, je krijgt mogelijk een 'Timeout', wat normaal is)",
|
||||||
"repeater_cliHelpClock": "Toont de huidige tijd per apparaat's klok.",
|
"repeater_cliHelpClock": "Toont de huidige tijd per apparaat's klok.",
|
||||||
"repeater_cliHelpPassword": "Stelt een nieuw beheerderswachtwoord in voor het apparaat.",
|
"repeater_cliHelpPassword": "Stelt een nieuw beheerderswachtwoord in voor het apparaat.",
|
||||||
@@ -1175,7 +1264,7 @@
|
|||||||
"repeater_general": "Algemeen",
|
"repeater_general": "Algemeen",
|
||||||
"repeater_settingsCategory": "Instellingen",
|
"repeater_settingsCategory": "Instellingen",
|
||||||
"repeater_bridge": "Bruggen",
|
"repeater_bridge": "Bruggen",
|
||||||
"repeater_logging": "Logging",
|
"repeater_logging": "Loggen",
|
||||||
"repeater_neighborsRepeaterOnly": "Buren (Alleen repeaters)",
|
"repeater_neighborsRepeaterOnly": "Buren (Alleen repeaters)",
|
||||||
"repeater_regionManagementRepeaterOnly": "Regiobeheer (Alleen Repeater)",
|
"repeater_regionManagementRepeaterOnly": "Regiobeheer (Alleen Repeater)",
|
||||||
"repeater_regionNote": "Regio-commando's zijn geïntroduceerd om regio-definities en permissies te beheren.",
|
"repeater_regionNote": "Regio-commando's zijn geïntroduceerd om regio-definities en permissies te beheren.",
|
||||||
@@ -1202,7 +1291,7 @@
|
|||||||
},
|
},
|
||||||
"telemetry_batteryLabel": "Batterij",
|
"telemetry_batteryLabel": "Batterij",
|
||||||
"telemetry_voltageLabel": "Spanning",
|
"telemetry_voltageLabel": "Spanning",
|
||||||
"telemetry_mcuTemperatureLabel": "MCU Temperatuur",
|
"telemetry_mcuTemperatureLabel": "MCU-temperatuur",
|
||||||
"telemetry_temperatureLabel": "Temperatuur",
|
"telemetry_temperatureLabel": "Temperatuur",
|
||||||
"telemetry_currentLabel": "Huidig",
|
"telemetry_currentLabel": "Huidig",
|
||||||
"telemetry_batteryValue": "{percent}% / {volts}V",
|
"telemetry_batteryValue": "{percent}% / {volts}V",
|
||||||
@@ -1246,12 +1335,12 @@
|
|||||||
"channelPath_title": "Pakketpad",
|
"channelPath_title": "Pakketpad",
|
||||||
"channelPath_viewMap": "Kaart bekijken",
|
"channelPath_viewMap": "Kaart bekijken",
|
||||||
"channelPath_otherObservedPaths": "Overige Waargenomen Paden",
|
"channelPath_otherObservedPaths": "Overige Waargenomen Paden",
|
||||||
"channelPath_repeaterHops": "Repeater Hops",
|
"channelPath_repeaterHops": "Herhaalde sprongen",
|
||||||
"channelPath_noHopDetails": "De details van de pakket zijn niet verstrekt.",
|
"channelPath_noHopDetails": "De details van de pakket zijn niet verstrekt.",
|
||||||
"channelPath_messageDetails": "Details Bericht",
|
"channelPath_messageDetails": "Details Bericht",
|
||||||
"channelPath_senderLabel": "Afzender",
|
"channelPath_senderLabel": "Afzender",
|
||||||
"channelPath_timeLabel": "Tijd",
|
"channelPath_timeLabel": "Tijd",
|
||||||
"channelPath_repeatsLabel": "Repeats",
|
"channelPath_repeatsLabel": "Herhaalt",
|
||||||
"channelPath_pathLabel": "Pad {index}",
|
"channelPath_pathLabel": "Pad {index}",
|
||||||
"channelPath_observedLabel": "Waargenomen",
|
"channelPath_observedLabel": "Waargenomen",
|
||||||
"channelPath_observedPathTitle": "Waargenomen pad {index} • {hops}",
|
"channelPath_observedPathTitle": "Waargenomen pad {index} • {hops}",
|
||||||
@@ -1289,7 +1378,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channelPath_unknownPath": "Onbekend",
|
"channelPath_unknownPath": "Onbekend",
|
||||||
"channelPath_floodPath": "Flood",
|
"channelPath_floodPath": "Overstroming",
|
||||||
"channelPath_directPath": "Direct",
|
"channelPath_directPath": "Direct",
|
||||||
"channelPath_observedZeroOf": "0 van {total} sprongen",
|
"channelPath_observedZeroOf": "0 van {total} sprongen",
|
||||||
"@channelPath_observedZeroOf": {
|
"@channelPath_observedZeroOf": {
|
||||||
@@ -1345,12 +1434,12 @@
|
|||||||
"listFilter_tooltip": "Filteren en sorteren",
|
"listFilter_tooltip": "Filteren en sorteren",
|
||||||
"listFilter_sortBy": "Sorteren door",
|
"listFilter_sortBy": "Sorteren door",
|
||||||
"listFilter_latestMessages": "Recente berichten",
|
"listFilter_latestMessages": "Recente berichten",
|
||||||
"listFilter_heardRecently": "Hoor je onlangs",
|
"listFilter_heardRecently": "Recent gezien",
|
||||||
"listFilter_az": "A-Z",
|
"listFilter_az": "Alfabetisch",
|
||||||
"listFilter_filters": "Filters",
|
"listFilter_filters": "Filters",
|
||||||
"listFilter_all": "Alles",
|
"listFilter_all": "Alles",
|
||||||
"listFilter_users": "Gebruikers",
|
"listFilter_users": "Gebruikers",
|
||||||
"listFilter_repeaters": "Repeaters",
|
"listFilter_repeaters": "Herhalers",
|
||||||
"listFilter_roomServers": "Roomservers",
|
"listFilter_roomServers": "Roomservers",
|
||||||
"listFilter_unreadOnly": "Alleen ongelezen",
|
"listFilter_unreadOnly": "Alleen ongelezen",
|
||||||
"listFilter_newGroup": "Nieuwe groep",
|
"listFilter_newGroup": "Nieuwe groep",
|
||||||
@@ -1362,20 +1451,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repeater_neighbors": "Buren",
|
"repeater_neighbors": "Buren",
|
||||||
"repeater_neighborsSubtitle": "Bekijk nul hops buren.",
|
"repeater_neighborsSubtitle": "Bekijk nul-hopsburen.",
|
||||||
"neighbors_receivedData": "Ontvangen Buurdata",
|
"neighbors_receivedData": "Ontvangen Buurdata",
|
||||||
"neighbors_requestTimedOut": "Buren vragen om tijdelijk uitgeschakeld.",
|
"neighbors_requestTimedOut": "Buren vragen om tijdelijk uitgeschakeld.",
|
||||||
"neighbors_errorLoading": "Fout bij het laden van buren: {error}",
|
"neighbors_errorLoading": "Fout bij het laden van buren: {error}",
|
||||||
"neighbors_repeatersNeighbors": "Herhalingen Buren",
|
"neighbors_repeatersNeighbors": "Repeatbburen",
|
||||||
"neighbors_noData": "Geen gegevens van buren beschikbaar.",
|
"neighbors_noData": "Geen gegevens van buren beschikbaar.",
|
||||||
"channels_createPrivateChannelDesc": "Beveiligd met een geheime sleutel.",
|
"channels_createPrivateChannelDesc": "Beveiligd met een geheime sleutel.",
|
||||||
"channels_createPrivateChannel": "Maak een Privé Kanaal",
|
"channels_createPrivateChannel": "PrivéKanaal Aanmaken",
|
||||||
"channels_joinPrivateChannel": "Sluit een Privé Kanaal aan",
|
"channels_joinPrivateChannel": "PrivéKanaal Toetreden",
|
||||||
"channels_joinPrivateChannelDesc": "Handmatig een geheime sleutel invoeren.",
|
"channels_joinPrivateChannelDesc": "Voer handmatig een geheime sleutel in.",
|
||||||
"channels_joinPublicChannel": "Sluit het Open Kanaal",
|
"channels_joinPublicChannel": "Publiek Kanaal Toetreden",
|
||||||
"channels_joinPublicChannelDesc": "Iedereen kan dit kanaal aanmelden.",
|
"channels_joinPublicChannelDesc": "Iedereen kan toetreden tot dit kanaal.",
|
||||||
"channels_joinHashtagChannel": "Sluit een Hashtag Kanaal",
|
"channels_joinHashtagChannel": "Hashtag-kanaal Aanmaken",
|
||||||
"channels_joinHashtagChannelDesc": "Iedereen kan lid worden van hashtag-kanalen.",
|
"channels_joinHashtagChannelDesc": "Iedereen kan toetreden tot hashtag-kanalen.",
|
||||||
"channels_scanQrCode": "Scan een QR-code",
|
"channels_scanQrCode": "Scan een QR-code",
|
||||||
"channels_scanQrCodeComingSoon": "Komt later",
|
"channels_scanQrCodeComingSoon": "Komt later",
|
||||||
"channels_enterHashtag": "Voer hashtag in",
|
"channels_enterHashtag": "Voer hashtag in",
|
||||||
@@ -1459,7 +1548,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"community_title": "Gemeenschap",
|
"community_title": "Gemeenschap",
|
||||||
"common_ok": "OK",
|
"common_ok": "Prima",
|
||||||
"community_createDesc": "Maak een nieuwe community en deel deze via QR-code.",
|
"community_createDesc": "Maak een nieuwe community en deel deze via QR-code.",
|
||||||
"community_create": "Maak Gemeenschap",
|
"community_create": "Maak Gemeenschap",
|
||||||
"community_join": "Sluit aan",
|
"community_join": "Sluit aan",
|
||||||
@@ -1553,11 +1642,11 @@
|
|||||||
"contacts_pathTrace": "Pad Traceren",
|
"contacts_pathTrace": "Pad Traceren",
|
||||||
"contacts_ping": "Pingen",
|
"contacts_ping": "Pingen",
|
||||||
"contacts_repeaterPathTrace": "Pad traceren naar repeater",
|
"contacts_repeaterPathTrace": "Pad traceren naar repeater",
|
||||||
"contacts_repeaterPing": "Ping repeater",
|
"contacts_repeaterPing": "Ping-repeater",
|
||||||
"contacts_roomPathTrace": "Padtrace naar room server",
|
"contacts_roomPathTrace": "Padtrace naar room server",
|
||||||
"contacts_roomPing": "Ping kamer server",
|
"contacts_roomPing": "Ping kamer server",
|
||||||
"contacts_chatTraceRoute": "Route traceren",
|
"contacts_chatTraceRoute": "Route traceren",
|
||||||
"contacts_pathTraceTo": "Trace route to {name}",
|
"contacts_pathTraceTo": "Volg de route naar {name}",
|
||||||
"appSettings_languageUk": "Oekraïens",
|
"appSettings_languageUk": "Oekraïens",
|
||||||
"contacts_invalidAdvertFormat": "Ongeldige contactgegevens",
|
"contacts_invalidAdvertFormat": "Ongeldige contactgegevens",
|
||||||
"contacts_contactImportFailed": "Contact kon niet geïmporteerd worden.",
|
"contacts_contactImportFailed": "Contact kon niet geïmporteerd worden.",
|
||||||
@@ -1573,7 +1662,7 @@
|
|||||||
"contacts_zeroHopContactAdvertSent": "Contact verzonden via advertentie",
|
"contacts_zeroHopContactAdvertSent": "Contact verzonden via advertentie",
|
||||||
"contacts_contactAdvertCopied": "Reclame gekopieerd naar Klembord.",
|
"contacts_contactAdvertCopied": "Reclame gekopieerd naar Klembord.",
|
||||||
"contacts_contactAdvertCopyFailed": "Kopiëren van advertentie naar Clipboard is mislukt.",
|
"contacts_contactAdvertCopyFailed": "Kopiëren van advertentie naar Clipboard is mislukt.",
|
||||||
"contacts_ShareContact": "Kontakt naar Klembord kopiëren",
|
"contacts_ShareContact": "Contact naar Klembord kopiëren",
|
||||||
"contacts_ShareContactZeroHop": "Contact delen via advertentie",
|
"contacts_ShareContactZeroHop": "Contact delen via advertentie",
|
||||||
"contacts_zeroHopContactAdvertFailed": "Mislukt om contact te verzenden",
|
"contacts_zeroHopContactAdvertFailed": "Mislukt om contact te verzenden",
|
||||||
"notification_activityTitle": "MeshCore Activiteit",
|
"notification_activityTitle": "MeshCore Activiteit",
|
||||||
@@ -1611,8 +1700,8 @@
|
|||||||
"snrIndicator_lastSeen": "Laatst gezien",
|
"snrIndicator_lastSeen": "Laatst gezien",
|
||||||
"snrIndicator_nearByRepeaters": "Nabije herhalingseenheden",
|
"snrIndicator_nearByRepeaters": "Nabije herhalingseenheden",
|
||||||
"chat_ShowAllPaths": "Toon alle paden",
|
"chat_ShowAllPaths": "Toon alle paden",
|
||||||
"settings_clientRepeat": "Herhalen: Afgekoppeld",
|
"settings_clientRepeat": "Off-Grid Herhalen",
|
||||||
"settings_clientRepeatSubtitle": "Laat dit apparaat de mesh-pakketten opnieuw verzenden voor andere apparaten.",
|
"settings_clientRepeatSubtitle": "Laat dit apparaat de berichten van andere apparaten doorsturen.",
|
||||||
"settings_clientRepeatFreqWarning": "Om een signaal buiten het netwerk te versturen, zijn frequenties van 433, 869 of 918 MHz vereist.",
|
"settings_clientRepeatFreqWarning": "Om een signaal buiten het netwerk te versturen, zijn frequenties van 433, 869 of 918 MHz vereist.",
|
||||||
"settings_aboutOpenMeteoAttribution": "LOS-hoogtegegevens: Open-Meteo (CC BY 4.0)",
|
"settings_aboutOpenMeteoAttribution": "LOS-hoogtegegevens: Open-Meteo (CC BY 4.0)",
|
||||||
"appSettings_unitsTitle": "Eenheden",
|
"appSettings_unitsTitle": "Eenheden",
|
||||||
@@ -1859,5 +1948,372 @@
|
|||||||
"usbStatus_notConnected": "Selecteer een USB-apparaat",
|
"usbStatus_notConnected": "Selecteer een USB-apparaat",
|
||||||
"usbStatus_connecting": "Verbinding maken met USB-apparaat...",
|
"usbStatus_connecting": "Verbinding maken met USB-apparaat...",
|
||||||
"usbStatus_searching": "Zoeken naar USB-apparaten...",
|
"usbStatus_searching": "Zoeken naar USB-apparaten...",
|
||||||
"usbErrorConnectTimedOut": "Verbinding is verbroken. Zorg ervoor dat het apparaat de juiste USB-firmware heeft."
|
"usbErrorConnectTimedOut": "Verbinding is verbroken. Zorg ervoor dat het apparaat de juiste USB-firmware heeft.",
|
||||||
|
"@tcpStatus_connectingTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"endpoint": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tcpConnectionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tcpScreenTitle": "Verbind via TCP",
|
||||||
|
"tcpHostLabel": "IP-adres",
|
||||||
|
"tcpHostHint": "192.168.40.10",
|
||||||
|
"connectionChoiceTcpLabel": "TCP",
|
||||||
|
"tcpPortLabel": "Poort",
|
||||||
|
"tcpPortHint": "5000",
|
||||||
|
"tcpStatus_notConnected": "Voer het eindpunt in en verbind",
|
||||||
|
"tcpStatus_connectingTo": "Verbinding maken met {endpoint}...",
|
||||||
|
"tcpErrorHostRequired": "Een IP-adres is vereist.",
|
||||||
|
"tcpErrorPortInvalid": "De poortwaarde moet tussen 1 en 65535 liggen.",
|
||||||
|
"tcpErrorUnsupported": "TCP-transport wordt niet ondersteund op deze platform.",
|
||||||
|
"tcpErrorTimedOut": "De TCP-verbinding is verlopen.",
|
||||||
|
"tcpConnectionFailed": "Verbinding met TCP mislukt: {error}",
|
||||||
|
"map_showDiscoveryContacts": "Ontdek contacten weergeven",
|
||||||
|
"map_setAsMyLocation": "Stel dit in als mijn locatie",
|
||||||
|
"@path_routeWeight": {
|
||||||
|
"placeholders": {
|
||||||
|
"weight": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings_privacy": "Privacyinstellingen",
|
||||||
|
"settings_privacySubtitle": "Beheer welke informatie wordt gedeeld",
|
||||||
|
"settings_telemetryLocationMode": "Telemetrie-locatiemodus",
|
||||||
|
"settings_telemetryEnvironmentMode": "Telemetrie-omgevingsmodus",
|
||||||
|
"settings_advertLocation": "Advertentielocatie",
|
||||||
|
"settings_advertLocationSubtitle": "Locatie opnemen in advertentie",
|
||||||
|
"settings_privacySettingsDescription": "Kies welke informatie uw apparaat deelt met anderen",
|
||||||
|
"settings_allowByContact": "Toestaan op basis van contactvlaggen",
|
||||||
|
"settings_allowAll": "Alles toestaan",
|
||||||
|
"settings_denyAll": "Weiger alles",
|
||||||
|
"contact_info": "Contactinformatie",
|
||||||
|
"settings_telemetryBaseMode": "Telemetrie-basismodus",
|
||||||
|
"contact_teleBase": "Telemetrie_basis",
|
||||||
|
"contact_teleLoc": "Telemetrielocatie",
|
||||||
|
"contact_teleLocSubtitle": "Locatiegegevens delen toestaan",
|
||||||
|
"contact_teleEnv": "Telemetrieomgeving",
|
||||||
|
"contact_teleEnvSubtitle": "Delen van omgevingsensordata toestaan",
|
||||||
|
"contact_settings": "Contactinstellingen",
|
||||||
|
"contact_telemetry": "Telemetrie",
|
||||||
|
"contact_lastSeen": "Laatst gezien",
|
||||||
|
"contact_clearChat": "Chat leegmaken",
|
||||||
|
"contact_teleBaseSubtitle": "Sta delen van batterij niveau en basis telemetrie toe",
|
||||||
|
"appSettings_maxRouteWeightSubtitle": "Het maximale gewicht dat een route kan bereiken door succesvolle leveringen.",
|
||||||
|
"appSettings_initialRouteWeight": "เริ่มต้น gewicht van de route",
|
||||||
|
"appSettings_maxRouteWeight": "Maximale gewicht voor de route",
|
||||||
|
"appSettings_initialRouteWeightSubtitle": "Startgewicht voor nieuwe, ontdekte routes",
|
||||||
|
"appSettings_routeWeightSuccessIncrement": "Toename in het gewicht van het succes",
|
||||||
|
"appSettings_routeWeightSuccessIncrementSubtitle": "Gewicht wordt toegevoegd aan een route na een succesvolle levering.",
|
||||||
|
"appSettings_routeWeightFailureDecrement": "Vermindering van het gewicht van fouten",
|
||||||
|
"appSettings_routeWeightFailureDecrementSubtitle": "Gewicht verwijderd van een pad na een mislukte levering",
|
||||||
|
"appSettings_maxMessageRetries": "Aantal pogingen om berichten te versturen",
|
||||||
|
"appSettings_maxMessageRetriesSubtitle": "Aantal pogingen om een bericht opnieuw te versturen voordat het als mislukt wordt gemarkeerd",
|
||||||
|
"path_routeWeight": "{weight}/{max}",
|
||||||
|
"settings_telemetryModeUpdated": "Telemetrie-modus bijgewerkt",
|
||||||
|
"settings_multiAck": "Meerdere bevestigingen",
|
||||||
|
"map_showOverlaps": "Herhalingssleutel overlapt",
|
||||||
|
"map_runTraceWithReturnPath": "Terugkeren op hetzelfde pad.",
|
||||||
|
"@radioStats_noiseFloor": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastRssi": {
|
||||||
|
"placeholders": {
|
||||||
|
"rssiDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastSnr": {
|
||||||
|
"placeholders": {
|
||||||
|
"snr": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_txAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_rxAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_stripNoise": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_jumpToOldestUnread": "Ga naar het oudste ongelezen bericht",
|
||||||
|
"appSettings_jumpToOldestUnreadSubtitle": "Bij het openen van een chat met ongelezen berichten, scroll dan naar het eerste ongelezen bericht, in plaats van naar het meest recente.",
|
||||||
|
"chat_sendCooldown": "Gelieve even te wachten voordat u opnieuw verzendt.",
|
||||||
|
"appSettings_languageHu": "Hongaars",
|
||||||
|
"appSettings_languageJa": "Japanisch",
|
||||||
|
"appSettings_languageKo": "Koreaans",
|
||||||
|
"radioStats_tooltip": "Statistieken voor radio en mesh-netwerken",
|
||||||
|
"radioStats_screenTitle": "Statistieken over radio",
|
||||||
|
"radioStats_notConnected": "Verbind met een apparaat om radio-statistieken te bekijken.",
|
||||||
|
"radioStats_firmwareTooOld": "Om de statistieken via radio te kunnen gebruiken, is firmware versie 8 of een nieuwere vereist.",
|
||||||
|
"radioStats_waiting": "Wacht op gegevens…",
|
||||||
|
"radioStats_noiseFloor": "Ruisfrequentie: {noiseDbm} dBm",
|
||||||
|
"radioStats_lastRssi": "Laatste RSSI-waarde: {rssiDbm} dBm",
|
||||||
|
"radioStats_lastSnr": "Laatste SNR: {snr} dB",
|
||||||
|
"radioStats_txAir": "TX-tijd (totaal): {seconds} s",
|
||||||
|
"radioStats_rxAir": "Tijd besteed met RX (totaal): {seconds} s",
|
||||||
|
"radioStats_chartCaption": "Ruisfrequentie (dBm) over recente metingen.",
|
||||||
|
"radioStats_stripNoise": "Ruisfrequentie: {noiseDbm} dBm",
|
||||||
|
"radioStats_stripWaiting": "Radio-statistieken ophalen…",
|
||||||
|
"radioStats_settingsTile": "Statistieken over radio",
|
||||||
|
"radioStats_settingsSubtitle": "Ruimtelijke ruis, RSSI, SNR en beschikbare tijd",
|
||||||
|
"@translation_downloadFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_enableSubtitle": "Vertaal inkomende berichten en maak het mogelijk om berichten vooraf te vertalen.",
|
||||||
|
"translation_enableTitle": "Activeer vertaling",
|
||||||
|
"translation_title": "Vertaling",
|
||||||
|
"translation_composerTitle": "Vertaal voor verzending",
|
||||||
|
"translation_composerSubtitle": "Stelt de standaardstatus van het pictogram voor de vertaling van de componist in.",
|
||||||
|
"translation_useAppLanguage": "Gebruik de taal van de app",
|
||||||
|
"translation_targetLanguage": "Doeltaal",
|
||||||
|
"translation_downloadedModelLabel": "Gedownloade model",
|
||||||
|
"translation_presetModelLabel": "Voorgeprogrammeerd Hugging Face-model",
|
||||||
|
"translation_manualUrlLabel": "URL van de handleiding",
|
||||||
|
"translation_downloadModel": "Download het model",
|
||||||
|
"translation_downloading": "Downloaden...",
|
||||||
|
"translation_working": "Werken...",
|
||||||
|
"translation_mergingChunks": "Het samenvoegen van de gedownloade stukken tot één eindbestand...",
|
||||||
|
"translation_stop": "Stoppen",
|
||||||
|
"translation_downloadedModels": "Gedownloade modellen",
|
||||||
|
"translation_deleteModel": "Model verwijderen",
|
||||||
|
"translation_modelDownloaded": "Vertalingmodel gedownload.",
|
||||||
|
"translation_downloadStopped": "Download is afgebroken.",
|
||||||
|
"translation_downloadFailed": "Download mislukt: {error}",
|
||||||
|
"translation_enterUrlFirst": "Voer eerst een URL van een model in.",
|
||||||
|
"@scanner_linuxPairingPinPrompt": {
|
||||||
|
"placeholders": {
|
||||||
|
"deviceName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@translation_translateTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"language": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_composerDisabledHint": "Stuur berichten in de oorspronkelijke, getypte taal.",
|
||||||
|
"translation_translateBeforeSending": "Vertaal voor verzending",
|
||||||
|
"translation_composerEnabledHint": "De berichten worden vertaald voordat ze verzonden worden.",
|
||||||
|
"translation_messageTranslation": "Berichtvertaling",
|
||||||
|
"translation_translationOptions": "Opties voor vertaling",
|
||||||
|
"translation_systemLanguage": "Taal van het systeem",
|
||||||
|
"translation_translateTo": "Vertalen naar {language}",
|
||||||
|
"scanner_linuxPairingShowPin": "Toon PIN",
|
||||||
|
"scanner_linuxPairingHidePin": "PIN verbergen",
|
||||||
|
"scanner_linuxPairingPinPrompt": "Voer PIN in voor {deviceName} (laat leeg als er geen is).",
|
||||||
|
"scanner_linuxPairingPinTitle": "Bluetooth‑koppelings‑PIN",
|
||||||
|
"repeater_cliQuickDiscovery": "Ontdek Buren",
|
||||||
|
"repeater_cliQuickClockSync": "Kloksynchronisatie",
|
||||||
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Automatisch een \"klok synchroniseren\" bericht versturen na een succesvolle inlog.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Na het inloggen, klok synchroniseren",
|
||||||
|
"repeater_guestTools": "Gastenfuncties",
|
||||||
|
"room_guest": "Informatie over de server",
|
||||||
|
"chat_sendMessage": "Verzend bericht",
|
||||||
|
"repeater_guest": "Informatie over herhalingsapparatuur",
|
||||||
|
"repeater_getCategory": "Waarden verkrijgen",
|
||||||
|
"repeater_powerMgmt": "Energiebeheer",
|
||||||
|
"repeater_sensors": "Sensoren",
|
||||||
|
"repeater_cliHelpPowerOff": "Zorgt ervoor dat het apparaat wordt uitgeschakeld. (geen reactie verwacht)",
|
||||||
|
"repeater_cliHelpClkReboot": "Stelt de klok terug naar een bekende tijd en start het apparaat opnieuw op.",
|
||||||
|
"repeater_cliHelpAdvertZeroHop": "Verstuurt een advertentie die alleen naar directe buren wordt gericht (geen tussenliggende stops).",
|
||||||
|
"repeater_cliHelpStartOta": "Start een firmware-update via de lucht op ondersteunde boards.",
|
||||||
|
"repeater_cliHelpTime": "Stelt de klok van het apparaat in op de gegeven Unix-tijd (aantal seconden vanaf de Unix-epoch). De klok kan niet teruggedraaid worden.",
|
||||||
|
"repeater_cliHelpBoard": "Geeft de fabrikant van het bord en/of de hardware-identificatie weer.",
|
||||||
|
"repeater_cliHelpDiscoverNeighbors": "Stuurt een verzoek om buren in de buurt te ontdekken. (Alleen van toepassing op een repeater)",
|
||||||
|
"repeater_cliHelpPowersaving": "Geeft aan of de energiebesparingsmodus is ingeschakeld of uitgeschakeld.",
|
||||||
|
"repeater_cliHelpPowersavingOnOff": "Activeert of deactiveert de energiebesparingsmodus (indien ondersteund).",
|
||||||
|
"repeater_cliHelpErase": "(Alleen voor seriële verbindingen) Formateert het bestandssysteem van het apparaat. Verwijdert alle instellingen en contacten.",
|
||||||
|
"repeater_cliHelpSetDutyCycle": "Stelt het maximale toegestane transmissiepercentage in (in procenten, 1-100). Past intern de tijdsschaal aan.",
|
||||||
|
"repeater_cliHelpSetPrvKey": "(Alleen voor seriële toepassingen) Vervangt de private sleutel van het apparaat. Een herstart is vereist om deze wijziging toe te passen. Genereert een nieuwe publieke sleutel.",
|
||||||
|
"repeater_cliHelpSetRadioRxGain": "(Alleen voor SX126x-chips) Schakelt de versterkte RX-gain in om de gevoeligheid te verbeteren bij een hoger stroomverbruik.",
|
||||||
|
"repeater_cliHelpSetOwnerInfo": "Definieert de string met contactgegevens van de eigenaar, die in de advertenties wordt opgenomen. Gebruik '|' voor nieuwe regels.",
|
||||||
|
"repeater_cliHelpSetPathHashMode": "Stelt de modus voor het berekenen van de hash van de route in. 0 = voorheen, 1 = standaard, 2 = strikt. Beïnvloedt hoe de routes worden gematched.",
|
||||||
|
"repeater_cliHelpSetLoopDetect": "Stelt de gevoeligheid voor het detecteren van een lus in de routing in: uit, minimaal, matig of strikt.",
|
||||||
|
"repeater_cliHelpSetFreq": "(Alleen voor seriële communicatie) Stelt snel alleen de frequentie in. Herstart is vereist. Het is aan te raden om \"radio instellingen\" te gebruiken voor alle radioparameters.",
|
||||||
|
"repeater_cliHelpSetBridgeChannel": "(Alleen voor ESPNow-brug) Stelt het WiFi-kanaal (1-14) in dat door de brug wordt gebruikt.",
|
||||||
|
"repeater_cliHelpGetName": "Toont de naam van de geconfigureerde knoop.",
|
||||||
|
"repeater_cliHelpGetRole": "Geeft de rol van de firmware aan (herhaald, server voor een kamer, enz.).",
|
||||||
|
"repeater_cliHelpGetPublicKey": "Toont het openbare sleutel van het apparaat.",
|
||||||
|
"repeater_cliHelpGetPrvKey": "(Alleen voor seriële communicatie) Toont de private sleutel van het apparaat. Behandel dit als een geheim.",
|
||||||
|
"repeater_cliHelpGetRepeat": "Geeft aan of het doorsturen van pakketten (als repeater) is ingeschakeld of uitgeschakeld.",
|
||||||
|
"repeater_cliHelpGetTx": "Toont de huidige zendvermogen in dBm.",
|
||||||
|
"repeater_cliHelpGetFreq": "Toont de geconfigureerde frequentie in MHz.",
|
||||||
|
"repeater_cliHelpGetRadio": "Geeft alle radioparameters weer: frequentie, bandbreedte, spreidfactor, codegraad.",
|
||||||
|
"repeater_cliHelpGetRadioRxGain": "(Alleen voor SX126x-chips) Toont de status van de versterking van de RX-ontvangst.",
|
||||||
|
"repeater_cliHelpGetAf": "Geeft de huidige tijdsfactor weer.",
|
||||||
|
"repeater_cliHelpGetDutyCycle": "Toont de huidige toegestane duty cycle als een percentage.",
|
||||||
|
"repeater_cliHelpGetIntThresh": "Toont het drempelwaarde voor signaalinterferentie in dB.",
|
||||||
|
"repeater_cliHelpGetAgcResetInterval": "Geeft het interval in seconden aan voor het resetten van de AGC (Automatic Gain Control).",
|
||||||
|
"repeater_cliHelpGetMultiAcks": "Geeft aan of de modus \"dubbele bevestiging\" is ingeschakeld (1) of uitgeschakeld (0).",
|
||||||
|
"repeater_cliHelpGetAllowReadOnly": "Geeft aan of er toegang is voor gastgebruikers zonder rechten.",
|
||||||
|
"repeater_cliHelpGetAdvertInterval": "Geeft de duur van de lokale reclame in minuten aan.",
|
||||||
|
"repeater_cliHelpGetFloodAdvertInterval": "Geeft de duur van de reclame-interval in uren aan.",
|
||||||
|
"repeater_cliHelpGetGuestPassword": "Toont het ingestelde gastwachtwoord.",
|
||||||
|
"repeater_cliHelpGetLat": "Toont de ingestelde breedtegraad.",
|
||||||
|
"repeater_cliHelpGetLon": "Toont de ingestelde lengtegraad.",
|
||||||
|
"repeater_cliHelpGetRxDelay": "Toont de basiswaarde van de rx-vertraging.",
|
||||||
|
"repeater_cliHelpGetTxDelay": "Geeft de factor weer die de vertraging in de flood-modus bepaalt.",
|
||||||
|
"repeater_cliHelpGetDirectTxDelay": "Geeft de factor voor de vertraging in de directe modus weer.",
|
||||||
|
"repeater_cliHelpGetFloodMax": "Toont het maximale aantal keer dat een overstroming heeft plaatsgevonden.",
|
||||||
|
"repeater_cliHelpGetOwnerInfo": "Toont de string met contactgegevens van de eigenaar.",
|
||||||
|
"repeater_cliHelpGetPathHashMode": "Toont de modus voor het berekenen van de hash (0/1/2).",
|
||||||
|
"repeater_cliHelpGetLoopDetect": "Geeft de gevoeligheid voor het detecteren van lusvorming weer.",
|
||||||
|
"repeater_cliHelpGetAcl": "(Alleen voor seriële communicatie) Geeft de toegangscontroles weer op een repeater.",
|
||||||
|
"repeater_cliHelpGetBridgeEnabled": "Geeft aan of de brug is ingeschakeld.",
|
||||||
|
"repeater_cliHelpGetBridgeDelay": "Geeft de vertraging van de brug in milliseconden weer.",
|
||||||
|
"repeater_cliHelpGetBridgeSource": "Geeft aan of de brug RX- of TX-pakketten verwerkt.",
|
||||||
|
"repeater_cliHelpGetBridgeBaud": "(Alleen RS232-brug) Toont de baud-snelheid van de brug.",
|
||||||
|
"repeater_cliHelpGetBridgeChannel": "(Alleen voor ESPNow-brug) Toont het WiFi-kanaal van de brug.",
|
||||||
|
"repeater_cliHelpGetBridgeSecret": "(Alleen voor ESPNow-brug) Toont het gedeelde geheime sleutel.",
|
||||||
|
"repeater_cliHelpGetBootloaderVer": "(Alleen voor NRF52) Toont de versie van de bootloader.",
|
||||||
|
"repeater_cliHelpGetAdcMultiplier": "Toont de ADC-vermenigvuldiging (schalen van de batterijspanning).",
|
||||||
|
"repeater_cliHelpGetPwrMgtSupport": "Geeft aan of het bestuur ondersteuning heeft voor het beheer van energieverbruik.",
|
||||||
|
"repeater_cliHelpGetPwrMgtSource": "Geeft de huidige stroombron aan: extern of batterij.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootReason": "Geeft de meest recente redenen voor het opnieuw opstarten en afsluiten weer.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootMv": "Geeft de batterijspanning in mV weer, direct na het opstarten.",
|
||||||
|
"repeater_cliHelpSensorGet": "Leest een aangepaste sensorgegevens op basis van een sleutel.",
|
||||||
|
"repeater_cliHelpSensorSet": "Maakt een aangepaste instelling voor een sensor.",
|
||||||
|
"repeater_cliHelpSensorList": "Toont alle aangepaste instellingen voor sensoren, gegroepeerd op basis van een optionele startindex.",
|
||||||
|
"repeater_cliHelpRegionDefault": "Toont het huidige standaard regio-bereik.",
|
||||||
|
"repeater_cliHelpRegionDefaultSet": "Stelt de standaard regio-omvang in. Gebruik \"<null>\" om deze te resetten.",
|
||||||
|
"repeater_cliHelpRegionListAllowed": "Lijst van gebieden waar doorstromen tijdens overstromingen is toegestaan.",
|
||||||
|
"repeater_cliHelpRegionListDenied": "Geeft een lijst van regio's die het verkeer tijdens overstromingen verbieden.",
|
||||||
|
"repeater_cliHelpStatsPackets": "(Alleen voor seriële verbindingen) Toont statistieken op pakketniveau.",
|
||||||
|
"repeater_cliHelpStatsRadio": "(Alleen voor serienummers) Toont radio-statistieken.",
|
||||||
|
"repeater_cliHelpStatsCore": "(Alleen voor seriële communicatie) Toont de belangrijkste firmware-statistieken.",
|
||||||
|
"common_done": "Done",
|
||||||
|
"background_serviceTitle": "MeshCore running",
|
||||||
|
"background_serviceText": "Keeping BLE connected",
|
||||||
|
"appSettings_translationModelDeleted": "Deleted {name}",
|
||||||
|
"@appSettings_translationModelDeleted": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
|
||||||
|
"@appSettings_translationModelDeleteFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channels_channelUpdateFailed": "Failed to update channel: {error}",
|
||||||
|
"@channels_channelUpdateFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map_type": "Type",
|
||||||
|
"map_path": "Path",
|
||||||
|
"map_location": "Location",
|
||||||
|
"map_estLocation": "Est. Location",
|
||||||
|
"map_publicKey": "Public Key",
|
||||||
|
"map_publicKeyPrefixHint": "e.g. ab12",
|
||||||
|
"contact_typeChat": "Chat",
|
||||||
|
"contact_typeRepeater": "Repeater",
|
||||||
|
"contact_typeRoom": "Room",
|
||||||
|
"contact_typeSensor": "Sensor",
|
||||||
|
"contact_typeUnknown": "Unknown",
|
||||||
|
"channels_via": "via {path}",
|
||||||
|
"chat_score": "Score",
|
||||||
|
"map_sharedAt": "Gedeeld",
|
||||||
|
"@losBlockedSpotChip": {
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@losSelectedObstructionDetails": {
|
||||||
|
"placeholders": {
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromA": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromB": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"losSelectedObstructionTitle": "Geselecteerde obstakel",
|
||||||
|
"losBlockedSpotsHint": "Tik op een geblokkeerd gebied om het op de kaart te markeren.",
|
||||||
|
"losBlockedSpotsTitle": "Geplande plaatsen",
|
||||||
|
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
|
||||||
|
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).",
|
||||||
|
"settings_companionDebugLog": "Debuglog voor aanvullende informatie",
|
||||||
|
"chat_newMessages": "Nieuwe berichten",
|
||||||
|
"chat_markAsUnread": "Markeer als ongelezen",
|
||||||
|
"settings_companionDebugLogSubtitle": "BLE/TCP/USB commando's, antwoorden en ruwe data",
|
||||||
|
"repeater_chanUtil": "Gebruik van het kanaal",
|
||||||
|
"dialog_connectCompanion": "Maak verbinding met een companion om repeater- en kamerserverfuncties te gebruiken.",
|
||||||
|
"dialog_disconnectedTitle": "Verbroken",
|
||||||
|
"dialog_disconnectedMessage": "Je bent losgekoppeld van je companion.",
|
||||||
|
"contact_connectCompanion": "Maak verbinding met een companion om toegang te krijgen tot repeater- en roomserverfuncties."
|
||||||
}
|
}
|
||||||
|
|||||||
+749
-255
File diff suppressed because it is too large
Load Diff
+486
-30
@@ -52,7 +52,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scanner_title": "MeshCore Open",
|
"scanner_title": "MeshCore: Versão aberta",
|
||||||
"scanner_scanning": "Procurando por dispositivos...",
|
"scanner_scanning": "Procurando por dispositivos...",
|
||||||
"scanner_connecting": "Conectando...",
|
"scanner_connecting": "Conectando...",
|
||||||
"scanner_disconnecting": "Desconectando...",
|
"scanner_disconnecting": "Desconectando...",
|
||||||
@@ -104,6 +104,8 @@
|
|||||||
"settings_privacyModeEnabled": "Modo de privacidade ativado",
|
"settings_privacyModeEnabled": "Modo de privacidade ativado",
|
||||||
"settings_privacyModeDisabled": "Modo de privacidade desativado",
|
"settings_privacyModeDisabled": "Modo de privacidade desativado",
|
||||||
"settings_actions": "Ações",
|
"settings_actions": "Ações",
|
||||||
|
"settings_deleteAllPaths": "Delete All Paths",
|
||||||
|
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
|
||||||
"settings_sendAdvertisement": "Enviar Publicidade",
|
"settings_sendAdvertisement": "Enviar Publicidade",
|
||||||
"settings_sendAdvertisementSubtitle": "Presença de transmissão agora",
|
"settings_sendAdvertisementSubtitle": "Presença de transmissão agora",
|
||||||
"settings_advertisementSent": "Anúncio enviado",
|
"settings_advertisementSent": "Anúncio enviado",
|
||||||
@@ -133,12 +135,12 @@
|
|||||||
"settings_aboutDescription": "Um cliente Flutter de código aberto para dispositivos de rede mesh LoRa Core da MeshCore.",
|
"settings_aboutDescription": "Um cliente Flutter de código aberto para dispositivos de rede mesh LoRa Core da MeshCore.",
|
||||||
"settings_infoName": "Nome",
|
"settings_infoName": "Nome",
|
||||||
"settings_infoId": "ID",
|
"settings_infoId": "ID",
|
||||||
"settings_infoStatus": "Status",
|
"settings_infoStatus": "Estado",
|
||||||
"settings_infoBattery": "Bateria",
|
"settings_infoBattery": "Bateria",
|
||||||
"settings_infoPublicKey": "Chave Pública",
|
"settings_infoPublicKey": "Chave Pública",
|
||||||
"settings_infoContactsCount": "Número de Contatos",
|
"settings_infoContactsCount": "Número de Contatos",
|
||||||
"settings_infoChannelCount": "Número do Canal",
|
"settings_infoChannelCount": "Número do Canal",
|
||||||
"settings_presets": "Presets",
|
"settings_presets": "Configurações pré-definidas",
|
||||||
"settings_frequency": "Frequência (MHz)",
|
"settings_frequency": "Frequência (MHz)",
|
||||||
"settings_frequencyHelper": "300,0 - 2500,0",
|
"settings_frequencyHelper": "300,0 - 2500,0",
|
||||||
"settings_frequencyInvalid": "Frequência inválida (300-2500 MHz)",
|
"settings_frequencyInvalid": "Frequência inválida (300-2500 MHz)",
|
||||||
@@ -164,19 +166,19 @@
|
|||||||
"appSettings_themeDark": "Escuro",
|
"appSettings_themeDark": "Escuro",
|
||||||
"appSettings_language": "Idioma",
|
"appSettings_language": "Idioma",
|
||||||
"appSettings_languageSystem": "Padrão do sistema",
|
"appSettings_languageSystem": "Padrão do sistema",
|
||||||
"appSettings_languageEn": "English",
|
"appSettings_languageEn": "Inglês",
|
||||||
"appSettings_languageFr": "Français",
|
"appSettings_languageFr": "Francês",
|
||||||
"appSettings_languageEs": "Español",
|
"appSettings_languageEs": "Espanhol",
|
||||||
"appSettings_languageDe": "Deutsch",
|
"appSettings_languageDe": "Alemão",
|
||||||
"appSettings_languagePl": "Polski",
|
"appSettings_languagePl": "Polonês",
|
||||||
"appSettings_languageSl": "Slovenščina",
|
"appSettings_languageSl": "Esloveno",
|
||||||
"appSettings_languagePt": "Português",
|
"appSettings_languagePt": "Português",
|
||||||
"appSettings_languageIt": "Italiano",
|
"appSettings_languageIt": "Italiano",
|
||||||
"appSettings_languageZh": "中文",
|
"appSettings_languageZh": "Chinês",
|
||||||
"appSettings_languageSv": "Svenska",
|
"appSettings_languageSv": "Sueco",
|
||||||
"appSettings_languageNl": "Nederlands",
|
"appSettings_languageNl": "Holandês",
|
||||||
"appSettings_languageSk": "Slovenčina",
|
"appSettings_languageSk": "Esloveno",
|
||||||
"appSettings_languageBg": "Български",
|
"appSettings_languageBg": "Búlgaro",
|
||||||
"appSettings_notifications": "Notificações",
|
"appSettings_notifications": "Notificações",
|
||||||
"appSettings_enableNotifications": "Ativar Notificações",
|
"appSettings_enableNotifications": "Ativar Notificações",
|
||||||
"appSettings_enableNotificationsSubtitle": "Receber notificações para mensagens e anúncios",
|
"appSettings_enableNotificationsSubtitle": "Receber notificações para mensagens e anúncios",
|
||||||
@@ -285,6 +287,7 @@
|
|||||||
"contacts_newGroup": "Novo Grupo",
|
"contacts_newGroup": "Novo Grupo",
|
||||||
"contacts_groupName": "Nome do grupo",
|
"contacts_groupName": "Nome do grupo",
|
||||||
"contacts_groupNameRequired": "O nome do grupo é obrigatório.",
|
"contacts_groupNameRequired": "O nome do grupo é obrigatório.",
|
||||||
|
"contacts_groupNameReserved": "Este nome de grupo está reservado",
|
||||||
"contacts_groupAlreadyExists": "O grupo \"{name}\" já existe",
|
"contacts_groupAlreadyExists": "O grupo \"{name}\" já existe",
|
||||||
"@contacts_groupAlreadyExists": {
|
"@contacts_groupAlreadyExists": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -336,11 +339,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_hashtagChannel": "Canal com hashtag",
|
|
||||||
"channels_public": "Público",
|
"channels_public": "Público",
|
||||||
"channels_private": "Privado",
|
"channels_private": "Privado",
|
||||||
"channels_publicChannel": "Canal público",
|
|
||||||
"channels_privateChannel": "Canal privado",
|
|
||||||
"channels_editChannel": "Editar canal",
|
"channels_editChannel": "Editar canal",
|
||||||
"channels_muteChannel": "Silenciar canal",
|
"channels_muteChannel": "Silenciar canal",
|
||||||
"channels_unmuteChannel": "Ativar canal",
|
"channels_unmuteChannel": "Ativar canal",
|
||||||
@@ -387,6 +387,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_smazCompression": "Compressão SMAZ",
|
"channels_smazCompression": "Compressão SMAZ",
|
||||||
|
"channels_cyr2latCompression": "Compressão Cyr2Lat",
|
||||||
|
"channels_cyr2latCompressionDscr": "Substitui alguns caracteres cirílicos por caracteres latinos ao enviar.",
|
||||||
|
"channels_cyr2latSettingsHeading": "Configuração do Cyr2Lat",
|
||||||
|
"channels_cyr2latSettingsSubheading": "Lista de substituições",
|
||||||
|
"channels_cyr2latSettingsDscr": "Editar a configuração JSON de substituição de caracteres",
|
||||||
|
"channels_cyr2latSettingsDialogHint": "Mapa de substituições JSON",
|
||||||
|
"channels_cyr2latSettingsDialogWrongJSON": "JSON incorreto: {error}",
|
||||||
|
"settings_cyr2latProfileAdd": "Adicionar perfil Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileName": "Nome do perfil",
|
||||||
|
"settings_cyr2latProfileNameEmpty": "O nome do perfil não pode estar vazio",
|
||||||
|
"settings_cyr2latProfileAdded": "Perfil adicionado com sucesso",
|
||||||
|
"settings_cyr2latProfileUpdated": "Perfil atualizado com sucesso",
|
||||||
|
"settings_cyr2latProfileEdit": "Editar perfil Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileDelete": "Eliminar perfil Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileDeleted": "Perfil eliminado com sucesso",
|
||||||
|
"settings_cyr2latProfileDeleteDscr": "Tem a certeza de que deseja eliminar o perfil \"{name}\"?",
|
||||||
"channels_channelUpdated": "Canal \"{name}\" atualizado",
|
"channels_channelUpdated": "Canal \"{name}\" atualizado",
|
||||||
"@channels_channelUpdated": {
|
"@channels_channelUpdated": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -398,7 +414,7 @@
|
|||||||
"channels_publicChannelAdded": "Canal público adicionado",
|
"channels_publicChannelAdded": "Canal público adicionado",
|
||||||
"channels_sortBy": "Ordenar por",
|
"channels_sortBy": "Ordenar por",
|
||||||
"channels_sortManual": "Manual",
|
"channels_sortManual": "Manual",
|
||||||
"channels_sortAZ": "A-Z",
|
"channels_sortAZ": "De A a Z",
|
||||||
"channels_sortLatestMessages": "Últimas mensagens",
|
"channels_sortLatestMessages": "Últimas mensagens",
|
||||||
"channels_sortUnread": "Não lido",
|
"channels_sortUnread": "Não lido",
|
||||||
"chat_noMessages": "Ainda não existem mensagens.",
|
"chat_noMessages": "Ainda não existem mensagens.",
|
||||||
@@ -503,7 +519,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debugFrame_timestamp": "- Timestamp: {timestamp}",
|
"debugFrame_timestamp": "- Carimbo: {timestamp}",
|
||||||
"@debugFrame_timestamp": {
|
"@debugFrame_timestamp": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"timestamp": {
|
"timestamp": {
|
||||||
@@ -530,7 +546,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debugFrame_textTypeCli": "CLI",
|
"debugFrame_textTypeCli": "Interface de Linha de Comando",
|
||||||
"debugFrame_textTypePlain": "Simples",
|
"debugFrame_textTypePlain": "Simples",
|
||||||
"debugFrame_text": "- Texto: \"{text}\"",
|
"debugFrame_text": "- Texto: \"{text}\"",
|
||||||
"@debugFrame_text": {
|
"@debugFrame_text": {
|
||||||
@@ -549,7 +565,7 @@
|
|||||||
"chat_pathHistoryFull": "O histórico está cheio. Remova entradas para adicionar novas.",
|
"chat_pathHistoryFull": "O histórico está cheio. Remova entradas para adicionar novas.",
|
||||||
"chat_hopSingular": "pule",
|
"chat_hopSingular": "pule",
|
||||||
"chat_hopPlural": "salta",
|
"chat_hopPlural": "salta",
|
||||||
"chat_hopsCount": "{count} {count, plural, =1{hop} other{hops}}",
|
"chat_hopsCount": "{count} {count, plural, =1{salto} other{saltos}}",
|
||||||
"@chat_hopsCount": {
|
"@chat_hopsCount": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {
|
"count": {
|
||||||
@@ -818,7 +834,7 @@
|
|||||||
"login_autoUseSavedPath": "Auto (usar caminho salvo)",
|
"login_autoUseSavedPath": "Auto (usar caminho salvo)",
|
||||||
"login_forceFloodMode": "Modo de Inundação Forçado",
|
"login_forceFloodMode": "Modo de Inundação Forçado",
|
||||||
"login_managePaths": "Gerenciar Caminhos",
|
"login_managePaths": "Gerenciar Caminhos",
|
||||||
"login_login": "Login",
|
"login_login": "Entrar",
|
||||||
"login_attempt": "Tentar {current}/{max}",
|
"login_attempt": "Tentar {current}/{max}",
|
||||||
"@login_attempt": {
|
"@login_attempt": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -878,11 +894,11 @@
|
|||||||
"path_setPath": "Definir Caminho",
|
"path_setPath": "Definir Caminho",
|
||||||
"repeater_management": "Gerenciamento de Repetidor",
|
"repeater_management": "Gerenciamento de Repetidor",
|
||||||
"repeater_managementTools": "Ferramentas de Gerenciamento",
|
"repeater_managementTools": "Ferramentas de Gerenciamento",
|
||||||
"repeater_status": "Status",
|
"repeater_status": "Estado",
|
||||||
"repeater_statusSubtitle": "Visualizar status do repetidor, estatísticas e vizinhos.",
|
"repeater_statusSubtitle": "Visualizar status do repetidor, estatísticas e vizinhos.",
|
||||||
"repeater_telemetry": "Telemetria",
|
"repeater_telemetry": "Telemetria",
|
||||||
"repeater_telemetrySubtitle": "Visualizar telemetria de sensores e estatísticas do sistema",
|
"repeater_telemetrySubtitle": "Visualizar telemetria de sensores e estatísticas do sistema",
|
||||||
"repeater_cli": "CLI",
|
"repeater_cli": "Interface de Linha de Comando",
|
||||||
"repeater_cliSubtitle": "Enviar comandos ao repetidor",
|
"repeater_cliSubtitle": "Enviar comandos ao repetidor",
|
||||||
"repeater_settings": "Configurações",
|
"repeater_settings": "Configurações",
|
||||||
"repeater_settingsSubtitle": "Configurar parâmetros do repetidor",
|
"repeater_settingsSubtitle": "Configurar parâmetros do repetidor",
|
||||||
@@ -992,7 +1008,7 @@
|
|||||||
"repeater_radioSettings": "Configurações de Rádio",
|
"repeater_radioSettings": "Configurações de Rádio",
|
||||||
"repeater_frequencyMhz": "Frequência (MHz)",
|
"repeater_frequencyMhz": "Frequência (MHz)",
|
||||||
"repeater_frequencyHelper": "300-2500 MHz",
|
"repeater_frequencyHelper": "300-2500 MHz",
|
||||||
"repeater_txPower": "TX Power",
|
"repeater_txPower": "Energia da TX",
|
||||||
"repeater_txPowerHelper": "1-30 dBm",
|
"repeater_txPowerHelper": "1-30 dBm",
|
||||||
"repeater_bandwidth": "Largura de banda",
|
"repeater_bandwidth": "Largura de banda",
|
||||||
"repeater_spreadingFactor": "Fator de Dispersão",
|
"repeater_spreadingFactor": "Fator de Dispersão",
|
||||||
@@ -1058,6 +1074,81 @@
|
|||||||
},
|
},
|
||||||
"repeater_confirm": "Confirmar",
|
"repeater_confirm": "Confirmar",
|
||||||
"repeater_settingsSaved": "Configurações salvas com sucesso",
|
"repeater_settingsSaved": "Configurações salvas com sucesso",
|
||||||
|
"repeater_rxGain": "Aumento do ganho do RX",
|
||||||
|
"repeater_rxGainHelper": "Maior sensibilidade, maior consumo de corrente (apenas para SX1262/SX1268)",
|
||||||
|
"repeater_refreshRxGain": "Reforçar o ganho do RX",
|
||||||
|
"repeater_multiAcks": "Múltiplas respostas de confirmação",
|
||||||
|
"repeater_multiAcksSubtitle": "Reconheça mensagens através de múltiplos caminhos para uma melhor entrega.",
|
||||||
|
"repeater_refreshMultiAcks": "Reiniciar múltiplas confirmações",
|
||||||
|
"repeater_networkHealth": "Saúde da rede",
|
||||||
|
"repeater_loopDetect": "Detecção de loops",
|
||||||
|
"repeater_loopDetectHelper": "Envie pacotes que pareçam ser loops de roteamento.",
|
||||||
|
"repeater_loopDetectOff": "Desligado",
|
||||||
|
"repeater_loopDetectMinimal": "Mínimo",
|
||||||
|
"repeater_loopDetectModerate": "Moderado",
|
||||||
|
"repeater_loopDetectStrict": "Rígido",
|
||||||
|
"repeater_dutyCycle": "Ciclo de operação",
|
||||||
|
"repeater_dutyCycleHelper": "Porcentagem máxima de tempo de transmissão",
|
||||||
|
"repeater_dutyCyclePercent": "{percent}%",
|
||||||
|
"@repeater_dutyCyclePercent": {
|
||||||
|
"placeholders": {
|
||||||
|
"percent": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_ownerInfo": "Informações sobre o operador",
|
||||||
|
"repeater_ownerInfoHelper": "Metadados públicos para este repetidor",
|
||||||
|
"repeater_refreshOwnerInfo": "Atualizar informações do operador",
|
||||||
|
"repeater_floodMax": "Número máximo de saltos em caso de inundação",
|
||||||
|
"repeater_floodMaxHelper": "Número máximo de saltos que um pacote de inundação pode percorrer (0-64)",
|
||||||
|
"repeater_advancedSettings": "Avançado",
|
||||||
|
"repeater_advancedSettingsSubtitle": "Controles de ajuste para operadores experientes",
|
||||||
|
"repeater_pathHashMode": "Modo de hash de caminho",
|
||||||
|
"repeater_pathHashModeHelper": "Bytes utilizados para codificar o ID deste repetidor nas tags de caminho/detecção de loop. 0=1 byte (256 IDs, até 64 saltos), 1=2 bytes (65.000 IDs, até 32 saltos), 2=3 bytes (16 milhões de IDs, até 21 saltos). As versões 1.13 e anteriores do firmware não suportam caminhos multi-byte — apenas funcionam uma vez após a ativação da rede (a partir da versão 1.14+).",
|
||||||
|
"repeater_txDelay": "Atraso na entrega em Flood, TX",
|
||||||
|
"repeater_txDelayHelper": "Ajuste de espaçamento para tráfego de inundações, como um multiplicador do tempo de transmissão (0-2, padrão 0,5). Quanto maior, menos colisões, mas uma entrega mais lenta.",
|
||||||
|
"repeater_directTxDelay": "Atraso direto no sinal TX",
|
||||||
|
"repeater_directTxDelayHelper": "Intervalo de retransmissão para tráfego direto (não em enxame), como um multiplicador do tempo de transmissão do pacote (0-2, padrão 0,3).",
|
||||||
|
"repeater_intThresh": "Limite de interferência",
|
||||||
|
"repeater_intThreshHelper": "O limite é definido para o nível de ruído do rádio, de modo que ele rejeite interferências acima desse nível. 0 desativa – aumente apenas se você observar erros de RX em uma faixa de frequência com ruído.",
|
||||||
|
"repeater_agcResetInterval": "Intervalo de reinicialização do AGC",
|
||||||
|
"repeater_agcResetIntervalHelper": "Com que frequência redefinir o controle automático de ganho do rádio para recuperar de um estado em que o ganho está travado. Segundos, reduzidos a um múltiplo de 4. 0 desativa as redefinições periódicas.",
|
||||||
|
"repeater_actionsTitle": "Ações",
|
||||||
|
"repeater_sendAdvert": "Envie anúncio sobre inundações",
|
||||||
|
"repeater_sendAdvertSubtitle": "Transmita um anúncio sobre inundações pela rede.",
|
||||||
|
"repeater_sendAdvertZeroHop": "Enviar anúncio sem intermediários",
|
||||||
|
"repeater_sendAdvertZeroHopSubtitle": "Transmita um anúncio de um único salto (sem repetição).",
|
||||||
|
"repeater_clockSync": "Sincronize o relógio agora",
|
||||||
|
"repeater_clockSyncSubtitle": "Envie a hora do seu telefone para o repetidor.",
|
||||||
|
"repeater_actionSucceeded": "{action} succeeded",
|
||||||
|
"@repeater_actionSucceeded": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_actionFailed": "{action} failed: {error}",
|
||||||
|
"@repeater_actionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_settingsSavedRebootNeeded": "Configurações salvas — reinicie o repetidor para aplicar as alterações.",
|
||||||
|
"repeater_settingsPartialFailure": "Algumas configurações falharam: {failures}",
|
||||||
|
"@repeater_settingsPartialFailure": {
|
||||||
|
"placeholders": {
|
||||||
|
"failures": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repeater_errorSavingSettings": "Erro ao salvar as configurações: {error}",
|
"repeater_errorSavingSettings": "Erro ao salvar as configurações: {error}",
|
||||||
"@repeater_errorSavingSettings": {
|
"@repeater_errorSavingSettings": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1069,11 +1160,9 @@
|
|||||||
"repeater_refreshBasicSettings": "Atualizar Configurações Básicas",
|
"repeater_refreshBasicSettings": "Atualizar Configurações Básicas",
|
||||||
"repeater_refreshRadioSettings": "Atualizar Configurações de Rádio",
|
"repeater_refreshRadioSettings": "Atualizar Configurações de Rádio",
|
||||||
"repeater_refreshTxPower": "Atualizar TX de energia",
|
"repeater_refreshTxPower": "Atualizar TX de energia",
|
||||||
"repeater_refreshLocationSettings": "Atualizar Configurações de Localização",
|
|
||||||
"repeater_refreshPacketForwarding": "Atualizar Roteamento de Pacotes",
|
"repeater_refreshPacketForwarding": "Atualizar Roteamento de Pacotes",
|
||||||
"repeater_refreshGuestAccess": "Atualizar Acesso de Convidados",
|
"repeater_refreshGuestAccess": "Atualizar Acesso de Convidados",
|
||||||
"repeater_refreshPrivacyMode": "Atualizar Modo Privacidade",
|
"repeater_refreshPrivacyMode": "Atualizar Modo Privacidade",
|
||||||
"repeater_refreshAdvertisementSettings": "Atualizar Configurações do Anúncio",
|
|
||||||
"repeater_refreshed": "{label} atualizado",
|
"repeater_refreshed": "{label} atualizado",
|
||||||
"@repeater_refreshed": {
|
"@repeater_refreshed": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1346,7 +1435,7 @@
|
|||||||
"listFilter_sortBy": "Ordenar por",
|
"listFilter_sortBy": "Ordenar por",
|
||||||
"listFilter_latestMessages": "Últimas mensagens",
|
"listFilter_latestMessages": "Últimas mensagens",
|
||||||
"listFilter_heardRecently": "Ouvido recentemente",
|
"listFilter_heardRecently": "Ouvido recentemente",
|
||||||
"listFilter_az": "A-Z",
|
"listFilter_az": "De A a Z",
|
||||||
"listFilter_filters": "Filtros",
|
"listFilter_filters": "Filtros",
|
||||||
"listFilter_all": "Tudo",
|
"listFilter_all": "Tudo",
|
||||||
"listFilter_users": "Usuários",
|
"listFilter_users": "Usuários",
|
||||||
@@ -1460,7 +1549,7 @@
|
|||||||
},
|
},
|
||||||
"community_title": "Comunidade",
|
"community_title": "Comunidade",
|
||||||
"community_createDesc": "Crie uma nova comunidade e compartilhe via código QR.",
|
"community_createDesc": "Crie uma nova comunidade e compartilhe via código QR.",
|
||||||
"common_ok": "OK",
|
"common_ok": "Tudo bem",
|
||||||
"community_create": "Criar Comunidade",
|
"community_create": "Criar Comunidade",
|
||||||
"community_join": "Junte-se",
|
"community_join": "Junte-se",
|
||||||
"community_joinTitle": "Junte-se à Comunidade",
|
"community_joinTitle": "Junte-se à Comunidade",
|
||||||
@@ -1859,5 +1948,372 @@
|
|||||||
"usbStatus_notConnected": "Selecione um dispositivo USB",
|
"usbStatus_notConnected": "Selecione um dispositivo USB",
|
||||||
"usbConnectionFailed": "Falha na conexão USB: {error}",
|
"usbConnectionFailed": "Falha na conexão USB: {error}",
|
||||||
"usbStatus_connecting": "Conectando ao dispositivo USB...",
|
"usbStatus_connecting": "Conectando ao dispositivo USB...",
|
||||||
"usbErrorConnectTimedOut": "A conexão expirou. Verifique se o dispositivo possui o firmware USB Companion."
|
"usbErrorConnectTimedOut": "A conexão expirou. Verifique se o dispositivo possui o firmware USB Companion.",
|
||||||
|
"@tcpStatus_connectingTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"endpoint": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tcpConnectionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tcpHostLabel": "Endereço IP",
|
||||||
|
"connectionChoiceTcpLabel": "TCP",
|
||||||
|
"tcpScreenTitle": "Estabelecer conexão via TCP",
|
||||||
|
"tcpHostHint": "192.168.40.10",
|
||||||
|
"tcpPortLabel": "Porta",
|
||||||
|
"tcpPortHint": "5000",
|
||||||
|
"tcpStatus_notConnected": "Insira o endereço final e conecte-se.",
|
||||||
|
"tcpStatus_connectingTo": "Conectando a {endpoint}...",
|
||||||
|
"tcpErrorHostRequired": "É necessário fornecer um endereço IP.",
|
||||||
|
"tcpErrorPortInvalid": "O valor do porto deve estar entre 1 e 65535.",
|
||||||
|
"tcpErrorUnsupported": "O protocolo TCP não é suportado nesta plataforma.",
|
||||||
|
"tcpErrorTimedOut": "A conexão TCP expirou.",
|
||||||
|
"tcpConnectionFailed": "Falha na conexão TCP: {error}",
|
||||||
|
"map_showDiscoveryContacts": "Mostrar Contatos de Descoberta",
|
||||||
|
"map_setAsMyLocation": "Defina minha localização",
|
||||||
|
"@path_routeWeight": {
|
||||||
|
"placeholders": {
|
||||||
|
"weight": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings_privacySettingsDescription": "Escolha quais informações o seu dispositivo compartilha com os outros.",
|
||||||
|
"settings_allowByContact": "Permitir por bandeiras de contato",
|
||||||
|
"settings_telemetryLocationMode": "Modo de Localização de Telemetria",
|
||||||
|
"settings_telemetryEnvironmentMode": "Modo de Ambiente de Telemetria",
|
||||||
|
"settings_advertLocation": "Localização do Anúncio",
|
||||||
|
"settings_advertLocationSubtitle": "Incluir localização no anúncio",
|
||||||
|
"settings_privacySubtitle": "Controle o que é compartilhado.",
|
||||||
|
"settings_denyAll": "Negar todos",
|
||||||
|
"settings_allowAll": "Permitir todos",
|
||||||
|
"settings_privacy": "Configurações de Privacidade",
|
||||||
|
"contact_info": "Informações de Contato",
|
||||||
|
"settings_telemetryBaseMode": "Modo Base de Telemetria",
|
||||||
|
"contact_teleBase": "Base de Telemetria",
|
||||||
|
"contact_teleLoc": "Localização de Telemetria",
|
||||||
|
"contact_teleLocSubtitle": "Permitir compartilhamento de dados de localização",
|
||||||
|
"contact_teleEnv": "Ambiente de Telemetria",
|
||||||
|
"contact_teleEnvSubtitle": "Permitir compartilhamento de dados do sensor de ambiente",
|
||||||
|
"contact_lastSeen": "Visto pela última vez",
|
||||||
|
"contact_clearChat": "Limpar Chat",
|
||||||
|
"contact_telemetry": "Telemetria",
|
||||||
|
"contact_settings": "Configurações de Contato",
|
||||||
|
"contact_teleBaseSubtitle": "Permitir compartilhamento do nível da bateria e telemetria básica",
|
||||||
|
"appSettings_initialRouteWeight": "Peso Inicial da Rota",
|
||||||
|
"appSettings_maxRouteWeight": "Peso Máximo da Rota",
|
||||||
|
"appSettings_maxRouteWeightSubtitle": "Peso máximo que um determinado percurso pode acumular com entregas bem-sucedidas.",
|
||||||
|
"appSettings_initialRouteWeightSubtitle": "Peso inicial para novos caminhos descobertos",
|
||||||
|
"appSettings_routeWeightSuccessIncrement": "Aumento do peso para indicar sucesso",
|
||||||
|
"appSettings_routeWeightSuccessIncrementSubtitle": "Peso adicionado a um caminho após a entrega bem-sucedida.",
|
||||||
|
"appSettings_routeWeightFailureDecrement": "Redução do peso da falha",
|
||||||
|
"appSettings_routeWeightFailureDecrementSubtitle": "Peso removido de um caminho após uma tentativa de entrega malsucedida.",
|
||||||
|
"appSettings_maxMessageRetries": "Número máximo de tentativas de envio de mensagens",
|
||||||
|
"appSettings_maxMessageRetriesSubtitle": "Número de tentativas de reenvio antes de classificar uma mensagem como falha.",
|
||||||
|
"path_routeWeight": "{weight}/{max}",
|
||||||
|
"settings_telemetryModeUpdated": "Modo de telemetria atualizado",
|
||||||
|
"settings_multiAck": "Multi-ACKs",
|
||||||
|
"map_showOverlaps": "Sobreposições da Chave Repeater",
|
||||||
|
"map_runTraceWithReturnPath": "Retornar ao mesmo caminho.",
|
||||||
|
"@radioStats_noiseFloor": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastRssi": {
|
||||||
|
"placeholders": {
|
||||||
|
"rssiDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastSnr": {
|
||||||
|
"placeholders": {
|
||||||
|
"snr": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_txAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_rxAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_stripNoise": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_jumpToOldestUnread": "Vá para a mensagem mais antiga não lida",
|
||||||
|
"chat_sendCooldown": "Por favor, aguarde um momento antes de reenviar.",
|
||||||
|
"appSettings_languageHu": "Húngaro",
|
||||||
|
"appSettings_jumpToOldestUnreadSubtitle": "Ao abrir uma conversa com mensagens não lidas, role para a primeira mensagem não lida, em vez da mais recente.",
|
||||||
|
"appSettings_languageJa": "Japonês",
|
||||||
|
"appSettings_languageKo": "Coreano",
|
||||||
|
"radioStats_tooltip": "Estatísticas de rádio e malha",
|
||||||
|
"radioStats_screenTitle": "Estatísticas de rádio",
|
||||||
|
"radioStats_notConnected": "Conecte-se a um dispositivo para visualizar estatísticas de rádio.",
|
||||||
|
"radioStats_firmwareTooOld": "As estatísticas de rádio exigem o firmware v8 ou uma versão mais recente.",
|
||||||
|
"radioStats_waiting": "Aguardando dados…",
|
||||||
|
"radioStats_noiseFloor": "Nível de ruído: {noiseDbm} dBm",
|
||||||
|
"radioStats_lastRssi": "Último RSSI: {rssiDbm} dBm",
|
||||||
|
"radioStats_lastSnr": "Último SNR: {snr} dB",
|
||||||
|
"radioStats_txAir": "Tempo de transmissão da TX (total): {seconds} s",
|
||||||
|
"radioStats_rxAir": "Tempo de uso do RX (total): {seconds} s",
|
||||||
|
"radioStats_chartCaption": "Nível de ruído (dBm) em amostras recentes.",
|
||||||
|
"radioStats_stripNoise": "Nível de ruído: {noiseDbm} dBm",
|
||||||
|
"radioStats_stripWaiting": "Obtendo estatísticas de rádio…",
|
||||||
|
"radioStats_settingsTile": "Estatísticas de rádio",
|
||||||
|
"radioStats_settingsSubtitle": "Nível de ruído, RSSI, SNR e tempo de transmissão",
|
||||||
|
"@translation_downloadFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_composerTitle": "Traduza antes de enviar",
|
||||||
|
"translation_enableSubtitle": "Traduzir mensagens recebidas e permitir a tradução antes do envio.",
|
||||||
|
"translation_enableTitle": "Ativar a tradução",
|
||||||
|
"translation_title": "Tradução",
|
||||||
|
"translation_composerSubtitle": "Controla o estado padrão do ícone de tradução do compositor.",
|
||||||
|
"translation_targetLanguage": "Língua-alvo",
|
||||||
|
"translation_useAppLanguage": "Utilize o idioma da aplicação",
|
||||||
|
"translation_downloadedModelLabel": "Modelo baixado",
|
||||||
|
"translation_presetModelLabel": "Modelo pré-definido da Hugging Face",
|
||||||
|
"translation_manualUrlLabel": "URL do modelo manual",
|
||||||
|
"translation_downloading": "Baixando...",
|
||||||
|
"translation_downloadModel": "Baixar modelo",
|
||||||
|
"translation_working": "Trabalhando...",
|
||||||
|
"translation_stop": "Pare",
|
||||||
|
"translation_mergingChunks": "Combinando os fragmentos baixados em um único arquivo...",
|
||||||
|
"translation_downloadedModels": "Modelos baixados",
|
||||||
|
"translation_deleteModel": "Excluir modelo",
|
||||||
|
"translation_modelDownloaded": "Modelo de tradução baixado.",
|
||||||
|
"translation_downloadStopped": "Download interrompido.",
|
||||||
|
"translation_downloadFailed": "Falha na descarga: {error}",
|
||||||
|
"translation_enterUrlFirst": "Insira primeiro a URL do modelo.",
|
||||||
|
"@scanner_linuxPairingPinPrompt": {
|
||||||
|
"placeholders": {
|
||||||
|
"deviceName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@translation_translateTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"language": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_messageTranslation": "Tradução da mensagem",
|
||||||
|
"translation_translateBeforeSending": "Traduzir antes de enviar",
|
||||||
|
"translation_composerEnabledHint": "As mensagens serão traduzidas antes de serem enviadas.",
|
||||||
|
"translation_composerDisabledHint": "Envie mensagens no idioma original, conforme digitado.",
|
||||||
|
"translation_translateTo": "Traduzir para {language}",
|
||||||
|
"translation_translationOptions": "Opções de tradução",
|
||||||
|
"translation_systemLanguage": "Idioma do sistema",
|
||||||
|
"scanner_linuxPairingShowPin": "Mostrar PIN",
|
||||||
|
"scanner_linuxPairingHidePin": "Ocultar PIN",
|
||||||
|
"scanner_linuxPairingPinPrompt": "Insira o PIN para {deviceName} (deixe em branco se não houver).",
|
||||||
|
"scanner_linuxPairingPinTitle": "PIN de emparelhamento Bluetooth",
|
||||||
|
"repeater_cliQuickClockSync": "Sincronização do Relógio",
|
||||||
|
"repeater_cliQuickDiscovery": "Descobrir Vizinhos",
|
||||||
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Enviar automaticamente a sincronização do \"relógio\" após um login bem-sucedido.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Sincronização do relógio após o login",
|
||||||
|
"room_guest": "Informações do Servidor",
|
||||||
|
"chat_sendMessage": "Enviar mensagem",
|
||||||
|
"repeater_guest": "Informações sobre repetidores",
|
||||||
|
"repeater_guestTools": "Ferramentas para hóspedes",
|
||||||
|
"repeater_getCategory": "Obter valores",
|
||||||
|
"repeater_powerMgmt": "Gerenciamento de energia",
|
||||||
|
"repeater_sensors": "Sensores",
|
||||||
|
"repeater_cliHelpPowerOff": "Desliga o dispositivo. (não se espera resposta)",
|
||||||
|
"repeater_cliHelpClkReboot": "Redefine o relógio para uma data conhecida e reinicia o dispositivo.",
|
||||||
|
"repeater_cliHelpAdvertZeroHop": "Envia um anúncio sem \"salto\" (apenas para vizinhos próximos).",
|
||||||
|
"repeater_cliHelpStartOta": "Inicia uma atualização de firmware via rádio em placas compatíveis.",
|
||||||
|
"repeater_cliHelpTime": "Define o relógio do dispositivo para os segundos da época Unix especificados. O relógio não pode retroceder.",
|
||||||
|
"repeater_cliHelpBoard": "Indica o fabricante da placa / identificador de hardware.",
|
||||||
|
"repeater_cliHelpDiscoverNeighbors": "Envia uma solicitação de descoberta de nós para os vizinhos próximos. (Apenas para repetidores)",
|
||||||
|
"repeater_cliHelpPowersaving": "Indica se o modo de economia de energia está ativado ou desativado.",
|
||||||
|
"repeater_cliHelpPowersavingOnOff": "Habilita ou desabilita o modo de economia de energia (quando disponível).",
|
||||||
|
"repeater_cliHelpErase": "(Apenas para dispositivos) Formata o sistema de arquivos do dispositivo. Apaga todas as configurações e contatos.",
|
||||||
|
"repeater_cliHelpSetDutyCycle": "Define o ciclo de transmissão máximo permitido como uma porcentagem (1-100). Ajusta internamente o fator de tempo de transmissão.",
|
||||||
|
"repeater_cliHelpSetPrvKey": "(Apenas para uso em série) Substitui a chave privada de identificação do dispositivo. É necessário reiniciar o dispositivo para aplicar a alteração. Gera uma nova chave pública.",
|
||||||
|
"repeater_cliHelpSetRadioRxGain": "(Apenas para SX126x) Alterna o ganho amplificado do receptor (RX) para melhorar a sensibilidade em condições de corrente mais elevada.",
|
||||||
|
"repeater_cliHelpSetOwnerInfo": "Define a string com as informações de contato do proprietário, que será incluída nos anúncios. Utilize '|' para indicar novas linhas.",
|
||||||
|
"repeater_cliHelpSetPathHashMode": "Define o modo de hash de caminho. 0 = modo legado, 1 = modo padrão, 2 = modo rigoroso. Afeta a forma como os caminhos de roteamento são correspondidos.",
|
||||||
|
"repeater_cliHelpSetLoopDetect": "Define o nível de sensibilidade para a detecção de loops de roteamento: desligado, mínimo, moderado ou estrito.",
|
||||||
|
"repeater_cliHelpSetFreq": "(Apenas para rádio) Define rapidamente a frequência. É necessário reiniciar o dispositivo. Recomenda-se usar a opção \"configurar rádio\" para definir todos os parâmetros do rádio.",
|
||||||
|
"repeater_cliHelpSetBridgeChannel": "(Apenas para a ponte ESPNow) Define o canal Wi-Fi (1-14) utilizado pela ponte.",
|
||||||
|
"repeater_cliHelpGetName": "Mostra o nome do nó configurado.",
|
||||||
|
"repeater_cliHelpGetRole": "Mostra o papel do firmware (Repetidor, Servidor de Sala, etc.).",
|
||||||
|
"repeater_cliHelpGetPublicKey": "Exibe a chave pública do dispositivo.",
|
||||||
|
"repeater_cliHelpGetPrvKey": "(Apenas para uso em série) Exibe a chave privada do dispositivo. Trate-a como uma informação confidencial.",
|
||||||
|
"repeater_cliHelpGetRepeat": "Indica se a função de encaminhamento de pacotes (função de repetidor) está ativada ou desativada.",
|
||||||
|
"repeater_cliHelpGetTx": "Mostra a potência atual em dBm.",
|
||||||
|
"repeater_cliHelpGetFreq": "Mostra a frequência de rádio configurada em MHz.",
|
||||||
|
"repeater_cliHelpGetRadio": "Exibe todos os parâmetros de rádio: frequência, largura de banda, fator de espalhamento, taxa de codificação.",
|
||||||
|
"repeater_cliHelpGetRadioRxGain": "(Apenas para SX126x) Mostra o estado do ganho amplificado do RX.",
|
||||||
|
"repeater_cliHelpGetAf": "Mostra o fator de tempo de transmissão atual.",
|
||||||
|
"repeater_cliHelpGetDutyCycle": "Mostra o ciclo de trabalho atual permitido em porcentagem.",
|
||||||
|
"repeater_cliHelpGetIntThresh": "Mostra o limite de interferência do canal em dB.",
|
||||||
|
"repeater_cliHelpGetAgcResetInterval": "Mostra o intervalo de reinicialização do AGC em segundos.",
|
||||||
|
"repeater_cliHelpGetMultiAcks": "Indica se o modo de confirmação dupla está ativado (1) ou desativado (0).",
|
||||||
|
"repeater_cliHelpGetAllowReadOnly": "Indica se o acesso somente de leitura para os convidados está habilitado.",
|
||||||
|
"repeater_cliHelpGetAdvertInterval": "Indica o intervalo de publicidade local em minutos.",
|
||||||
|
"repeater_cliHelpGetFloodAdvertInterval": "Mostra o intervalo de tempo da publicidade relacionada às inundações, em horas.",
|
||||||
|
"repeater_cliHelpGetGuestPassword": "Mostra a senha de convidado configurada.",
|
||||||
|
"repeater_cliHelpGetLat": "Mostra a latitude configurada.",
|
||||||
|
"repeater_cliHelpGetLon": "Mostra a longitude configurada.",
|
||||||
|
"repeater_cliHelpGetRxDelay": "Mostra o valor base do atraso de resposta.",
|
||||||
|
"repeater_cliHelpGetTxDelay": "Mostra o fator de atraso em modo de inundação.",
|
||||||
|
"repeater_cliHelpGetDirectTxDelay": "Mostra o fator de atraso direto.",
|
||||||
|
"repeater_cliHelpGetFloodMax": "Mostra o número máximo de saltos devido às inundações.",
|
||||||
|
"repeater_cliHelpGetOwnerInfo": "Exibe a string de informações de contato do proprietário.",
|
||||||
|
"repeater_cliHelpGetPathHashMode": "Mostra o modo de hash de caminho (0/1/2).",
|
||||||
|
"repeater_cliHelpGetLoopDetect": "Demonstra a sensibilidade na detecção de loops.",
|
||||||
|
"repeater_cliHelpGetAcl": "(Apenas para séries) Lista as entradas de controle de acesso em um repetidor.",
|
||||||
|
"repeater_cliHelpGetBridgeEnabled": "Indica se a ponte está habilitada.",
|
||||||
|
"repeater_cliHelpGetBridgeDelay": "Mostra o atraso da ponte em milissegundos.",
|
||||||
|
"repeater_cliHelpGetBridgeSource": "Indica se a ponte está enviando ou recebendo pacotes RX ou TX.",
|
||||||
|
"repeater_cliHelpGetBridgeBaud": "(Apenas para ponte RS232) Exibe a taxa de baud da ponte.",
|
||||||
|
"repeater_cliHelpGetBridgeChannel": "(Apenas para a ponte ESPNow) Exibe o canal WiFi da ponte.",
|
||||||
|
"repeater_cliHelpGetBridgeSecret": "(Apenas para a ponte ESPNow) Exibe o segredo compartilhado pela ponte.",
|
||||||
|
"repeater_cliHelpGetBootloaderVer": "(Apenas para NRF52) Exibe a versão do bootloader.",
|
||||||
|
"repeater_cliHelpGetAdcMultiplier": "Mostra o multiplicador do ADC (escalonamento da tensão da bateria).",
|
||||||
|
"repeater_cliHelpGetPwrMgtSupport": "Indica se o sistema possui suporte para gerenciamento de energia.",
|
||||||
|
"repeater_cliHelpGetPwrMgtSource": "Indica a fonte de energia atual: externa ou bateria.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootReason": "Mostra as razões mais recentes para a reinicialização e desligamento.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootMv": "Mostra a tensão da bateria no momento da inicialização, em milivolts (mV).",
|
||||||
|
"repeater_cliHelpSensorGet": "Lê uma configuração de sensor personalizada através de uma chave.",
|
||||||
|
"repeater_cliHelpSensorSet": "Cria uma configuração personalizada para um sensor.",
|
||||||
|
"repeater_cliHelpSensorList": "Lista todas as configurações de sensores personalizadas, organizadas em páginas a partir de um índice de início opcional.",
|
||||||
|
"repeater_cliHelpRegionDefault": "Mostra o escopo de região padrão atual.",
|
||||||
|
"repeater_cliHelpRegionDefaultSet": "Define o escopo regional padrão. Use \"<null>\" para limpar.",
|
||||||
|
"repeater_cliHelpRegionListAllowed": "Lista as regiões que permitem o tráfego em áreas de risco de inundações.",
|
||||||
|
"repeater_cliHelpRegionListDenied": "Lista as regiões que restringem o tráfego em áreas de risco de inundações.",
|
||||||
|
"repeater_cliHelpStatsPackets": "(Apenas para séries) Apresenta estatísticas em nível de pacotes.",
|
||||||
|
"repeater_cliHelpStatsRadio": "(Apenas para transmissões em série) Exibe estatísticas de rádio.",
|
||||||
|
"repeater_cliHelpStatsCore": "(Apenas para dispositivos em série) Exibe estatísticas básicas do firmware.",
|
||||||
|
"common_done": "Done",
|
||||||
|
"background_serviceTitle": "MeshCore running",
|
||||||
|
"background_serviceText": "Keeping BLE connected",
|
||||||
|
"appSettings_translationModelDeleted": "Deleted {name}",
|
||||||
|
"@appSettings_translationModelDeleted": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
|
||||||
|
"@appSettings_translationModelDeleteFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channels_channelUpdateFailed": "Failed to update channel: {error}",
|
||||||
|
"@channels_channelUpdateFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map_type": "Type",
|
||||||
|
"map_path": "Path",
|
||||||
|
"map_location": "Location",
|
||||||
|
"map_estLocation": "Est. Location",
|
||||||
|
"map_publicKey": "Public Key",
|
||||||
|
"map_publicKeyPrefixHint": "e.g. ab12",
|
||||||
|
"contact_typeChat": "Chat",
|
||||||
|
"contact_typeRepeater": "Repeater",
|
||||||
|
"contact_typeRoom": "Room",
|
||||||
|
"contact_typeSensor": "Sensor",
|
||||||
|
"contact_typeUnknown": "Unknown",
|
||||||
|
"channels_via": "via {path}",
|
||||||
|
"chat_score": "Score",
|
||||||
|
"map_sharedAt": "Compartilhado",
|
||||||
|
"@losBlockedSpotChip": {
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@losSelectedObstructionDetails": {
|
||||||
|
"placeholders": {
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromA": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromB": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"losBlockedSpotsTitle": "Locais ocupados",
|
||||||
|
"losBlockedSpotsHint": "Toque em um ponto bloqueado para destacá-lo no mapa.",
|
||||||
|
"losSelectedObstructionTitle": "Obstrução selecionada",
|
||||||
|
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
|
||||||
|
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).",
|
||||||
|
"settings_companionDebugLog": "Registro de depuração auxiliar",
|
||||||
|
"settings_companionDebugLogSubtitle": "Comandos, respostas e dados brutos para protocolos BLE/TCP/USB",
|
||||||
|
"chat_markAsUnread": "Marcar como não lido",
|
||||||
|
"chat_newMessages": "Novas mensagens",
|
||||||
|
"repeater_chanUtil": "Utilização do canal",
|
||||||
|
"dialog_connectCompanion": "Conecte-se a um dispositivo companion para acessar as funcionalidades de repetidor e servidor de salas.",
|
||||||
|
"dialog_disconnectedTitle": "Desconectado",
|
||||||
|
"dialog_disconnectedMessage": "Você foi desconectado do seu companheiro.",
|
||||||
|
"contact_connectCompanion": "Conecte-se a um companheiro para acessar recursos de repetidor e servidor de sala."
|
||||||
}
|
}
|
||||||
|
|||||||
+523
-4
@@ -81,6 +81,8 @@
|
|||||||
"settings_privacyModeEnabled": "Режим конфиденциальности включен",
|
"settings_privacyModeEnabled": "Режим конфиденциальности включен",
|
||||||
"settings_privacyModeDisabled": "Режим конфиденциальности выключен",
|
"settings_privacyModeDisabled": "Режим конфиденциальности выключен",
|
||||||
"settings_actions": "Действия",
|
"settings_actions": "Действия",
|
||||||
|
"settings_deleteAllPaths": "Delete All Paths",
|
||||||
|
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
|
||||||
"settings_sendAdvertisement": "Отправить анонсирование",
|
"settings_sendAdvertisement": "Отправить анонсирование",
|
||||||
"settings_sendAdvertisementSubtitle": "Отправить анонсирование о присутствии сейчас",
|
"settings_sendAdvertisementSubtitle": "Отправить анонсирование о присутствии сейчас",
|
||||||
"settings_advertisementSent": "Анонсирование отправлено",
|
"settings_advertisementSent": "Анонсирование отправлено",
|
||||||
@@ -212,6 +214,7 @@
|
|||||||
"contacts_newGroup": "Новая группа",
|
"contacts_newGroup": "Новая группа",
|
||||||
"contacts_groupName": "Имя группы",
|
"contacts_groupName": "Имя группы",
|
||||||
"contacts_groupNameRequired": "Имя группы обязательно",
|
"contacts_groupNameRequired": "Имя группы обязательно",
|
||||||
|
"contacts_groupNameReserved": "Это имя группы зарезервировано",
|
||||||
"contacts_groupAlreadyExists": "Группа \"{name}\" уже существует",
|
"contacts_groupAlreadyExists": "Группа \"{name}\" уже существует",
|
||||||
"contacts_filterContacts": "Фильтр контактов...",
|
"contacts_filterContacts": "Фильтр контактов...",
|
||||||
"contacts_noContactsMatchFilter": "Нет контактов, соответствующих фильтру",
|
"contacts_noContactsMatchFilter": "Нет контактов, соответствующих фильтру",
|
||||||
@@ -228,11 +231,8 @@
|
|||||||
"channels_searchChannels": "Поиск каналов...",
|
"channels_searchChannels": "Поиск каналов...",
|
||||||
"channels_noChannelsFound": "Каналы не найдены",
|
"channels_noChannelsFound": "Каналы не найдены",
|
||||||
"channels_channelIndex": "Канал {index}",
|
"channels_channelIndex": "Канал {index}",
|
||||||
"channels_hashtagChannel": "Хэштег-канал",
|
|
||||||
"channels_public": "Публичный",
|
"channels_public": "Публичный",
|
||||||
"channels_private": "Приватный",
|
"channels_private": "Приватный",
|
||||||
"channels_publicChannel": "Публичный канал",
|
|
||||||
"channels_privateChannel": "Приватный канал",
|
|
||||||
"channels_editChannel": "Изменить канал",
|
"channels_editChannel": "Изменить канал",
|
||||||
"channels_muteChannel": "Отключить уведомления канала",
|
"channels_muteChannel": "Отключить уведомления канала",
|
||||||
"channels_unmuteChannel": "Включить уведомления канала",
|
"channels_unmuteChannel": "Включить уведомления канала",
|
||||||
@@ -251,6 +251,22 @@
|
|||||||
"channels_channelAdded": "Канал \"{name}\" добавлен",
|
"channels_channelAdded": "Канал \"{name}\" добавлен",
|
||||||
"channels_editChannelTitle": "Изменить канал {index}",
|
"channels_editChannelTitle": "Изменить канал {index}",
|
||||||
"channels_smazCompression": "Сжатие SMAZ",
|
"channels_smazCompression": "Сжатие SMAZ",
|
||||||
|
"channels_cyr2latCompression": "Сжатие Cyr2Lat",
|
||||||
|
"channels_cyr2latCompressionDscr": "Заменяет некоторые кириллические символы на латиницу при отправке.",
|
||||||
|
"channels_cyr2latSettingsHeading": "Настройка Cyr2Lat",
|
||||||
|
"channels_cyr2latSettingsSubheading": "Список замен",
|
||||||
|
"channels_cyr2latSettingsDscr": "Редактировать JSON-конфигурацию замены символов",
|
||||||
|
"channels_cyr2latSettingsDialogHint": "JSON-карта замен",
|
||||||
|
"channels_cyr2latSettingsDialogWrongJSON": "Некорректный JSON: {error}",
|
||||||
|
"settings_cyr2latProfileAdd": "Добавить профиль Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileName": "Название профиля",
|
||||||
|
"settings_cyr2latProfileNameEmpty": "Название профиля не может быть пустым",
|
||||||
|
"settings_cyr2latProfileAdded": "Профиль добавлен",
|
||||||
|
"settings_cyr2latProfileUpdated": "Профиль успешно обновлен",
|
||||||
|
"settings_cyr2latProfileEdit": "Редактировать профиль Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileDelete": "Удалить профиль Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileDeleted": "Профиль успешно удален",
|
||||||
|
"settings_cyr2latProfileDeleteDscr": "Вы действительно хотите удалить профиль \"{name}\"?",
|
||||||
"channels_channelUpdated": "Канал \"{name}\" обновлён",
|
"channels_channelUpdated": "Канал \"{name}\" обновлён",
|
||||||
"channels_publicChannelAdded": "Публичный канал добавлен",
|
"channels_publicChannelAdded": "Публичный канал добавлен",
|
||||||
"channels_sortBy": "Сортировка",
|
"channels_sortBy": "Сортировка",
|
||||||
@@ -357,6 +373,8 @@
|
|||||||
"chat_direct": "Прямой",
|
"chat_direct": "Прямой",
|
||||||
"chat_poiShared": "Точка интереса отправлена",
|
"chat_poiShared": "Точка интереса отправлена",
|
||||||
"chat_unread": "Непрочитанных: {count}",
|
"chat_unread": "Непрочитанных: {count}",
|
||||||
|
"chat_markAsUnread": "Пометить как непрочитанные",
|
||||||
|
"chat_newMessages": "Новые сообщения",
|
||||||
"map_title": "Карта нод",
|
"map_title": "Карта нод",
|
||||||
"map_noNodesWithLocation": "Нет нод с данными о местоположении",
|
"map_noNodesWithLocation": "Нет нод с данными о местоположении",
|
||||||
"map_nodesNeedGps": "Ноды должны передавать свои GPS-координаты, чтобы отображаться на карте",
|
"map_nodesNeedGps": "Ноды должны передавать свои GPS-координаты, чтобы отображаться на карте",
|
||||||
@@ -1099,5 +1117,506 @@
|
|||||||
"usbStatus_connecting": "Подключение к USB-устройству...",
|
"usbStatus_connecting": "Подключение к USB-устройству...",
|
||||||
"usbConnectionFailed": "Не удалось установить соединение через USB: {error}",
|
"usbConnectionFailed": "Не удалось установить соединение через USB: {error}",
|
||||||
"usbStatus_notConnected": "Выберите USB-устройство",
|
"usbStatus_notConnected": "Выберите USB-устройство",
|
||||||
"usbErrorConnectTimedOut": "Соединение не установлено. Убедитесь, что устройство имеет установленное программное обеспечение USB Companion."
|
"usbErrorConnectTimedOut": "Соединение не установлено. Убедитесь, что устройство имеет установленное программное обеспечение USB Companion.",
|
||||||
|
"@tcpStatus_connectingTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"endpoint": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tcpConnectionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tcpHostHint": "192.168.40.10",
|
||||||
|
"connectionChoiceTcpLabel": "TCP",
|
||||||
|
"tcpHostLabel": "IP-адрес",
|
||||||
|
"tcpScreenTitle": "Установить соединение по протоколу TCP",
|
||||||
|
"tcpPortLabel": "Порт",
|
||||||
|
"tcpPortHint": "5000",
|
||||||
|
"tcpStatus_notConnected": "Введите адрес и подключитесь.",
|
||||||
|
"tcpStatus_connectingTo": "Подключение к {endpoint}...",
|
||||||
|
"tcpErrorHostRequired": "Необходимо указать IP-адрес.",
|
||||||
|
"tcpErrorPortInvalid": "Порт должен находиться в диапазоне от 1 до 65535.",
|
||||||
|
"tcpErrorUnsupported": "Протокол TCP не поддерживается на этой платформе.",
|
||||||
|
"tcpErrorTimedOut": "Соединение TCP не удалось установить.",
|
||||||
|
"tcpConnectionFailed": "Не удалось установить соединение TCP: {error}",
|
||||||
|
"map_showDiscoveryContacts": "Показать контакты Discovery",
|
||||||
|
"map_setAsMyLocation": "Установить мое местоположение",
|
||||||
|
"@path_routeWeight": {
|
||||||
|
"placeholders": {
|
||||||
|
"weight": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings_privacy": "Настройки конфиденциальности",
|
||||||
|
"settings_privacySubtitle": "Контролируйте, какую информацию делиться.",
|
||||||
|
"settings_telemetryLocationMode": "Режим местоположения телеметрии",
|
||||||
|
"settings_telemetryEnvironmentMode": "Режим среды телеметрии",
|
||||||
|
"settings_advertLocation": "Местоположение рекламы",
|
||||||
|
"settings_advertLocationSubtitle": "Включить местоположение в объявление",
|
||||||
|
"settings_allowAll": "Разрешить все",
|
||||||
|
"settings_privacySettingsDescription": "Выберите, какую информацию ваше устройство будет делиться с другими.",
|
||||||
|
"settings_denyAll": "Отклонить все",
|
||||||
|
"settings_allowByContact": "Разрешить по флагам контактов",
|
||||||
|
"contact_info": "Контактная информация",
|
||||||
|
"settings_telemetryBaseMode": "Базовый режим телеметрии",
|
||||||
|
"contact_teleBase": "База телеметрии",
|
||||||
|
"contact_teleLoc": "Местоположение телеметрии",
|
||||||
|
"contact_teleLocSubtitle": "Разрешить обмен данными о местоположении",
|
||||||
|
"contact_teleEnv": "Среда телеметрии",
|
||||||
|
"contact_teleEnvSubtitle": "Разрешить обмен данными датчиков окружающей среды",
|
||||||
|
"contact_settings": "Настройки контактов",
|
||||||
|
"contact_telemetry": "Телеметрия",
|
||||||
|
"contact_clearChat": "Очистить чат",
|
||||||
|
"contact_lastSeen": "Последний раз видели",
|
||||||
|
"contact_teleBaseSubtitle": "Разрешить обмен уровнем заряда батареи и базовой телеметрией",
|
||||||
|
"appSettings_maxRouteWeight": "Максимальный допустимый вес маршрута",
|
||||||
|
"appSettings_maxRouteWeightSubtitle": "Максимальный вес, который может быть перевезён по определённому маршруту при успешных доставках.",
|
||||||
|
"appSettings_initialRouteWeightSubtitle": "Начальный вес для новых, только что открытых маршрутов",
|
||||||
|
"appSettings_initialRouteWeight": "Начальный вес маршрута",
|
||||||
|
"appSettings_routeWeightSuccessIncrement": "Увеличение веса успеха",
|
||||||
|
"appSettings_routeWeightSuccessIncrementSubtitle": "Вес, добавленный к маршруту после успешной доставки.",
|
||||||
|
"appSettings_routeWeightFailureDecrement": "Уменьшение веса неудачи",
|
||||||
|
"appSettings_routeWeightFailureDecrementSubtitle": "Вес, который был удален с пути после неудачной доставки.",
|
||||||
|
"appSettings_maxMessageRetries": "Максимальное количество повторных попыток отправки сообщения",
|
||||||
|
"appSettings_maxMessageRetriesSubtitle": "Количество попыток повторной отправки сообщения перед тем, как пометить его как неудачное.",
|
||||||
|
"path_routeWeight": "{weight}/{max}",
|
||||||
|
"settings_telemetryModeUpdated": "Режим телеметрии обновлен",
|
||||||
|
"map_showOverlaps": "Перекрытия ключа повтора",
|
||||||
|
"map_runTraceWithReturnPath": "Вернуться обратно по тому же пути",
|
||||||
|
"@radioStats_noiseFloor": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastRssi": {
|
||||||
|
"placeholders": {
|
||||||
|
"rssiDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastSnr": {
|
||||||
|
"placeholders": {
|
||||||
|
"snr": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_txAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_rxAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_stripNoise": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chat_sendCooldown": "Пожалуйста, подождите немного, прежде чем отправлять сообщение снова.",
|
||||||
|
"appSettings_jumpToOldestUnread": "Перейти к самому старому непрочитанному сообщению",
|
||||||
|
"appSettings_languageHu": "Венгерский",
|
||||||
|
"appSettings_jumpToOldestUnreadSubtitle": "При открытии чата с непрочитанными сообщениями, прокрутите страницу, чтобы увидеть первое непрочитанное сообщение, а не последнее.",
|
||||||
|
"appSettings_languageJa": "Японский",
|
||||||
|
"appSettings_languageKo": "Корейский",
|
||||||
|
"radioStats_tooltip": "Статистика радио и беспроводной сети",
|
||||||
|
"radioStats_screenTitle": "Статистика радиовещания",
|
||||||
|
"radioStats_notConnected": "Подключитесь к устройству, чтобы просмотреть статистику радио.",
|
||||||
|
"radioStats_firmwareTooOld": "Для работы радиостатистики требуется установленная версия прошивки v8 или более новая.",
|
||||||
|
"radioStats_waiting": "Ожидаем данных…",
|
||||||
|
"radioStats_noiseFloor": "Уровень шума: {noiseDbm} дБм",
|
||||||
|
"radioStats_lastRssi": "Последнее значение RSSI: {rssiDbm} дБм",
|
||||||
|
"radioStats_lastSnr": "Последнее значение SNR: {snr} дБ",
|
||||||
|
"radioStats_txAir": "Время эфира на телеканале TX (общее): {seconds} секунд",
|
||||||
|
"radioStats_rxAir": "Общее время использования RX (в секундах): {seconds} с",
|
||||||
|
"radioStats_chartCaption": "Уровень шума (дБм) на основе последних измерений.",
|
||||||
|
"radioStats_stripNoise": "Уровень шума: {noiseDbm} дБм",
|
||||||
|
"radioStats_stripWaiting": "Получение данных о радио…",
|
||||||
|
"radioStats_settingsTile": "Статистика радиовещания",
|
||||||
|
"radioStats_settingsSubtitle": "Уровень шума, RSSI, SNR и время передачи",
|
||||||
|
"@translation_downloadFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_enableSubtitle": "Переводить входящие сообщения и позволять предварительный перевод перед отправкой.",
|
||||||
|
"translation_composerTitle": "Переводить перед отправкой",
|
||||||
|
"translation_title": "Перевод",
|
||||||
|
"translation_enableTitle": "Включить перевод",
|
||||||
|
"translation_composerSubtitle": "Управляет исходным состоянием значка перевода, предоставляемого редактором.",
|
||||||
|
"translation_targetLanguage": "Целевой язык",
|
||||||
|
"translation_useAppLanguage": "Используйте язык приложения",
|
||||||
|
"translation_downloadedModelLabel": "Загруженная модель",
|
||||||
|
"translation_presetModelLabel": "Предопределенная модель от Hugging Face",
|
||||||
|
"translation_manualUrlLabel": "Ссылка на руководство",
|
||||||
|
"translation_downloadModel": "Скачать модель",
|
||||||
|
"translation_downloading": "Загрузка...",
|
||||||
|
"translation_stop": "Прекратите",
|
||||||
|
"translation_working": "Работа...",
|
||||||
|
"translation_mergingChunks": "Объединение скачанных фрагментов в один финальный файл...",
|
||||||
|
"translation_downloadedModels": "Загруженные модели",
|
||||||
|
"translation_deleteModel": "Удалить модель",
|
||||||
|
"translation_modelDownloaded": "Модель перевода загружена.",
|
||||||
|
"translation_downloadStopped": "Процесс загрузки был прерван.",
|
||||||
|
"translation_downloadFailed": "Не удалось скачать: {error}",
|
||||||
|
"translation_enterUrlFirst": "Сначала введите URL модели.",
|
||||||
|
"@scanner_linuxPairingPinPrompt": {
|
||||||
|
"placeholders": {
|
||||||
|
"deviceName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@translation_translateTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"language": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_translateBeforeSending": "Перевести перед отправкой",
|
||||||
|
"translation_composerEnabledHint": "Сообщения будут переведены перед отправкой.",
|
||||||
|
"translation_messageTranslation": "Перевод сообщения",
|
||||||
|
"translation_composerDisabledHint": "Отправляйте сообщения на языке, в котором они были изначально набраны.",
|
||||||
|
"translation_translateTo": "Перевести на {language}",
|
||||||
|
"translation_translationOptions": "Варианты перевода",
|
||||||
|
"translation_systemLanguage": "Язык системы",
|
||||||
|
"scanner_linuxPairingShowPin": "Показать PIN",
|
||||||
|
"scanner_linuxPairingPinPrompt": "Введите PIN‑код для {deviceName} (оставьте пустым, если нет).",
|
||||||
|
"scanner_linuxPairingHidePin": "Скрыть PIN",
|
||||||
|
"scanner_linuxPairingPinTitle": "PIN‑код сопряжения Bluetooth",
|
||||||
|
"repeater_cliQuickDiscovery": "Обнаружить Соседей",
|
||||||
|
"repeater_cliQuickClockSync": "Синхронизация часов",
|
||||||
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLogin": "Синхронизация часов после входа в систему",
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Автоматически отправлять сообщение \"синхронизация времени\" после успешной авторизации.",
|
||||||
|
"chat_sendMessage": "Отправить сообщение",
|
||||||
|
"repeater_guest": "Информация о ретрансляторе",
|
||||||
|
"room_guest": "Информация о сервере",
|
||||||
|
"repeater_guestTools": "Инструменты для гостей",
|
||||||
|
"common_done": "Готово",
|
||||||
|
"background_serviceTitle": "MeshCore работает",
|
||||||
|
"background_serviceText": "Поддерживает BLE-соединение",
|
||||||
|
"appSettings_translationModelDeleted": "Удалено {name}",
|
||||||
|
"@appSettings_translationModelDeleted": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_translationModelDeleteFailed": "Не удалось удалить: {error}",
|
||||||
|
"@appSettings_translationModelDeleteFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channels_channelUpdateFailed": "Не удалось обновить канал: {error}",
|
||||||
|
"@channels_channelUpdateFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map_type": "Тип",
|
||||||
|
"map_path": "Путь",
|
||||||
|
"map_location": "Местоположение",
|
||||||
|
"map_estLocation": "Прибл. местоположение",
|
||||||
|
"map_publicKey": "Публичный ключ",
|
||||||
|
"map_publicKeyPrefixHint": "напр. ab12",
|
||||||
|
"contact_typeChat": "Чат",
|
||||||
|
"contact_typeRepeater": "Ретранслятор",
|
||||||
|
"contact_typeRoom": "Комната",
|
||||||
|
"contact_typeSensor": "Датчик",
|
||||||
|
"contact_typeUnknown": "Неизвестно",
|
||||||
|
"channels_via": "через {path}",
|
||||||
|
"chat_score": "Оценка",
|
||||||
|
"settings_multiAck": "Несколько подтверждений",
|
||||||
|
"map_sharedAt": "Поделено",
|
||||||
|
"@losBlockedSpotChip": {
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@losSelectedObstructionDetails": {
|
||||||
|
"placeholders": {
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromA": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromB": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"losBlockedSpotsHint": "Щелкните по заблокированной области, чтобы выделить ее на карте.",
|
||||||
|
"losBlockedSpotsTitle": "Зарезервированные места",
|
||||||
|
"losSelectedObstructionTitle": "Выбранный объект, препятствующий движению",
|
||||||
|
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
|
||||||
|
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).",
|
||||||
|
"repeater_rxGain": "Увеличенная эффективность RX",
|
||||||
|
"repeater_rxGainHelper": "Более высокая чувствительность, больший ток потребления (только для SX1262/SX1268)",
|
||||||
|
"repeater_refreshRxGain": "Обновите усиление RX",
|
||||||
|
"repeater_multiAcks": "Несколько подтверждений",
|
||||||
|
"repeater_multiAcksSubtitle": "Обеспечьте доставку сообщений по нескольким каналам для повышения эффективности.",
|
||||||
|
"repeater_refreshMultiAcks": "Обновление нескольких подтверждений",
|
||||||
|
"repeater_networkHealth": "Состояние сети",
|
||||||
|
"repeater_loopDetect": "Обнаружение циклов",
|
||||||
|
"repeater_loopDetectHelper": "Создайте пакеты данных, которые выглядят как циклы маршрутизации.",
|
||||||
|
"repeater_loopDetectOff": "Отключено",
|
||||||
|
"repeater_loopDetectMinimal": "Минимальный",
|
||||||
|
"repeater_loopDetectModerate": "Умеренный",
|
||||||
|
"repeater_loopDetectStrict": "Строгий",
|
||||||
|
"repeater_dutyCycle": "Цикл работы",
|
||||||
|
"repeater_dutyCycleHelper": "Максимальный процент времени, выделенного на трансляцию.",
|
||||||
|
"repeater_dutyCyclePercent": "{percent}%",
|
||||||
|
"@repeater_dutyCyclePercent": {
|
||||||
|
"placeholders": {
|
||||||
|
"percent": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_ownerInfo": "Информация о операторе",
|
||||||
|
"repeater_ownerInfoHelper": "Общая метаинформация для этого ретранслятора",
|
||||||
|
"repeater_refreshOwnerInfo": "Обновить информацию о операторе",
|
||||||
|
"repeater_floodMax": "Максимальное количество прыжков при наводнении",
|
||||||
|
"repeater_floodMaxHelper": "Максимальное количество пакетов, которые могут быть отправлены в одном потоке (0-64)",
|
||||||
|
"repeater_advancedSettings": "Продвинутый",
|
||||||
|
"repeater_advancedSettingsSubtitle": "Регуляторы для опытных операторов",
|
||||||
|
"repeater_pathHashMode": "Режим хеширования пути",
|
||||||
|
"repeater_pathHashModeHelper": "Байты, используемые для кодирования идентификатора этого ретранслятора в тегах для обнаружения потоков/циклов. 0 = 1 байт (256 идентификаторов, до 64 переходов), 1 = 2 байта (65 000 идентификаторов, до 32 переходов), 2 = 3 байта (1 600 000 идентификаторов, до 21 перехода). Версии прошивки v1.13 и более ранние версии не поддерживают многобайтовые пути — они поднимаются только после того, как ваша сеть будет обновлена до версии v1.14 и выше.",
|
||||||
|
"repeater_txDelay": "Задержка в работе системы Flood TX",
|
||||||
|
"repeater_txDelayHelper": "Передача с увеличенным интервалом для трафика во время наводнения, в качестве коэффициента, умножающего время передачи пакета (от 0 до 2, по умолчанию 0,5). Более высокое значение означает меньшее количество столкновений, но более медленную передачу.",
|
||||||
|
"repeater_directTxDelay": "Прямая задержка сигнала TX",
|
||||||
|
"repeater_directTxDelayHelper": "Передача промежуточных данных для прямого (немассового) трафика, в качестве коэффициента, равного времени передачи пакета (от 0 до 2, по умолчанию 0,3).",
|
||||||
|
"repeater_intThresh": "Пороговое значение помех",
|
||||||
|
"repeater_intThreshHelper": "Порог устанавливается для калибровки уровня шума радио, чтобы оно отсеивало помехи, превышающие этот уровень. Значение \"0\" означает отключение – используйте только в случае, если вы наблюдаете ошибки при приеме сигнала в шумном диапазоне.",
|
||||||
|
"repeater_agcResetInterval": "Интервал сброса AGC",
|
||||||
|
"repeater_agcResetIntervalHelper": "Как часто следует сбрасывать автоматическую регулировку усиления радио, чтобы вернуться к нормальному состоянию после заклинивания? Интервал сброса составляет несколько секунд, кратный 4. Отключение периодического сброса осуществляется с помощью параметра 0.",
|
||||||
|
"repeater_actionsTitle": "Действия",
|
||||||
|
"repeater_sendAdvert": "Отправить объявление о наводнении",
|
||||||
|
"repeater_sendAdvertSubtitle": "Разместите рекламу о наводнении в эфире по всей сети.",
|
||||||
|
"repeater_sendAdvertZeroHop": "Опубликуйте рекламу, не требующую промежуточного распространения.",
|
||||||
|
"repeater_sendAdvertZeroHopSubtitle": "Разместите рекламу, распространяемую одним способом (без использования ретрансляторов).",
|
||||||
|
"repeater_clockSync": "Синхронизировать время сейчас",
|
||||||
|
"repeater_clockSyncSubtitle": "Установите время на вашем телефоне, чтобы оно совпадало со временем ретранслятора.",
|
||||||
|
"repeater_actionSucceeded": "{action} succeeded",
|
||||||
|
"@repeater_actionSucceeded": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_actionFailed": "{action} failed: {error}",
|
||||||
|
"@repeater_actionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_settingsSavedRebootNeeded": "Настройки сохранены — перезагрузите ретранслятор, чтобы применить их.",
|
||||||
|
"repeater_settingsPartialFailure": "Некоторые настройки не удалось применить: {failures}",
|
||||||
|
"@repeater_settingsPartialFailure": {
|
||||||
|
"placeholders": {
|
||||||
|
"failures": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@settings_multiAck": {
|
||||||
|
"placeholders": {
|
||||||
|
"value": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@common_percentValue": {
|
||||||
|
"placeholders": {
|
||||||
|
"percent": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@settings_aboutVersion": {
|
||||||
|
"placeholders": {
|
||||||
|
"version": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@telemetry_temperatureValue": {
|
||||||
|
"placeholders": {
|
||||||
|
"celsius": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"fahrenheit": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@channelPath_timeWithDate": {
|
||||||
|
"placeholders": {
|
||||||
|
"day": {
|
||||||
|
"type": "int"
|
||||||
|
},
|
||||||
|
"month": {
|
||||||
|
"type": "int"
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@channelPath_timeOnly": {
|
||||||
|
"placeholders": {
|
||||||
|
"time": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@channelPath_selectedPathLabel": {
|
||||||
|
"placeholders": {
|
||||||
|
"label": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"prefixes": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_getCategory": "Получить значения",
|
||||||
|
"repeater_powerMgmt": "Управление энергопотреблением",
|
||||||
|
"repeater_sensors": "Датчики",
|
||||||
|
"repeater_cliHelpPowerOff": "Отключает устройство. (ожидается отсутствие ответа).",
|
||||||
|
"repeater_cliHelpClkReboot": "Сбрасывает часы до известной эпохи и перезапускает устройство.",
|
||||||
|
"repeater_cliHelpAdvertZeroHop": "Отправляет рекламу, распространяемую только среди ближайших соседей (без промежуточных узлов).",
|
||||||
|
"repeater_cliHelpStartOta": "Запускает обновление прошивки по воздуху на поддерживаемых устройствах.",
|
||||||
|
"repeater_cliHelpTime": "Устанавливает время устройства в соответствии с заданными секундами от начала эпохи Unix. Время не может сброситься назад.",
|
||||||
|
"repeater_cliHelpBoard": "Отображает информацию о производителе платы / идентификатор аппаратного обеспечения.",
|
||||||
|
"repeater_cliHelpDiscoverNeighbors": "Отправляет запрос на обнаружение соседних узлов. (Только для ретранслятора)",
|
||||||
|
"repeater_cliHelpPowersavingOnOff": "Включает или выключает режим экономии энергии (если он поддерживается).",
|
||||||
|
"repeater_cliHelpPowersaving": "Показывает, включен ли режим экономии энергии.",
|
||||||
|
"repeater_cliHelpErase": "(Только для серийного использования) Форматирует файловую систему устройства. Удаляет все настройки и контакты.",
|
||||||
|
"repeater_cliHelpSetDutyCycle": "Устанавливает максимальный допустимый цикл передачи данных в процентах (от 1 до 100). Внутренне корректирует коэффициент времени передачи.",
|
||||||
|
"repeater_cliHelpSetPrvKey": "(Только для серийного использования) Заменяет приватный ключ, идентифицирующий устройство. Требуется перезагрузка для применения. Генерирует новый публичный ключ.",
|
||||||
|
"repeater_cliHelpSetRadioRxGain": "(Только для SX126x) Переключает усиление RX для повышения чувствительности при больших токах потребления.",
|
||||||
|
"repeater_cliHelpSetOwnerInfo": "Указывает строку с контактной информацией владельца, которая должна быть включена в объявления. Используйте '|' для переносов строк.",
|
||||||
|
"repeater_cliHelpSetPathHashMode": "Устанавливает режим хеширования пути. 0 = устаревший, 1 = стандартный, 2 = строгий. Влияет на то, как определяются маршруты.",
|
||||||
|
"repeater_cliHelpSetLoopDetect": "Устанавливает чувствительность обнаружения циклов маршрутизации: \"выключено\", \"минимальная\", \"умеренная\" или \"строгая\".",
|
||||||
|
"repeater_cliHelpSetFreq": "(Только для настройки) Быстро устанавливает только частоту. Требуется перезагрузка. Рекомендуется использовать функцию \"настройка радио\" для полного набора параметров.",
|
||||||
|
"repeater_cliHelpSetBridgeChannel": "(Только для моста ESPNow) Устанавливает канал Wi-Fi (от 1 до 14), используемый мостом.",
|
||||||
|
"repeater_cliHelpGetName": "Отображает имя настроенного узла.",
|
||||||
|
"repeater_cliHelpGetRole": "Отображает роль прошивки (ретранслятор, сервер для комнаты и т.д.).",
|
||||||
|
"repeater_cliHelpGetPublicKey": "Отображает открытый ключ устройства.",
|
||||||
|
"repeater_cliHelpGetPrvKey": "(Только для серийного использования) Отображает приватный ключ устройства. Рассматривайте его как секретную информацию.",
|
||||||
|
"repeater_cliHelpGetRepeat": "Отображает, включена ли функция перенаправления пакетов (функция ретранслятора) или нет.",
|
||||||
|
"repeater_cliHelpGetTx": "Отображает текущую мощность передатчика в дБм.",
|
||||||
|
"repeater_cliHelpGetFreq": "Отображает настроенную частоту радиосигнала в мегагерцах.",
|
||||||
|
"repeater_cliHelpGetRadio": "Отображает все параметры радиосигнала: частоту, полосу пропускания, коэффициент модуляции, скорость кодирования.",
|
||||||
|
"repeater_cliHelpGetRadioRxGain": "(Только для SX126x) Отображает состояние усиления сигнала на входе RX.",
|
||||||
|
"repeater_cliHelpGetAf": "Отображает текущий коэффициент времени эфира.",
|
||||||
|
"repeater_cliHelpGetDutyCycle": "Отображает текущий допустимый цикл работы в процентах.",
|
||||||
|
"repeater_cliHelpGetIntThresh": "Отображает порог помех в децибелах.",
|
||||||
|
"repeater_cliHelpGetAgcResetInterval": "Отображает интервал сброса автоматической регулировки усиления в секундах.",
|
||||||
|
"repeater_cliHelpGetMultiAcks": "Показывает, включен ли режим двойной подтверждения (1) или выключен (0).",
|
||||||
|
"repeater_cliHelpGetAllowReadOnly": "Отображает, разрешен ли доступ для чтения только для гостей.",
|
||||||
|
"repeater_cliHelpGetAdvertInterval": "Отображает продолжительность рекламного блока в минутах.",
|
||||||
|
"repeater_cliHelpGetFloodAdvertInterval": "Отображает интервал времени показа рекламного ролика в часах.",
|
||||||
|
"repeater_cliHelpGetGuestPassword": "Отображает установленный пароль для гостя.",
|
||||||
|
"repeater_cliHelpGetLat": "Отображает заданную широту.",
|
||||||
|
"repeater_cliHelpGetLon": "Отображает заданную долготу.",
|
||||||
|
"repeater_cliHelpGetRxDelay": "Отображает базовое значение задержки.",
|
||||||
|
"repeater_cliHelpGetTxDelay": "Отображает коэффициент задержки при работе в режиме затопления.",
|
||||||
|
"repeater_cliHelpGetDirectTxDelay": "Отображает коэффициент задержки в режиме прямого подключения.",
|
||||||
|
"repeater_cliHelpGetFloodMax": "Отображает максимальное количество переходов при затоплении.",
|
||||||
|
"repeater_cliHelpGetOwnerInfo": "Отображает строку с контактной информацией владельца.",
|
||||||
|
"repeater_cliHelpGetPathHashMode": "Отображает режим работы с хэшем пути (0/1/2).",
|
||||||
|
"repeater_cliHelpGetLoopDetect": "Отображает чувствительность к обнаружению циклов.",
|
||||||
|
"repeater_cliHelpGetAcl": "(Только для серий) Перечисляет записи управления доступом на ретрансляторе.",
|
||||||
|
"repeater_cliHelpGetBridgeEnabled": "Показывает, включена ли функция моста.",
|
||||||
|
"repeater_cliHelpGetBridgeDelay": "Отображает задержку в миллисекундах.",
|
||||||
|
"repeater_cliHelpGetBridgeSource": "Отображает, какие пакеты RX или TX передаются через мост.",
|
||||||
|
"repeater_cliHelpGetBridgeBaud": "(Только для интерфейса RS232) Отображает скорость передачи данных на интерфейсе RS232.",
|
||||||
|
"repeater_cliHelpGetBridgeChannel": "(Только для моста ESPNow) Отображает канал WiFi, используемый мостом.",
|
||||||
|
"repeater_cliHelpGetBridgeSecret": "(Только для моста ESPNow) Отображает общий секрет, используемый мостом.",
|
||||||
|
"repeater_cliHelpGetBootloaderVer": "(Только для NRF52) Отображает версию загрузчика.",
|
||||||
|
"repeater_cliHelpGetAdcMultiplier": "Отображает коэффициент умножения аналого-цифрового преобразователя (масштабирование напряжения от батареи).",
|
||||||
|
"repeater_cliHelpGetPwrMgtSupport": "Сообщает, есть ли у совета поддержки функций управления питанием.",
|
||||||
|
"repeater_cliHelpGetPwrMgtSource": "Отображает текущий источник питания: внешний или аккумулятор.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootReason": "Отображает последние причины сброса и выключения.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootMv": "Отображает напряжение батареи при запуске системы в милливольтах (мВ).",
|
||||||
|
"repeater_cliHelpSensorGet": "Считывает пользовательское значение для датчика по указанному ключу.",
|
||||||
|
"repeater_cliHelpSensorSet": "Создает пользовательские настройки для датчика.",
|
||||||
|
"repeater_cliHelpSensorList": "Перечисляет все пользовательские настройки датчиков, разбитые на страницы с возможностью указания начального индекса.",
|
||||||
|
"repeater_cliHelpRegionDefault": "Отображает текущий область действия по умолчанию.",
|
||||||
|
"repeater_cliHelpRegionDefaultSet": "Устанавливает значение региона по умолчанию. Используйте \"<null>\", чтобы сбросить значение.",
|
||||||
|
"repeater_cliHelpRegionListAllowed": "Перечисляет регионы, где разрешено движение транспорта во время наводнений.",
|
||||||
|
"repeater_cliHelpRegionListDenied": "Перечисляет регионы, где запрещено движение транспорта во время наводнений.",
|
||||||
|
"repeater_cliHelpStatsPackets": "(Только для серийной версии) Отображает статистику на уровне пакетов.",
|
||||||
|
"repeater_cliHelpStatsRadio": "(Только для серий) Отображает статистику радио.",
|
||||||
|
"repeater_cliHelpStatsCore": "(Только для серийного оборудования) Отображает основные статистические данные прошивки.",
|
||||||
|
"settings_companionDebugLogSubtitle": "Команды, ответы и необработанные данные, используемые для протоколов BLE, TCP и USB.",
|
||||||
|
"repeater_chanUtil": "Использование канала",
|
||||||
|
"settings_companionDebugLog": "Журнал отладки (для сопутствующего приложения)",
|
||||||
|
"dialog_connectCompanion": "Подключитесь к компаньону, чтобы получить доступ к функциям ретранслятора и сервера комнат.",
|
||||||
|
"dialog_disconnectedTitle": "Отключено",
|
||||||
|
"dialog_disconnectedMessage": "Вы были отключены от вашего компаньона.",
|
||||||
|
"contact_connectCompanion": "Подключитесь к компаньону, чтобы получить доступ к функциям репитера и серверу комнаты."
|
||||||
}
|
}
|
||||||
|
|||||||
+490
-34
@@ -52,7 +52,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scanner_title": "MeshCore Open",
|
"scanner_title": "MeshCore – Verzia pre verejnosť",
|
||||||
"scanner_scanning": "Skrívania zariadení...",
|
"scanner_scanning": "Skrívania zariadení...",
|
||||||
"scanner_connecting": "Pripojujem sa...",
|
"scanner_connecting": "Pripojujem sa...",
|
||||||
"scanner_disconnecting": "Odpojuje sa...",
|
"scanner_disconnecting": "Odpojuje sa...",
|
||||||
@@ -104,6 +104,8 @@
|
|||||||
"settings_privacyModeEnabled": "Ochranný režim je povolený.",
|
"settings_privacyModeEnabled": "Ochranný režim je povolený.",
|
||||||
"settings_privacyModeDisabled": "Ochranný režim je vypnutý",
|
"settings_privacyModeDisabled": "Ochranný režim je vypnutý",
|
||||||
"settings_actions": "Možné akcie",
|
"settings_actions": "Možné akcie",
|
||||||
|
"settings_deleteAllPaths": "Delete All Paths",
|
||||||
|
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
|
||||||
"settings_sendAdvertisement": "Odoslať reklamu",
|
"settings_sendAdvertisement": "Odoslať reklamu",
|
||||||
"settings_sendAdvertisementSubtitle": "Momentálne priezornejšie.",
|
"settings_sendAdvertisementSubtitle": "Momentálne priezornejšie.",
|
||||||
"settings_advertisementSent": "Reklama odeslaná",
|
"settings_advertisementSent": "Reklama odeslaná",
|
||||||
@@ -121,7 +123,7 @@
|
|||||||
"settings_appDebugLog": "Záznam ladenia aplikácie",
|
"settings_appDebugLog": "Záznam ladenia aplikácie",
|
||||||
"settings_appDebugLogSubtitle": "Správy z ladenia aplikácie",
|
"settings_appDebugLogSubtitle": "Správy z ladenia aplikácie",
|
||||||
"settings_about": "O nás",
|
"settings_about": "O nás",
|
||||||
"settings_aboutVersion": "MeshCore Open v{version}",
|
"settings_aboutVersion": "MeshCore, verzia {version}",
|
||||||
"@settings_aboutVersion": {
|
"@settings_aboutVersion": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"version": {
|
"version": {
|
||||||
@@ -132,8 +134,8 @@
|
|||||||
"settings_aboutLegalese": "MeshCore Open Source Projekt 2024",
|
"settings_aboutLegalese": "MeshCore Open Source Projekt 2024",
|
||||||
"settings_aboutDescription": "Otvorený zdrojový Flutter klient pre MeshCore LoRa sieťové zariadenia.",
|
"settings_aboutDescription": "Otvorený zdrojový Flutter klient pre MeshCore LoRa sieťové zariadenia.",
|
||||||
"settings_infoName": "Meno",
|
"settings_infoName": "Meno",
|
||||||
"settings_infoId": "ID",
|
"settings_infoId": "Identifikátor",
|
||||||
"settings_infoStatus": "Status",
|
"settings_infoStatus": "Stav",
|
||||||
"settings_infoBattery": "Batéria",
|
"settings_infoBattery": "Batéria",
|
||||||
"settings_infoPublicKey": "Verejný kľúč",
|
"settings_infoPublicKey": "Verejný kľúč",
|
||||||
"settings_infoContactsCount": "Počet kontaktov",
|
"settings_infoContactsCount": "Počet kontaktov",
|
||||||
@@ -146,7 +148,7 @@
|
|||||||
"settings_spreadingFactor": "Rozptýľovací faktor",
|
"settings_spreadingFactor": "Rozptýľovací faktor",
|
||||||
"settings_codingRate": "Cenový kurz pre programovanie",
|
"settings_codingRate": "Cenový kurz pre programovanie",
|
||||||
"settings_txPower": "TX Výkon (dBm)",
|
"settings_txPower": "TX Výkon (dBm)",
|
||||||
"settings_txPowerHelper": "0 - 22",
|
"settings_txPowerHelper": "0 – 22",
|
||||||
"settings_txPowerInvalid": "Neplatná hodnota výkonu TX (0-22 dBm)",
|
"settings_txPowerInvalid": "Neplatná hodnota výkonu TX (0-22 dBm)",
|
||||||
"settings_error": "Chyba: {message}",
|
"settings_error": "Chyba: {message}",
|
||||||
"@settings_error": {
|
"@settings_error": {
|
||||||
@@ -164,19 +166,19 @@
|
|||||||
"appSettings_themeDark": "Tmavé",
|
"appSettings_themeDark": "Tmavé",
|
||||||
"appSettings_language": "Jazyk",
|
"appSettings_language": "Jazyk",
|
||||||
"appSettings_languageSystem": "Predvolený systém",
|
"appSettings_languageSystem": "Predvolený systém",
|
||||||
"appSettings_languageEn": "English",
|
"appSettings_languageEn": "Anglicky",
|
||||||
"appSettings_languageFr": "Français",
|
"appSettings_languageFr": "Francúzština",
|
||||||
"appSettings_languageEs": "Español",
|
"appSettings_languageEs": "Španielsky",
|
||||||
"appSettings_languageDe": "Deutsch",
|
"appSettings_languageDe": "Nemecky",
|
||||||
"appSettings_languagePl": "Polski",
|
"appSettings_languagePl": "Poľský",
|
||||||
"appSettings_languageSl": "Slovenščina",
|
"appSettings_languageSl": "Slovenčina",
|
||||||
"appSettings_languagePt": "Português",
|
"appSettings_languagePt": "Portugalčina",
|
||||||
"appSettings_languageIt": "Italiano",
|
"appSettings_languageIt": "Taliančina",
|
||||||
"appSettings_languageZh": "中文",
|
"appSettings_languageZh": "Čínština",
|
||||||
"appSettings_languageSv": "Svenska",
|
"appSettings_languageSv": "Švédska",
|
||||||
"appSettings_languageNl": "Nederlands",
|
"appSettings_languageNl": "Niderlandsky",
|
||||||
"appSettings_languageSk": "Slovenčina",
|
"appSettings_languageSk": "Slovenčina",
|
||||||
"appSettings_languageBg": "Български",
|
"appSettings_languageBg": "Българština",
|
||||||
"appSettings_notifications": "Upozornenia",
|
"appSettings_notifications": "Upozornenia",
|
||||||
"appSettings_enableNotifications": "Povolte Notifikácie",
|
"appSettings_enableNotifications": "Povolte Notifikácie",
|
||||||
"appSettings_enableNotificationsSubtitle": "Zísť o upozornenia na správy a inzeráty",
|
"appSettings_enableNotificationsSubtitle": "Zísť o upozornenia na správy a inzeráty",
|
||||||
@@ -285,6 +287,7 @@
|
|||||||
"contacts_newGroup": "Nová skupina",
|
"contacts_newGroup": "Nová skupina",
|
||||||
"contacts_groupName": "Názov skupiny",
|
"contacts_groupName": "Názov skupiny",
|
||||||
"contacts_groupNameRequired": "Skupina musí mať názov.",
|
"contacts_groupNameRequired": "Skupina musí mať názov.",
|
||||||
|
"contacts_groupNameReserved": "Tento názov skupiny je rezervovaný",
|
||||||
"contacts_groupAlreadyExists": "Skupina \"{name}\" už existuje",
|
"contacts_groupAlreadyExists": "Skupina \"{name}\" už existuje",
|
||||||
"@contacts_groupAlreadyExists": {
|
"@contacts_groupAlreadyExists": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -336,11 +339,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_hashtagChannel": "Kanál s hashtagom",
|
|
||||||
"channels_public": "Veľké verejné",
|
"channels_public": "Veľké verejné",
|
||||||
"channels_private": "Osobné",
|
"channels_private": "Osobné",
|
||||||
"channels_publicChannel": "Veľké verejne kanály",
|
|
||||||
"channels_privateChannel": "Osobné kanál",
|
|
||||||
"channels_editChannel": "Upraviť kanál",
|
"channels_editChannel": "Upraviť kanál",
|
||||||
"channels_muteChannel": "Stlmiť kanál",
|
"channels_muteChannel": "Stlmiť kanál",
|
||||||
"channels_unmuteChannel": "Zrušiť stlmenie kanála",
|
"channels_unmuteChannel": "Zrušiť stlmenie kanála",
|
||||||
@@ -387,6 +387,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_smazCompression": "Odstránenie kompresie SMAZ",
|
"channels_smazCompression": "Odstránenie kompresie SMAZ",
|
||||||
|
"channels_cyr2latCompression": "Odstránenie kompresie Cyr2Lat",
|
||||||
|
"channels_cyr2latCompressionDscr": "Pri odosielaní nahradí niektoré znaky cyriliky latinskými znakmi.",
|
||||||
|
"channels_cyr2latSettingsHeading": "Nastavenia Cyr2Lat",
|
||||||
|
"channels_cyr2latSettingsSubheading": "Zoznam nahradení",
|
||||||
|
"channels_cyr2latSettingsDscr": "Upravte konfiguráciu JSON pre nahradenie znakov",
|
||||||
|
"channels_cyr2latSettingsDialogHint": "JSON mapa nahradení",
|
||||||
|
"channels_cyr2latSettingsDialogWrongJSON": "Nesprávny JSON: {error}",
|
||||||
|
"settings_cyr2latProfileAdd": "Pridať profil Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileName": "Názov profilu",
|
||||||
|
"settings_cyr2latProfileNameEmpty": "Názov profilu nesmie byť prázdny",
|
||||||
|
"settings_cyr2latProfileAdded": "Profil bol úspešne pridaný",
|
||||||
|
"settings_cyr2latProfileUpdated": "Profil bol úspešne aktualizovaný",
|
||||||
|
"settings_cyr2latProfileEdit": "Upraviť profil Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileDelete": "Odstrániť profil Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileDeleted": "Profil bol úspešne odstránený",
|
||||||
|
"settings_cyr2latProfileDeleteDscr": "Naozaj chcete odstrániť profil \"{name}\"?",
|
||||||
"channels_channelUpdated": "Kanál \"{name}\" bol aktualizovaný",
|
"channels_channelUpdated": "Kanál \"{name}\" bol aktualizovaný",
|
||||||
"@channels_channelUpdated": {
|
"@channels_channelUpdated": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -398,7 +414,7 @@
|
|||||||
"channels_publicChannelAdded": "Veľký kanál pridaný",
|
"channels_publicChannelAdded": "Veľký kanál pridaný",
|
||||||
"channels_sortBy": "Triediť podľa",
|
"channels_sortBy": "Triediť podľa",
|
||||||
"channels_sortManual": "Ručne",
|
"channels_sortManual": "Ručne",
|
||||||
"channels_sortAZ": "A-Z",
|
"channels_sortAZ": "Od A po Z",
|
||||||
"channels_sortLatestMessages": "Posledné správy",
|
"channels_sortLatestMessages": "Posledné správy",
|
||||||
"channels_sortUnread": "Nezriadené",
|
"channels_sortUnread": "Nezriadené",
|
||||||
"chat_noMessages": "Zatiaľ žiadne správy.",
|
"chat_noMessages": "Zatiaľ žiadne správy.",
|
||||||
@@ -476,7 +492,7 @@
|
|||||||
"debugLog_noEntries": "Zatiaľ neboli zaznamenané žiadne debug logy.",
|
"debugLog_noEntries": "Zatiaľ neboli zaznamenané žiadne debug logy.",
|
||||||
"debugLog_enableInSettings": "Povolte ladicové logy v nastaveniach",
|
"debugLog_enableInSettings": "Povolte ladicové logy v nastaveniach",
|
||||||
"debugLog_frames": "Rámce",
|
"debugLog_frames": "Rámce",
|
||||||
"debugLog_rawLogRx": "Raw Log-RX",
|
"debugLog_rawLogRx": "Čistý log – RX",
|
||||||
"debugLog_noBleActivity": "Zatiaľ žiadna aktivita BLE.",
|
"debugLog_noBleActivity": "Zatiaľ žiadna aktivita BLE.",
|
||||||
"debugFrame_length": "Dĺžka rámca: {count} bajtov",
|
"debugFrame_length": "Dĺžka rámca: {count} bajtov",
|
||||||
"@debugFrame_length": {
|
"@debugFrame_length": {
|
||||||
@@ -530,7 +546,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debugFrame_textTypeCli": "CLI",
|
"debugFrame_textTypeCli": "CLI (Command Line Interface)",
|
||||||
"debugFrame_textTypePlain": "Jednoduché",
|
"debugFrame_textTypePlain": "Jednoduché",
|
||||||
"debugFrame_text": "- Text: \"{text}\"",
|
"debugFrame_text": "- Text: \"{text}\"",
|
||||||
"@debugFrame_text": {
|
"@debugFrame_text": {
|
||||||
@@ -540,7 +556,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debugFrame_hexDump": "Hex Dump:",
|
"debugFrame_hexDump": "Hexová analýza:",
|
||||||
"chat_pathManagement": "Správa ciest",
|
"chat_pathManagement": "Správa ciest",
|
||||||
"chat_routingMode": "Režim trasy",
|
"chat_routingMode": "Režim trasy",
|
||||||
"chat_autoUseSavedPath": "Použiť uloženú cestu",
|
"chat_autoUseSavedPath": "Použiť uloženú cestu",
|
||||||
@@ -549,7 +565,7 @@
|
|||||||
"chat_pathHistoryFull": "História ciest je plná. Odstráňte záznamy, aby ste mohli pridať nové.",
|
"chat_pathHistoryFull": "História ciest je plná. Odstráňte záznamy, aby ste mohli pridať nové.",
|
||||||
"chat_hopSingular": "Skok",
|
"chat_hopSingular": "Skok",
|
||||||
"chat_hopPlural": "Skákať",
|
"chat_hopPlural": "Skákať",
|
||||||
"chat_hopsCount": "{count} {count, plural, =1{hop} other{hops}}",
|
"chat_hopsCount": "{count} {count, plural, =1{skok} other{skoky}}",
|
||||||
"@chat_hopsCount": {
|
"@chat_hopsCount": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {
|
"count": {
|
||||||
@@ -878,11 +894,11 @@
|
|||||||
"path_setPath": "Nastaviť cestu",
|
"path_setPath": "Nastaviť cestu",
|
||||||
"repeater_management": "Správa opakérov",
|
"repeater_management": "Správa opakérov",
|
||||||
"repeater_managementTools": "Nástroje na správu",
|
"repeater_managementTools": "Nástroje na správu",
|
||||||
"repeater_status": "Status",
|
"repeater_status": "Stav",
|
||||||
"repeater_statusSubtitle": "Zobraziť stav, štatistiky a susedov repeatera",
|
"repeater_statusSubtitle": "Zobraziť stav, štatistiky a susedov repeatera",
|
||||||
"repeater_telemetry": "Telemetria",
|
"repeater_telemetry": "Telemetria",
|
||||||
"repeater_telemetrySubtitle": "Zobraziť telemetriu senzorov a systémových štatistík",
|
"repeater_telemetrySubtitle": "Zobraziť telemetriu senzorov a systémových štatistík",
|
||||||
"repeater_cli": "CLI",
|
"repeater_cli": "CLI (Command Line Interface)",
|
||||||
"repeater_cliSubtitle": "Pošlite príkazy opakovaču",
|
"repeater_cliSubtitle": "Pošlite príkazy opakovaču",
|
||||||
"repeater_settings": "Nastavenia",
|
"repeater_settings": "Nastavenia",
|
||||||
"repeater_settingsSubtitle": "Konfigurujte parametre opakovača",
|
"repeater_settingsSubtitle": "Konfigurujte parametre opakovača",
|
||||||
@@ -991,7 +1007,7 @@
|
|||||||
"repeater_guestPasswordHelper": "Prístupový heslo iba na čítanie",
|
"repeater_guestPasswordHelper": "Prístupový heslo iba na čítanie",
|
||||||
"repeater_radioSettings": "Nastavenia rádia",
|
"repeater_radioSettings": "Nastavenia rádia",
|
||||||
"repeater_frequencyMhz": "Frekvencia (MHz)",
|
"repeater_frequencyMhz": "Frekvencia (MHz)",
|
||||||
"repeater_frequencyHelper": "300-2500 MHz",
|
"repeater_frequencyHelper": "300–2500 MHz",
|
||||||
"repeater_txPower": "TX Power",
|
"repeater_txPower": "TX Power",
|
||||||
"repeater_txPowerHelper": "1-30 dBm",
|
"repeater_txPowerHelper": "1-30 dBm",
|
||||||
"repeater_bandwidth": "Šírka pásma",
|
"repeater_bandwidth": "Šírka pásma",
|
||||||
@@ -1058,6 +1074,81 @@
|
|||||||
},
|
},
|
||||||
"repeater_confirm": "Potvrdiť",
|
"repeater_confirm": "Potvrdiť",
|
||||||
"repeater_settingsSaved": "Nastavenia boli uložené úspešne.",
|
"repeater_settingsSaved": "Nastavenia boli uložené úspešne.",
|
||||||
|
"repeater_rxGain": "Zvýšený zisk RX",
|
||||||
|
"repeater_rxGainHelper": "Vyššia citlivosť, vyšší príkon (platí len pre modely SX1262/SX1268)",
|
||||||
|
"repeater_refreshRxGain": "Obnovte zvýšený zisk z RX",
|
||||||
|
"repeater_multiAcks": "Víťazné potvrdenia (víťazné ACK)",
|
||||||
|
"repeater_multiAcksSubtitle": "Potvrďte správy prostredníctvom viacerých trás pre lepšiu doručenie.",
|
||||||
|
"repeater_refreshMultiAcks": "Opätovne potvrďte viacero ACK signálov",
|
||||||
|
"repeater_networkHealth": "Zdravie siete",
|
||||||
|
"repeater_loopDetect": "Detekcia slučiek",
|
||||||
|
"repeater_loopDetectHelper": "Vytvorte balíčky, ktoré vizuálne pripomínajú slučky v síti.",
|
||||||
|
"repeater_loopDetectOff": "Vypnuté",
|
||||||
|
"repeater_loopDetectMinimal": "Minimálny",
|
||||||
|
"repeater_loopDetectModerate": "Stredný, mierny",
|
||||||
|
"repeater_loopDetectStrict": "Prísne",
|
||||||
|
"repeater_dutyCycle": "Cyklus činnosti",
|
||||||
|
"repeater_dutyCycleHelper": "Maximálna percentáľ dostupného času vysielania",
|
||||||
|
"repeater_dutyCyclePercent": "{percent}%",
|
||||||
|
"@repeater_dutyCyclePercent": {
|
||||||
|
"placeholders": {
|
||||||
|
"percent": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_ownerInfo": "Informácie o poskytovateľovi",
|
||||||
|
"repeater_ownerInfoHelper": "Veľké dátové informácie pre tento vysielací zdroj",
|
||||||
|
"repeater_refreshOwnerInfo": "Zísť informácie o operátore",
|
||||||
|
"repeater_floodMax": "Maximálny počet skokov pri povodni",
|
||||||
|
"repeater_floodMaxHelper": "Maximálny počet paketov, ktoré môžu preletieť cez jeden hop (0-64)",
|
||||||
|
"repeater_advancedSettings": "Pokročilé",
|
||||||
|
"repeater_advancedSettingsSubtitle": "Ovládacie knopy pre skúsených operátorov",
|
||||||
|
"repeater_pathHashMode": "Režim hashovania cesty",
|
||||||
|
"repeater_pathHashModeHelper": "Byty použité na zakódovanie ID tohto opakovača v tagoch pre trasu/detekciu slučky. 0 = 1 bytu (256 ID, až 64 skokov), 1 = 2 byty (65 000 ID, až 32 skokov), 2 = 3 byty (16 miliónov ID, až 21 skokov). Verzie 1.13 a staršie nepodporujú viacbytové trasy – fungujú len, keď je sieť aktivovaná.",
|
||||||
|
"repeater_txDelay": "Zpoždanie v Flood, TX",
|
||||||
|
"repeater_txDelayHelper": "Nastavenie pre opakované vysielanie pre dopravu počas povodní, ako násobok času, ktorý paket využije (0-2, výchoce hodnota 0,5). Vyššia hodnota znamená menej kolízii, ale pomalšie doručovanie.",
|
||||||
|
"repeater_directTxDelay": "Priame oneskorenie TX",
|
||||||
|
"repeater_directTxDelayHelper": "Nastavenie pre retransmisiu pre priame (nie pre plnú sieť), ako násobok času prenosu paketov (0-2, výchoce 0,3).",
|
||||||
|
"repeater_intThresh": "Hranica, pri ktorej dochádza k rušeniu",
|
||||||
|
"repeater_intThreshHelper": "Hranica je nastavená tak, aby odfiltrovala šum nad touto úrovňou. Hodnota 0 znamená, že sa nebude nič odfiltrovať – nastavte ju len v prípade, že zaznamenáte chyby pri prijímaní signálu v šumnej frekvencii.",
|
||||||
|
"repeater_agcResetInterval": "Interval reštartu AGC",
|
||||||
|
"repeater_agcResetIntervalHelper": "Ako často by ste mali reštartovať automatické ovládanie zosilnenia, aby ste sa vrátili do normálneho stavu, ak je zosilnenie zablokované? Nastavenie „4.0“ vypne pravidelné reštarty.",
|
||||||
|
"repeater_actionsTitle": "Opatrenia",
|
||||||
|
"repeater_sendAdvert": "Odoslať inzerát o povodňovej situácii",
|
||||||
|
"repeater_sendAdvertSubtitle": "Zverejnite reklamu na povodňu prostredníctvom siete.",
|
||||||
|
"repeater_sendAdvertZeroHop": "Odoslať reklamu bez prenosu",
|
||||||
|
"repeater_sendAdvertZeroHopSubtitle": "Zverejnite reklamnú správu, ktorá sa prenáša len raz (bez prenosov).",
|
||||||
|
"repeater_clockSync": "Synchronizujte hodiny teraz",
|
||||||
|
"repeater_clockSyncSubtitle": "Nastavte čas na vašom telefóne, aby odpovedal na volania z vysielacieho zariadenia.",
|
||||||
|
"repeater_actionSucceeded": "{action} succeeded",
|
||||||
|
"@repeater_actionSucceeded": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_actionFailed": "{action} failed: {error}",
|
||||||
|
"@repeater_actionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_settingsSavedRebootNeeded": "Nastavenia uložené – reštartujte vysielací prístroj, aby sa nastavenia aplikovali.",
|
||||||
|
"repeater_settingsPartialFailure": "Niektoré nastavenia neúspešné: {failures}",
|
||||||
|
"@repeater_settingsPartialFailure": {
|
||||||
|
"placeholders": {
|
||||||
|
"failures": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repeater_errorSavingSettings": "Chyba pri ukladaní nastavení: {error}",
|
"repeater_errorSavingSettings": "Chyba pri ukladaní nastavení: {error}",
|
||||||
"@repeater_errorSavingSettings": {
|
"@repeater_errorSavingSettings": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1069,11 +1160,9 @@
|
|||||||
"repeater_refreshBasicSettings": "Obnoviť základné nastavenia",
|
"repeater_refreshBasicSettings": "Obnoviť základné nastavenia",
|
||||||
"repeater_refreshRadioSettings": "Obnoviť Nastavenia Rádií",
|
"repeater_refreshRadioSettings": "Obnoviť Nastavenia Rádií",
|
||||||
"repeater_refreshTxPower": "Obnoviť TX napájanie",
|
"repeater_refreshTxPower": "Obnoviť TX napájanie",
|
||||||
"repeater_refreshLocationSettings": "Obnoviť Nastavenia Miesta",
|
|
||||||
"repeater_refreshPacketForwarding": "Obnoviť smerovanie paketov",
|
"repeater_refreshPacketForwarding": "Obnoviť smerovanie paketov",
|
||||||
"repeater_refreshGuestAccess": "Obnoviť prístup hosťa",
|
"repeater_refreshGuestAccess": "Obnoviť prístup hosťa",
|
||||||
"repeater_refreshPrivacyMode": "Obnoviť Ochranný režim",
|
"repeater_refreshPrivacyMode": "Obnoviť Ochranný režim",
|
||||||
"repeater_refreshAdvertisementSettings": "Obnoviť nastavenia reklamy",
|
|
||||||
"repeater_refreshed": "{label} sa znova načítalo",
|
"repeater_refreshed": "{label} sa znova načítalo",
|
||||||
"@repeater_refreshed": {
|
"@repeater_refreshed": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1346,7 +1435,7 @@
|
|||||||
"listFilter_sortBy": "Triediť podľa",
|
"listFilter_sortBy": "Triediť podľa",
|
||||||
"listFilter_latestMessages": "Posledné správy",
|
"listFilter_latestMessages": "Posledné správy",
|
||||||
"listFilter_heardRecently": "Nedávno počuli.",
|
"listFilter_heardRecently": "Nedávno počuli.",
|
||||||
"listFilter_az": "A-Z",
|
"listFilter_az": "Od A po Z",
|
||||||
"listFilter_filters": "Filtre",
|
"listFilter_filters": "Filtre",
|
||||||
"listFilter_all": "Všetko",
|
"listFilter_all": "Všetko",
|
||||||
"listFilter_users": "Používatelia",
|
"listFilter_users": "Používatelia",
|
||||||
@@ -1618,8 +1707,8 @@
|
|||||||
"appSettings_unitsTitle": "Jednotky",
|
"appSettings_unitsTitle": "Jednotky",
|
||||||
"appSettings_unitsMetric": "Metrické (m / km)",
|
"appSettings_unitsMetric": "Metrické (m / km)",
|
||||||
"appSettings_unitsImperial": "Imperiálne (ft / mi)",
|
"appSettings_unitsImperial": "Imperiálne (ft / mi)",
|
||||||
"map_lineOfSight": "Line of Sight",
|
"map_lineOfSight": "Úroveň výhľadu",
|
||||||
"map_losScreenTitle": "Line of Sight",
|
"map_losScreenTitle": "Úroveň výhľadu",
|
||||||
"losSelectStartEnd": "Vyberte počiatočný a koncový uzol pre LOS.",
|
"losSelectStartEnd": "Vyberte počiatočný a koncový uzol pre LOS.",
|
||||||
"losRunFailed": "Kontrola priamej viditeľnosti zlyhala: {error}",
|
"losRunFailed": "Kontrola priamej viditeľnosti zlyhala: {error}",
|
||||||
"@losRunFailed": {
|
"@losRunFailed": {
|
||||||
@@ -1859,5 +1948,372 @@
|
|||||||
"usbConnectionFailed": "Neúspešné pripojenie cez USB: {error}",
|
"usbConnectionFailed": "Neúspešné pripojenie cez USB: {error}",
|
||||||
"usbStatus_notConnected": "Vyberte USB zariadenie",
|
"usbStatus_notConnected": "Vyberte USB zariadenie",
|
||||||
"usbStatus_connecting": "Pripojenie k USB zariadeniu...",
|
"usbStatus_connecting": "Pripojenie k USB zariadeniu...",
|
||||||
"usbErrorConnectTimedOut": "Pripojenie nebolo úspešné. Uistite sa, že zariadenie má nainštalovaný firmware USB Companion."
|
"usbErrorConnectTimedOut": "Pripojenie nebolo úspešné. Uistite sa, že zariadenie má nainštalovaný firmware USB Companion.",
|
||||||
|
"@tcpStatus_connectingTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"endpoint": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tcpConnectionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tcpHostHint": "192.168.40.10",
|
||||||
|
"tcpHostLabel": "IP adresa",
|
||||||
|
"tcpScreenTitle": "Spojte sa pomocou protokolu TCP",
|
||||||
|
"connectionChoiceTcpLabel": "TCP",
|
||||||
|
"tcpPortLabel": "Prístav",
|
||||||
|
"tcpPortHint": "5 000",
|
||||||
|
"tcpStatus_notConnected": "Zadajte cieľovú adresu a pripojte sa.",
|
||||||
|
"tcpStatus_connectingTo": "Pripojenie k {endpoint}...",
|
||||||
|
"tcpErrorHostRequired": "Je potrebné zadať IP adresu.",
|
||||||
|
"tcpErrorPortInvalid": "Číslo portu musí byť medzi 1 a 65535.",
|
||||||
|
"tcpErrorUnsupported": "Prevoz prostredníctvom protokolu TCP nie je na tejto platforme podporovaný.",
|
||||||
|
"tcpErrorTimedOut": "Pripojenie TCP vypršalo.",
|
||||||
|
"tcpConnectionFailed": "Neúspešné vytvorenie TCP spojenia: {error}",
|
||||||
|
"map_showDiscoveryContacts": "Zobraziť kontakty objavov",
|
||||||
|
"map_setAsMyLocation": "Nastavte ako moju polohu",
|
||||||
|
"@path_routeWeight": {
|
||||||
|
"placeholders": {
|
||||||
|
"weight": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings_privacy": "Nastavenia súkromia",
|
||||||
|
"settings_privacySubtitle": "Ovládni, aké informácie sa zdieľajú.",
|
||||||
|
"settings_telemetryLocationMode": "Režim umiestnenia telemetrie",
|
||||||
|
"settings_telemetryBaseMode": "Základný režim telemetrie",
|
||||||
|
"settings_advertLocation": "Umiestnenie inzerátu",
|
||||||
|
"settings_telemetryEnvironmentMode": "Režim prostredia telemetrie",
|
||||||
|
"settings_advertLocationSubtitle": "Zahrnúť polohu do inzerátu",
|
||||||
|
"settings_allowAll": "Povoliť všetko",
|
||||||
|
"settings_privacySettingsDescription": "Vyberte, ktoré informácie váš zariadenie zdieľa s ostatnými.",
|
||||||
|
"settings_denyAll": "Zamietnuť všetko",
|
||||||
|
"settings_allowByContact": "Povoliť podľa kontaktových vlajok",
|
||||||
|
"contact_info": "Kontaktné informácie",
|
||||||
|
"contact_settings": "Nastavenia kontaktov",
|
||||||
|
"contact_teleBaseSubtitle": "Povoliť zdieľanie úrovne batérie a základnej telemetrie",
|
||||||
|
"contact_teleLoc": "Lokácia telemetrie",
|
||||||
|
"contact_teleLocSubtitle": "Povoliť zdieľanie údajov o lokalite",
|
||||||
|
"contact_teleEnv": "Prostredie telemetrie",
|
||||||
|
"contact_telemetry": "Telemetria",
|
||||||
|
"contact_clearChat": "Vymazať chat",
|
||||||
|
"contact_lastSeen": "Naposledy videný",
|
||||||
|
"contact_teleBase": "Báza telemetrie",
|
||||||
|
"contact_teleEnvSubtitle": "Povoliť zdieľanie údajov senzorov prostredia",
|
||||||
|
"appSettings_maxRouteWeightSubtitle": "Maximálna hmotnosť, ktorú môže trás prenášať vďaka úspešným zásielkam.",
|
||||||
|
"appSettings_initialRouteWeightSubtitle": "Počiatočná váha pre nové, objavené cesty",
|
||||||
|
"appSettings_initialRouteWeight": "Počiatočná váha trasy",
|
||||||
|
"appSettings_maxRouteWeight": "Maximálna hmotnosť trasy",
|
||||||
|
"appSettings_routeWeightSuccessIncrement": "Zvyšenie váhy úspechu",
|
||||||
|
"appSettings_routeWeightSuccessIncrementSubtitle": "Hmotnosť pridaná k trase po úspešnej doručení",
|
||||||
|
"appSettings_routeWeightFailureDecrement": "Sníženie váhy, ktorá sa používa na odhad rizika.",
|
||||||
|
"appSettings_routeWeightFailureDecrementSubtitle": "Hmotnosť odstránená z cesty po neúspešnej doručenie",
|
||||||
|
"appSettings_maxMessageRetries": "Maximalný počet pokusov o doručenie správ",
|
||||||
|
"appSettings_maxMessageRetriesSubtitle": "Počet pokusov o odošleť pred označením správy ako neúspešnej",
|
||||||
|
"path_routeWeight": "{weight}/{max}",
|
||||||
|
"settings_telemetryModeUpdated": "Režim telemetrie bol aktualizovaný",
|
||||||
|
"settings_multiAck": "Viaceré ACK",
|
||||||
|
"map_showOverlaps": "Prekrývanie opakovača kľúča",
|
||||||
|
"map_runTraceWithReturnPath": "Vráťte sa späť po tej istej ceste.",
|
||||||
|
"@radioStats_noiseFloor": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastRssi": {
|
||||||
|
"placeholders": {
|
||||||
|
"rssiDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastSnr": {
|
||||||
|
"placeholders": {
|
||||||
|
"snr": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_txAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_rxAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_stripNoise": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chat_sendCooldown": "Prosím, počkajte chvíľu, než zašlete znova.",
|
||||||
|
"appSettings_jumpToOldestUnread": "Presk oceň",
|
||||||
|
"appSettings_jumpToOldestUnreadSubtitle": "Pri otvorení chatu s neprečítanými správami, prejdite do prvého neprečítaného, namiesto poslednej.",
|
||||||
|
"appSettings_languageHu": "Maďarský",
|
||||||
|
"appSettings_languageJa": "Japonský",
|
||||||
|
"appSettings_languageKo": "Kórejský",
|
||||||
|
"radioStats_tooltip": "Statistiky rádiových a sieťových kanálov",
|
||||||
|
"radioStats_screenTitle": "Štatistiky rádiových vysielaní",
|
||||||
|
"radioStats_notConnected": "Pripojte sa k zariadeniu, aby ste mohli sledovať štatistiky rádiového vysielania.",
|
||||||
|
"radioStats_firmwareTooOld": "Statistické údaje z rádia vyžadujú sprievodný softvér verzie v8 alebo novšej.",
|
||||||
|
"radioStats_waiting": "Čakám na údaje…",
|
||||||
|
"radioStats_noiseFloor": "Úroveň hluku: {noiseDbm} dBm",
|
||||||
|
"radioStats_lastRssi": "Posledný údaj RSSI: {rssiDbm} dBm",
|
||||||
|
"radioStats_lastSnr": "Posledná hodnota SNR: {snr} dB",
|
||||||
|
"radioStats_txAir": "Čas vysielania na TX (celkový): {seconds} s",
|
||||||
|
"radioStats_rxAir": "Čas RX (celkový): {seconds} s",
|
||||||
|
"radioStats_chartCaption": "Úroveň šumu (dBm) pre posledné vzorky.",
|
||||||
|
"radioStats_stripNoise": "Úroveň hluku: {noiseDbm} dBm",
|
||||||
|
"radioStats_stripWaiting": "Získavanie údajov o rádiu…",
|
||||||
|
"radioStats_settingsTile": "Štatistiky rádiových vysielaní",
|
||||||
|
"radioStats_settingsSubtitle": "Úroveň hluku, RSSI, SNR a časové rozloženie",
|
||||||
|
"@translation_downloadFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_enableSubtitle": "Prekladajte prichádzajúce správy a umožnite ich preklad pred odoslaním.",
|
||||||
|
"translation_enableTitle": "Aktivovať preklad",
|
||||||
|
"translation_composerTitle": "Preložte pred odeslaním",
|
||||||
|
"translation_title": "Preklad",
|
||||||
|
"translation_composerSubtitle": "Riadi výchoce stav ikony pre preklad, ktorú používa program.",
|
||||||
|
"translation_targetLanguage": "Cieľový jazyk",
|
||||||
|
"translation_useAppLanguage": "Použite jazyk aplikácie",
|
||||||
|
"translation_downloadedModelLabel": "Stiahnutý model",
|
||||||
|
"translation_presetModelLabel": "Prednastavený model od Hugging Face",
|
||||||
|
"translation_manualUrlLabel": "Odkaz na manuál (v elektronickej forme)",
|
||||||
|
"translation_downloadModel": "Stiahnuť model",
|
||||||
|
"translation_downloading": "Stiahnutie...",
|
||||||
|
"translation_working": "Práca...",
|
||||||
|
"translation_stop": "Zastavte",
|
||||||
|
"translation_mergingChunks": "Sliečenie stiahnutých častí do konečného súboru...",
|
||||||
|
"translation_downloadedModels": "Stiahnuté modely",
|
||||||
|
"translation_deleteModel": "Odstrániť model",
|
||||||
|
"translation_modelDownloaded": "Model pre preklad bol stiahnutý.",
|
||||||
|
"translation_downloadStopped": "Stiahnutie bolo prerušené.",
|
||||||
|
"translation_downloadFailed": "Neúspešné stiahnutie: {error}",
|
||||||
|
"translation_enterUrlFirst": "Najprv zadajte URL pre konkrétny model.",
|
||||||
|
"@scanner_linuxPairingPinPrompt": {
|
||||||
|
"placeholders": {
|
||||||
|
"deviceName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scanner_linuxPairingHidePin": "Skryť PIN",
|
||||||
|
"scanner_linuxPairingShowPin": "Zobraziť PIN",
|
||||||
|
"scanner_linuxPairingPinTitle": "PIN pre párovanie cez Bluetooth",
|
||||||
|
"scanner_linuxPairingPinPrompt": "Zadajte PIN pre {deviceName} (ak neexistuje, nechajte prázdne).",
|
||||||
|
"@translation_translateTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"language": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_composerDisabledHint": "Posielajte správy v pôvodnej písanom jazyku.",
|
||||||
|
"translation_composerEnabledHint": "Správy budú preložené, než budú odoslané.",
|
||||||
|
"translation_translateBeforeSending": "Preložte pred odeslaním",
|
||||||
|
"translation_messageTranslation": "Preklad textu",
|
||||||
|
"translation_translateTo": "Preložte do {language}",
|
||||||
|
"translation_translationOptions": "Možnosti prekladania",
|
||||||
|
"translation_systemLanguage": "Jazyk systému",
|
||||||
|
"repeater_cliQuickClockSync": "Synchronizácia hodin",
|
||||||
|
"repeater_cliQuickDiscovery": "Objaviť susedov",
|
||||||
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLogin": "Synchronizácia hodiniek po prihlávení",
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Automaticky posielajte notifikáciu \"synchronizácia času\" po úspešnom prihládení.",
|
||||||
|
"chat_sendMessage": "Odoslať správu",
|
||||||
|
"repeater_guest": "Informácie o opakovači",
|
||||||
|
"room_guest": "Informácie o serveri",
|
||||||
|
"repeater_guestTools": "Nástroje pre hostí",
|
||||||
|
"repeater_getCategory": "Zísť hodnoty",
|
||||||
|
"repeater_powerMgmt": "Správa energie",
|
||||||
|
"repeater_sensors": "Senzory",
|
||||||
|
"repeater_cliHelpPowerOff": "Vypína zariadenie. (neočakáva sa žiadna odpoveď)",
|
||||||
|
"repeater_cliHelpClkReboot": "Resetuje hodiny na známu epochu a reštartuje zariadenie.",
|
||||||
|
"repeater_cliHelpAdvertZeroHop": "Rozosiela reklamu, ktorá sa prenáša len medzi susednými zariadeniami (bez prenosu cez iné siete).",
|
||||||
|
"repeater_cliHelpStartOta": "Spustí aktualizáciu firmvéru prostredníctvom diaľkového prenosu na podporovaných doskách.",
|
||||||
|
"repeater_cliHelpTime": "Nastavuje časovník zariadenia na zadané sekundy od Unix epochy. Časovník sa nedá otáčať dozadu.",
|
||||||
|
"repeater_cliHelpBoard": "Zobrazuje informácie o výrobcom dosky / identifikátor hardvéru.",
|
||||||
|
"repeater_cliHelpDiscoverNeighbors": "Odosiela požiadavku na nájdenie susedných uzlov. (Len pre opakovače)",
|
||||||
|
"repeater_cliHelpPowersaving": "Ukazuje, či je režim úspory energie zapnutý alebo vypnutý.",
|
||||||
|
"repeater_cliHelpPowersavingOnOff": "Umožňuje alebo vypína režim úspory energie (ak je podporovaný).",
|
||||||
|
"repeater_cliHelpErase": "(Používa sa len pre sériové zariadenia) Formátuje systém súborov zariadenia. Vymaže všetky nastavenia a kontakty.",
|
||||||
|
"repeater_cliHelpSetDutyCycle": "Nastavuje maximálnu povolenú frekvenciu prenosu ako percento (1-100). Internálne upravuje faktor času prenosu.",
|
||||||
|
"repeater_cliHelpSetPrvKey": "(Používa sa len v sériovej verzii) Nahradí privátny kľúč, ktorý identifikuje zariadenie. Po aplikácii je potrebné zariadenie reštartovať. Generuje nový verejný kľúč.",
|
||||||
|
"repeater_cliHelpSetRadioRxGain": "(iba pre SX126x) Zapína zvýšený zisk prijímania pre zlepšenie citlivosti pri vyššom príkonu.",
|
||||||
|
"repeater_cliHelpSetOwnerInfo": "Definuje reťazec s informáciami o kontaktnom osobě, ktorý je zahrnutý v reklamách. Používajte '|' pre nové riadky.",
|
||||||
|
"repeater_cliHelpSetPathHashMode": "Nastavuje režim hashovania cesty. 0 = starý režim, 1 = štandardný režim, 2 = striktný režim. Ovplyvňuje, ako sa prekladajú trasy.",
|
||||||
|
"repeater_cliHelpSetLoopDetect": "Nastavuje citlivosť detekcie slučky routovania: vypnutá, minimálna, stredná alebo prísna.",
|
||||||
|
"repeater_cliHelpSetFreq": "(Používa sa len v sériovej verzii) Rýchlo nastavuje len frekvenciu. Je potrebné reštartovať. Pre úplné nastavenie rádia preferujte funkciu \"nastavenie rádia\".",
|
||||||
|
"repeater_cliHelpSetBridgeChannel": "(Používa sa len pre ESPNow most) Nastavuje WiFi kanál (1-14), ktorý používa most.",
|
||||||
|
"repeater_cliHelpGetName": "Zobrazuje zadané meno uzla.",
|
||||||
|
"repeater_cliHelpGetRole": "Ukazuje funkciu firmvéru (opakovač, server pre miestnosť atď.).",
|
||||||
|
"repeater_cliHelpGetPublicKey": "Zobrazuje verejný kľúč zariadenia.",
|
||||||
|
"repeater_cliHelpGetPrvKey": "(Používa sa len v sériových aplikáciách) Zobrazuje súkromný kľúč zariadenia. Zotriďte ho ako tajný údaj.",
|
||||||
|
"repeater_cliHelpGetRepeat": "Ukazuje, či je funkcia preposielania paketov (funkcia opakéra) zapnutá alebo vypnutá.",
|
||||||
|
"repeater_cliHelpGetTx": "Zobrazuje aktuálnu výkonovú hodnotu TX v dBm.",
|
||||||
|
"repeater_cliHelpGetFreq": "Zobrazuje nakonfigurovanú frekvenciu v MHz.",
|
||||||
|
"repeater_cliHelpGetRadio": "Zobrazuje všetky parametre rádiového signálu: frekvencia, šírka pásma, faktor rozširovania, rýchlosť kódovania.",
|
||||||
|
"repeater_cliHelpGetRadioRxGain": "(iba pre SX126x) Zobrazuje stav zosilnenia prijímača RX.",
|
||||||
|
"repeater_cliHelpGetAf": "Zobrazuje aktuálny koeficient času vysielania.",
|
||||||
|
"repeater_cliHelpGetDutyCycle": "Zobrazuje aktuálnu povolenú frekvenciu ako percentáž.",
|
||||||
|
"repeater_cliHelpGetIntThresh": "Zobrazuje hranicu pre prechodové signály v dB.",
|
||||||
|
"repeater_cliHelpGetAgcResetInterval": "Zobrazuje interval reštartovania AGC v sekundách.",
|
||||||
|
"repeater_cliHelpGetMultiAcks": "Ukazuje, či je režim dvojité potvrdenie zapnutý (1) alebo vypnutý (0).",
|
||||||
|
"repeater_cliHelpGetAllowReadOnly": "Ukazuje, či je povolená len čítacia funkcia pre hostí.",
|
||||||
|
"repeater_cliHelpGetAdvertInterval": "Zobrazuje čas trvania miestnej reklamnej pauzy v minútach.",
|
||||||
|
"repeater_cliHelpGetFloodAdvertInterval": "Zobrazuje časový interval reklamy počas záplavy v hodinách.",
|
||||||
|
"repeater_cliHelpGetGuestPassword": "Zobrazuje nastavené heslo pre hosta.",
|
||||||
|
"repeater_cliHelpGetLat": "Zobrazuje nastavenú šírku.",
|
||||||
|
"repeater_cliHelpGetLon": "Zobrazuje nastavenú dĺžku.",
|
||||||
|
"repeater_cliHelpGetRxDelay": "Zobrazuje základnú hodnotu rxdelay.",
|
||||||
|
"repeater_cliHelpGetTxDelay": "Ukazuje faktor zpoždenia pre režim povodňovej komunikácie.",
|
||||||
|
"repeater_cliHelpGetDirectTxDelay": "Zobrazuje faktor zloženia pri priamej modulácii.",
|
||||||
|
"repeater_cliHelpGetFloodMax": "Zobrazuje maximálny počet opakovaní povodňového stavu.",
|
||||||
|
"repeater_cliHelpGetOwnerInfo": "Zobrazuje reťazec s kontaktnými údajmi vlastníka.",
|
||||||
|
"repeater_cliHelpGetPathHashMode": "Zobrazuje režim hashovania cesty (0/1/2).",
|
||||||
|
"repeater_cliHelpGetLoopDetect": "Ukazuje citlivosť na detekciu slučiek.",
|
||||||
|
"repeater_cliHelpGetAcl": "(Používa sa len v sériovej konfigurácii) Zobrazuje prístupové pravidlá na opakovači.",
|
||||||
|
"repeater_cliHelpGetBridgeEnabled": "Ukazuje, či je most povolený.",
|
||||||
|
"repeater_cliHelpGetBridgeDelay": "Zobrazuje čas strávený prechodom mosta v milisekundách.",
|
||||||
|
"repeater_cliHelpGetBridgeSource": "Ukazuje, či most prijíma alebo vysiela RX alebo TX balíky.",
|
||||||
|
"repeater_cliHelpGetBridgeBaud": "(iba pre rozhranie RS232) Zobrazuje rýchlosť prenosu dát na rozhraní RS232.",
|
||||||
|
"repeater_cliHelpGetBridgeChannel": "(Používa sa len pre ESPNow) Zobrazuje WiFi kanál mosta.",
|
||||||
|
"repeater_cliHelpGetBridgeSecret": "(Používa sa len pre ESPNow most) Zobrazuje spoločný tajný kľúč mosta.",
|
||||||
|
"repeater_cliHelpGetBootloaderVer": "(iba pre NRF52) Zobrazuje verziu bootloaderu.",
|
||||||
|
"repeater_cliHelpGetAdcMultiplier": "Zobrazuje násobič ADC (škálovanie napätia batérie).",
|
||||||
|
"repeater_cliHelpGetPwrMgtSupport": "Označuje, či riadiace orgány majú podporu pre správu energie.",
|
||||||
|
"repeater_cliHelpGetPwrMgtSource": "Ukazuje aktuálny zdroj napájania: externý alebo batéria.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootReason": "Zobrazuje najaktuálnejšie dôvody pre reštart a vypnutie.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootMv": "Zobrazuje napätie batérie pri spustení systému v milivoltov (mV).",
|
||||||
|
"repeater_cliHelpSensorGet": "Číta hodnotu nastavenia pre špecifický senzor pomocou klávesového vstupu.",
|
||||||
|
"repeater_cliHelpSensorSet": "Vytvára vlastné nastavenie pre senzor.",
|
||||||
|
"repeater_cliHelpSensorList": "Zobrazuje všetky nastavenia pre špecifické senzory, zoradené podľa voliteľného indexu začiatku.",
|
||||||
|
"repeater_cliHelpRegionDefault": "Zobrazuje aktuálnu rozsiahku, ktorá je nastavená ako výchozí.",
|
||||||
|
"repeater_cliHelpRegionDefaultSet": "Nastavuje výchoce rozsiahku regiónu. Použite \"<null>\", aby ju vymazal.",
|
||||||
|
"repeater_cliHelpRegionListAllowed": "Zoznam oblastí, ktoré umožňujú premávku počas povodní.",
|
||||||
|
"repeater_cliHelpRegionListDenied": "Zoznam oblastí, ktoré zakazujú premávku v dôsledku povodní.",
|
||||||
|
"repeater_cliHelpStatsPackets": "(Len pre sériové záznamy) Zobrazuje štatistiky na úrovni paketov.",
|
||||||
|
"repeater_cliHelpStatsRadio": "(Len pre sériu) Zobrazuje údaje o rádiových staniciach.",
|
||||||
|
"repeater_cliHelpStatsCore": "(Len pre sériové modely) Zobrazuje základné štatistiky firmvéru.",
|
||||||
|
"common_done": "Done",
|
||||||
|
"background_serviceTitle": "MeshCore running",
|
||||||
|
"background_serviceText": "Keeping BLE connected",
|
||||||
|
"appSettings_translationModelDeleted": "Deleted {name}",
|
||||||
|
"@appSettings_translationModelDeleted": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
|
||||||
|
"@appSettings_translationModelDeleteFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channels_channelUpdateFailed": "Failed to update channel: {error}",
|
||||||
|
"@channels_channelUpdateFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map_type": "Type",
|
||||||
|
"map_path": "Path",
|
||||||
|
"map_location": "Location",
|
||||||
|
"map_estLocation": "Est. Location",
|
||||||
|
"map_publicKey": "Public Key",
|
||||||
|
"map_publicKeyPrefixHint": "e.g. ab12",
|
||||||
|
"contact_typeChat": "Chat",
|
||||||
|
"contact_typeRepeater": "Repeater",
|
||||||
|
"contact_typeRoom": "Room",
|
||||||
|
"contact_typeSensor": "Sensor",
|
||||||
|
"contact_typeUnknown": "Unknown",
|
||||||
|
"channels_via": "via {path}",
|
||||||
|
"chat_score": "Score",
|
||||||
|
"map_sharedAt": "Zdieľané",
|
||||||
|
"@losBlockedSpotChip": {
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@losSelectedObstructionDetails": {
|
||||||
|
"placeholders": {
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromA": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromB": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"losBlockedSpotsTitle": "Zablokované miesta",
|
||||||
|
"losBlockedSpotsHint": "Kliknite na zablokované miesto, aby ste ho zvýraznili na mape.",
|
||||||
|
"losSelectedObstructionTitle": "Vybraná prekážka",
|
||||||
|
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
|
||||||
|
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).",
|
||||||
|
"chat_markAsUnread": "Označenie ako neprečítané",
|
||||||
|
"settings_companionDebugLogSubtitle": "Príkazy, odpovede a surové dáta pre protokoly BLE/TCP/USB",
|
||||||
|
"settings_companionDebugLog": "Logovanie pre ladenie (sprievodný log)",
|
||||||
|
"chat_newMessages": "Nové správy",
|
||||||
|
"repeater_chanUtil": "Využitie kanálu",
|
||||||
|
"dialog_connectCompanion": "Pripojte sa k sprievodcovi a získajte prístup k funkciám opakovača a serveru miestností.",
|
||||||
|
"dialog_disconnectedTitle": "Odpojené",
|
||||||
|
"dialog_disconnectedMessage": "Od vášho spoločníka ste boli odpojený.",
|
||||||
|
"contact_connectCompanion": "Pripojte sa k spoločníkovi pre prístup k funkciám opakovača a miestneho servera."
|
||||||
}
|
}
|
||||||
|
|||||||
+486
-30
@@ -44,7 +44,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common_percentValue": "{percent}%",
|
"common_percentValue": "{percent} %",
|
||||||
"@common_percentValue": {
|
"@common_percentValue": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"percent": {
|
"percent": {
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scanner_title": "MeshCore Open",
|
"scanner_title": "MeshCore – Odprto",
|
||||||
"scanner_scanning": "Skeniram za naprave...",
|
"scanner_scanning": "Skeniram za naprave...",
|
||||||
"scanner_connecting": "Povezujem se...",
|
"scanner_connecting": "Povezujem se...",
|
||||||
"scanner_disconnecting": "Odklapljam se...",
|
"scanner_disconnecting": "Odklapljam se...",
|
||||||
@@ -104,6 +104,8 @@
|
|||||||
"settings_privacyModeEnabled": "Privatni način je omogočen.",
|
"settings_privacyModeEnabled": "Privatni način je omogočen.",
|
||||||
"settings_privacyModeDisabled": "Privatni način je onemogočen.",
|
"settings_privacyModeDisabled": "Privatni način je onemogočen.",
|
||||||
"settings_actions": "Akcije",
|
"settings_actions": "Akcije",
|
||||||
|
"settings_deleteAllPaths": "Delete All Paths",
|
||||||
|
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
|
||||||
"settings_sendAdvertisement": "Pošlji Oglas",
|
"settings_sendAdvertisement": "Pošlji Oglas",
|
||||||
"settings_sendAdvertisementSubtitle": "Trenutna prisotnost v oddajah",
|
"settings_sendAdvertisementSubtitle": "Trenutna prisotnost v oddajah",
|
||||||
"settings_advertisementSent": "Oglas poslan",
|
"settings_advertisementSent": "Oglas poslan",
|
||||||
@@ -115,13 +117,13 @@
|
|||||||
"settings_rebootDevice": "Ponovni zagon naprave",
|
"settings_rebootDevice": "Ponovni zagon naprave",
|
||||||
"settings_rebootDeviceSubtitle": "Ponovno zaženi MeshCore napravo",
|
"settings_rebootDeviceSubtitle": "Ponovno zaženi MeshCore napravo",
|
||||||
"settings_rebootDeviceConfirm": "Ste prepričani, da želite ponovno zagnati napravo? Povezava bo prekinjena.",
|
"settings_rebootDeviceConfirm": "Ste prepričani, da želite ponovno zagnati napravo? Povezava bo prekinjena.",
|
||||||
"settings_debug": "Debug",
|
"settings_debug": "Odpravljanje napak",
|
||||||
"settings_bleDebugLog": "BLE debug log (razhroščevanje)",
|
"settings_bleDebugLog": "BLE debug log (razhroščevanje)",
|
||||||
"settings_bleDebugLogSubtitle": "BLE ukazi, odgovori in surovi podatki",
|
"settings_bleDebugLogSubtitle": "BLE ukazi, odgovori in surovi podatki",
|
||||||
"settings_appDebugLog": "Logi aplikacije",
|
"settings_appDebugLog": "Logi aplikacije",
|
||||||
"settings_appDebugLogSubtitle": "Debug sporočila aplikacije",
|
"settings_appDebugLogSubtitle": "Debug sporočila aplikacije",
|
||||||
"settings_about": "Oglejte si",
|
"settings_about": "Oglejte si",
|
||||||
"settings_aboutVersion": "MeshCore Open v{version}",
|
"settings_aboutVersion": "MeshCore, različ {version}",
|
||||||
"@settings_aboutVersion": {
|
"@settings_aboutVersion": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"version": {
|
"version": {
|
||||||
@@ -133,7 +135,7 @@
|
|||||||
"settings_aboutDescription": "Odprtokodni Flutter klient za naprave za LoRa omrežje MeshCore.",
|
"settings_aboutDescription": "Odprtokodni Flutter klient za naprave za LoRa omrežje MeshCore.",
|
||||||
"settings_infoName": "Ime",
|
"settings_infoName": "Ime",
|
||||||
"settings_infoId": "ID",
|
"settings_infoId": "ID",
|
||||||
"settings_infoStatus": "Status",
|
"settings_infoStatus": "Stanje",
|
||||||
"settings_infoBattery": "Baterija",
|
"settings_infoBattery": "Baterija",
|
||||||
"settings_infoPublicKey": "Javni ključ",
|
"settings_infoPublicKey": "Javni ključ",
|
||||||
"settings_infoContactsCount": "Število stikov",
|
"settings_infoContactsCount": "Število stikov",
|
||||||
@@ -146,7 +148,7 @@
|
|||||||
"settings_spreadingFactor": "Razširitveni faktor",
|
"settings_spreadingFactor": "Razširitveni faktor",
|
||||||
"settings_codingRate": "Programska hitrost",
|
"settings_codingRate": "Programska hitrost",
|
||||||
"settings_txPower": "TX Moč (dBm)",
|
"settings_txPower": "TX Moč (dBm)",
|
||||||
"settings_txPowerHelper": "0 - 22",
|
"settings_txPowerHelper": "0 – 22",
|
||||||
"settings_txPowerInvalid": "Neveljavna TX moč (0-22 dBm)",
|
"settings_txPowerInvalid": "Neveljavna TX moč (0-22 dBm)",
|
||||||
"settings_error": "Napaka: {message}",
|
"settings_error": "Napaka: {message}",
|
||||||
"@settings_error": {
|
"@settings_error": {
|
||||||
@@ -164,18 +166,18 @@
|
|||||||
"appSettings_themeDark": "Temno",
|
"appSettings_themeDark": "Temno",
|
||||||
"appSettings_language": "Jezik",
|
"appSettings_language": "Jezik",
|
||||||
"appSettings_languageSystem": "Sistemska privzeta vrednost",
|
"appSettings_languageSystem": "Sistemska privzeta vrednost",
|
||||||
"appSettings_languageEn": "English",
|
"appSettings_languageEn": "Angleščina",
|
||||||
"appSettings_languageFr": "Français",
|
"appSettings_languageFr": "Francija",
|
||||||
"appSettings_languageEs": "Español",
|
"appSettings_languageEs": "Španščina",
|
||||||
"appSettings_languageDe": "Deutsch",
|
"appSettings_languageDe": "Nemščina",
|
||||||
"appSettings_languagePl": "Polski",
|
"appSettings_languagePl": "Poljski",
|
||||||
"appSettings_languageSl": "Slovenščina",
|
"appSettings_languageSl": "Slovenščina",
|
||||||
"appSettings_languagePt": "Português",
|
"appSettings_languagePt": "Portugalski",
|
||||||
"appSettings_languageIt": "Italiano",
|
"appSettings_languageIt": "Italijanščina",
|
||||||
"appSettings_languageZh": "中文",
|
"appSettings_languageZh": "中文",
|
||||||
"appSettings_languageSv": "Svenska",
|
"appSettings_languageSv": "Švedska",
|
||||||
"appSettings_languageNl": "Nederlands",
|
"appSettings_languageNl": "Nizozemsko",
|
||||||
"appSettings_languageSk": "Slovenčina",
|
"appSettings_languageSk": "Slovenščina",
|
||||||
"appSettings_languageBg": "Български",
|
"appSettings_languageBg": "Български",
|
||||||
"appSettings_notifications": "Obvestila",
|
"appSettings_notifications": "Obvestila",
|
||||||
"appSettings_enableNotifications": "Omogoči obvestila",
|
"appSettings_enableNotifications": "Omogoči obvestila",
|
||||||
@@ -285,6 +287,7 @@
|
|||||||
"contacts_newGroup": "Nova skupina",
|
"contacts_newGroup": "Nova skupina",
|
||||||
"contacts_groupName": "Ime skupine",
|
"contacts_groupName": "Ime skupine",
|
||||||
"contacts_groupNameRequired": "Ime skupine je obvezno.",
|
"contacts_groupNameRequired": "Ime skupine je obvezno.",
|
||||||
|
"contacts_groupNameReserved": "To ime skupine je rezervirano",
|
||||||
"contacts_groupAlreadyExists": "Skupina \"{name}\" že obstaja",
|
"contacts_groupAlreadyExists": "Skupina \"{name}\" že obstaja",
|
||||||
"@contacts_groupAlreadyExists": {
|
"@contacts_groupAlreadyExists": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -336,11 +339,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_hashtagChannel": "Hashtag kanal",
|
|
||||||
"channels_public": "Javni",
|
"channels_public": "Javni",
|
||||||
"channels_private": "Zasebni",
|
"channels_private": "Zasebni",
|
||||||
"channels_publicChannel": "Javni kanal",
|
|
||||||
"channels_privateChannel": "Zasebni kanal",
|
|
||||||
"channels_editChannel": "Uredi kanal",
|
"channels_editChannel": "Uredi kanal",
|
||||||
"channels_muteChannel": "Utišaj kanal",
|
"channels_muteChannel": "Utišaj kanal",
|
||||||
"channels_unmuteChannel": "Vklopi obvestila kanala",
|
"channels_unmuteChannel": "Vklopi obvestila kanala",
|
||||||
@@ -387,6 +387,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_smazCompression": "Kompresija SMAZ",
|
"channels_smazCompression": "Kompresija SMAZ",
|
||||||
|
"channels_cyr2latCompression": "Kompresija Cyr2Lat",
|
||||||
|
"channels_cyr2latCompressionDscr": "Pri pošiljanju nekatere cirilice nadomesti z latiničnimi.",
|
||||||
|
"channels_cyr2latSettingsHeading": "Nastavitve Cyr2Lat",
|
||||||
|
"channels_cyr2latSettingsSubheading": "Seznam zamenjav",
|
||||||
|
"channels_cyr2latSettingsDscr": "Uredi JSON-konfiguracijo zamenjav znakov",
|
||||||
|
"channels_cyr2latSettingsDialogHint": "JSON-tabela zamenjav",
|
||||||
|
"channels_cyr2latSettingsDialogWrongJSON": "Nepravilen JSON: {error}",
|
||||||
|
"settings_cyr2latProfileAdd": "Dodaj profil Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileName": "Ime profila",
|
||||||
|
"settings_cyr2latProfileNameEmpty": "Ime profila ne sme biti prazno",
|
||||||
|
"settings_cyr2latProfileAdded": "Profil je bil uspešno dodan",
|
||||||
|
"settings_cyr2latProfileUpdated": "Profil je bil uspešno posodobljen",
|
||||||
|
"settings_cyr2latProfileEdit": "Uredi profil Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileDelete": "Izbriši profil Cyr2Lat",
|
||||||
|
"settings_cyr2latProfileDeleted": "Profil je bil uspešno izbrisan",
|
||||||
|
"settings_cyr2latProfileDeleteDscr": "Ali res želite izbrisati profil \"{name}\"?",
|
||||||
"channels_channelUpdated": "Kanal {name} je bil posodobljen",
|
"channels_channelUpdated": "Kanal {name} je bil posodobljen",
|
||||||
"@channels_channelUpdated": {
|
"@channels_channelUpdated": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -398,7 +414,7 @@
|
|||||||
"channels_publicChannelAdded": "javna skupnost dodana",
|
"channels_publicChannelAdded": "javna skupnost dodana",
|
||||||
"channels_sortBy": "Sortiraj po",
|
"channels_sortBy": "Sortiraj po",
|
||||||
"channels_sortManual": "Ročno",
|
"channels_sortManual": "Ročno",
|
||||||
"channels_sortAZ": "A-Z",
|
"channels_sortAZ": "A do Z",
|
||||||
"channels_sortLatestMessages": "Najnovejše sporočilo",
|
"channels_sortLatestMessages": "Najnovejše sporočilo",
|
||||||
"channels_sortUnread": "Nerešeno",
|
"channels_sortUnread": "Nerešeno",
|
||||||
"chat_noMessages": "Še ni sporočil.",
|
"chat_noMessages": "Še ni sporočil.",
|
||||||
@@ -530,7 +546,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debugFrame_textTypeCli": "CLI",
|
"debugFrame_textTypeCli": "CLI (Command Line Interface)",
|
||||||
"debugFrame_textTypePlain": "Preprosto",
|
"debugFrame_textTypePlain": "Preprosto",
|
||||||
"debugFrame_text": "- Tekst: \"{text}\"",
|
"debugFrame_text": "- Tekst: \"{text}\"",
|
||||||
"@debugFrame_text": {
|
"@debugFrame_text": {
|
||||||
@@ -540,7 +556,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debugFrame_hexDump": "Hex Dump:",
|
"debugFrame_hexDump": "Izpis heksadecimalnih vrednosti:",
|
||||||
"chat_pathManagement": "Upravljanje poti",
|
"chat_pathManagement": "Upravljanje poti",
|
||||||
"chat_routingMode": "Navodilo za usmerjevalni način",
|
"chat_routingMode": "Navodilo za usmerjevalni način",
|
||||||
"chat_autoUseSavedPath": "Avto (uporabi shranjeno pot)",
|
"chat_autoUseSavedPath": "Avto (uporabi shranjeno pot)",
|
||||||
@@ -549,7 +565,7 @@
|
|||||||
"chat_pathHistoryFull": "Zapiske o poti so popolni. Izbriši vnose, da dodaš nove.",
|
"chat_pathHistoryFull": "Zapiske o poti so popolni. Izbriši vnose, da dodaš nove.",
|
||||||
"chat_hopSingular": "skok",
|
"chat_hopSingular": "skok",
|
||||||
"chat_hopPlural": "skokov",
|
"chat_hopPlural": "skokov",
|
||||||
"chat_hopsCount": "{count} {count, plural, =1{hop} other{hops}}",
|
"chat_hopsCount": "{count} {count, plural, =1{skok} other{skoki}}",
|
||||||
"@chat_hopsCount": {
|
"@chat_hopsCount": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {
|
"count": {
|
||||||
@@ -878,11 +894,11 @@
|
|||||||
"path_setPath": "Nastavi Pot",
|
"path_setPath": "Nastavi Pot",
|
||||||
"repeater_management": "Upravljanje ponovitve",
|
"repeater_management": "Upravljanje ponovitve",
|
||||||
"repeater_managementTools": "Upravne orodje",
|
"repeater_managementTools": "Upravne orodje",
|
||||||
"repeater_status": "Status",
|
"repeater_status": "Stanje",
|
||||||
"repeater_statusSubtitle": "Pogledati stanje, statistike in sosede repeatera",
|
"repeater_statusSubtitle": "Pogledati stanje, statistike in sosede repeatera",
|
||||||
"repeater_telemetry": "Telemetrija",
|
"repeater_telemetry": "Telemetrija",
|
||||||
"repeater_telemetrySubtitle": "Pogledate telemetrijo senzorjev in sistemske statistike",
|
"repeater_telemetrySubtitle": "Pogledate telemetrijo senzorjev in sistemske statistike",
|
||||||
"repeater_cli": "CLI",
|
"repeater_cli": "CLI (Command Line Interface)",
|
||||||
"repeater_cliSubtitle": "Pošlji ukazne povelje na ponovitveno enoto.",
|
"repeater_cliSubtitle": "Pošlji ukazne povelje na ponovitveno enoto.",
|
||||||
"repeater_settings": "Nastavitve",
|
"repeater_settings": "Nastavitve",
|
||||||
"repeater_settingsSubtitle": "Konfigurirajte parametre ponovitelja",
|
"repeater_settingsSubtitle": "Konfigurirajte parametre ponovitelja",
|
||||||
@@ -991,7 +1007,7 @@
|
|||||||
"repeater_guestPasswordHelper": "Odpovedni dostopni geslo",
|
"repeater_guestPasswordHelper": "Odpovedni dostopni geslo",
|
||||||
"repeater_radioSettings": "Nastavitve Radija",
|
"repeater_radioSettings": "Nastavitve Radija",
|
||||||
"repeater_frequencyMhz": "Frekvenca (MHz)",
|
"repeater_frequencyMhz": "Frekvenca (MHz)",
|
||||||
"repeater_frequencyHelper": "300-2500 MHz",
|
"repeater_frequencyHelper": "300–2500 MHz",
|
||||||
"repeater_txPower": "TX Moč",
|
"repeater_txPower": "TX Moč",
|
||||||
"repeater_txPowerHelper": "1-30 dBm",
|
"repeater_txPowerHelper": "1-30 dBm",
|
||||||
"repeater_bandwidth": "Pasovna širina",
|
"repeater_bandwidth": "Pasovna širina",
|
||||||
@@ -1058,6 +1074,81 @@
|
|||||||
},
|
},
|
||||||
"repeater_confirm": "Potrdit",
|
"repeater_confirm": "Potrdit",
|
||||||
"repeater_settingsSaved": "Nastavitve so shranjene uspešno.",
|
"repeater_settingsSaved": "Nastavitve so shranjene uspešno.",
|
||||||
|
"repeater_rxGain": "Povečana dobitka RX",
|
||||||
|
"repeater_rxGainHelper": "Veća občutljivost, večji porabljeni tok (velja samo za SX1262/SX1268)",
|
||||||
|
"repeater_refreshRxGain": "Povečana dobitka RX, posodobit",
|
||||||
|
"repeater_multiAcks": "Več potrdil",
|
||||||
|
"repeater_multiAcksSubtitle": "Potrdite sporočila po več poti za boljši dostop",
|
||||||
|
"repeater_refreshMultiAcks": "Ponovite več potrdil",
|
||||||
|
"repeater_networkHealth": "Zdravilo omrežja",
|
||||||
|
"repeater_loopDetect": "Detekcija ciklov",
|
||||||
|
"repeater_loopDetectHelper": "Izpišite pakete, ki izgledajo kot pete v omrežju.",
|
||||||
|
"repeater_loopDetectOff": "Izklopljeno",
|
||||||
|
"repeater_loopDetectMinimal": "Minimalen",
|
||||||
|
"repeater_loopDetectModerate": "Umiren",
|
||||||
|
"repeater_loopDetectStrict": "Strogi",
|
||||||
|
"repeater_dutyCycle": "Ciklus delovanja",
|
||||||
|
"repeater_dutyCycleHelper": "Najvišji odstotek časa, ki ga lahko posreduje.",
|
||||||
|
"repeater_dutyCyclePercent": "{percent} %",
|
||||||
|
"@repeater_dutyCyclePercent": {
|
||||||
|
"placeholders": {
|
||||||
|
"percent": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_ownerInfo": "Informacije o operaterju",
|
||||||
|
"repeater_ownerInfoHelper": "javni podatki o tej napravi",
|
||||||
|
"repeater_refreshOwnerInfo": "Prejmi informacije o operaterju",
|
||||||
|
"repeater_floodMax": "Največji možni odmerek",
|
||||||
|
"repeater_floodMaxHelper": "Največje število paketov, ki lahko potujejo v enem plovilnem paketu (0-64)",
|
||||||
|
"repeater_advancedSettings": "Napredno",
|
||||||
|
"repeater_advancedSettingsSubtitle": "Gumbi za nastavljanje za izkušene uporabnike",
|
||||||
|
"repeater_pathHashMode": "Način ustvarjanja hash-a poti",
|
||||||
|
"repeater_pathHashModeHelper": "Biti, ki so bila uporabljena za kodiranje ID-ja tega releja v oznakah za zaznavanje pot/kroga, imajo naslednje velikosti: 0=1 bit (256 ID-jev, do 64 skokov), 1=2 biti (65.000 ID-jev, do 32 skokov), 2=3 biti (16 milijonov ID-jev, do 21 skokov). V različicah 1.13 in starejših se ustvarjajo večbitne poti – vendar se to zgodi šele, ko je omrežje vklopljeno v različicah 1.14 in kasnejših.",
|
||||||
|
"repeater_txDelay": "Zatemnitevanje zaradi poplav v Texasu",
|
||||||
|
"repeater_txDelayHelper": "Uporaba intervalov za ponovno pošiljanje v primeru prometa zaradi poplav, kot pomnožnik časovne trajanje paketa (0-2, privzeto 0,5). Veje vrednost = manjše kolizije, vendar počasnejše dostavo.",
|
||||||
|
"repeater_directTxDelay": "Neposredni časovno odlašanje",
|
||||||
|
"repeater_directTxDelayHelper": "Razdalja za ponovno pošiljanje za neposredno (neobvezen) promet, kot pomnožnik časovne trajanja paketa (0-2, privzeto 0,3).",
|
||||||
|
"repeater_intThresh": "Meja, pri kateri nastane motnja",
|
||||||
|
"repeater_intThreshHelper": "Tretja stopnja se uporablja za kalibracijo šumnega nivoja radija, kar omogoča, da se izklaplja pri šumu, ki presega to raven. 0 izklopi – uporabite le, če zaznate napake v šumnem pasu.",
|
||||||
|
"repeater_agcResetInterval": "Interval ponovne kalibracije AGC",
|
||||||
|
"repeater_agcResetIntervalHelper": "Kako pogosto je treba ponovno nastaviti samodejno regulacijo občutljivosti, da se vrnete v normalno stanje? Interval je nastavljen na nekaj sekund, natančno na 4. 0 izklopi periodično ponovno nastavljanje.",
|
||||||
|
"repeater_actionsTitle": "Dejanja",
|
||||||
|
"repeater_sendAdvert": "Pošlji oglas o poplavah",
|
||||||
|
"repeater_sendAdvertSubtitle": "Razpustite oglas o poplavah preko omrežja.",
|
||||||
|
"repeater_sendAdvertZeroHop": "Pošlji oglas, ki ne potrebuje posrednika.",
|
||||||
|
"repeater_sendAdvertZeroHopSubtitle": "Premejte oglas, ki uporablja eno povezavo (brez posrednikov).",
|
||||||
|
"repeater_clockSync": "Sinerizirajte uro zdaj",
|
||||||
|
"repeater_clockSyncSubtitle": "Nastavite čas na telefonu, da se sinhronizira s repeatrom.",
|
||||||
|
"repeater_actionSucceeded": "{action} je uspel",
|
||||||
|
"@repeater_actionSucceeded": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_actionFailed": "{action} ni bilo uspešno: {error}",
|
||||||
|
"@repeater_actionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_settingsSavedRebootNeeded": "Nastavitve shranjene – ponovni zagon repetitorja za uporabo",
|
||||||
|
"repeater_settingsPartialFailure": "Nekatna nastavitva niso uspešna: {failures}",
|
||||||
|
"@repeater_settingsPartialFailure": {
|
||||||
|
"placeholders": {
|
||||||
|
"failures": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repeater_errorSavingSettings": "Napaka pri shranjevanju nastavitev: {error}",
|
"repeater_errorSavingSettings": "Napaka pri shranjevanju nastavitev: {error}",
|
||||||
"@repeater_errorSavingSettings": {
|
"@repeater_errorSavingSettings": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1069,11 +1160,9 @@
|
|||||||
"repeater_refreshBasicSettings": "Ponovno nastavi osnovne nastavitve",
|
"repeater_refreshBasicSettings": "Ponovno nastavi osnovne nastavitve",
|
||||||
"repeater_refreshRadioSettings": "Ponovno Nastavitve Radija",
|
"repeater_refreshRadioSettings": "Ponovno Nastavitve Radija",
|
||||||
"repeater_refreshTxPower": "Ponovno nastavi TX moč",
|
"repeater_refreshTxPower": "Ponovno nastavi TX moč",
|
||||||
"repeater_refreshLocationSettings": "Ponovno Nastavi Nastavitve Lokacije",
|
|
||||||
"repeater_refreshPacketForwarding": "Ponovno nastavitve usmerjevanja paketa",
|
"repeater_refreshPacketForwarding": "Ponovno nastavitve usmerjevanja paketa",
|
||||||
"repeater_refreshGuestAccess": "Ponovno nastavitve dostopa gostov",
|
"repeater_refreshGuestAccess": "Ponovno nastavitve dostopa gostov",
|
||||||
"repeater_refreshPrivacyMode": "Ponovno aktiviraj način zasebnosti",
|
"repeater_refreshPrivacyMode": "Ponovno aktiviraj način zasebnosti",
|
||||||
"repeater_refreshAdvertisementSettings": "Ponovno nastavi Oglede Oglasi",
|
|
||||||
"repeater_refreshed": "{label} je bil/a posodobljen/a",
|
"repeater_refreshed": "{label} je bil/a posodobljen/a",
|
||||||
"@repeater_refreshed": {
|
"@repeater_refreshed": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1346,7 +1435,7 @@
|
|||||||
"listFilter_sortBy": "Sortiraj po",
|
"listFilter_sortBy": "Sortiraj po",
|
||||||
"listFilter_latestMessages": "Najnovejše sporočilo",
|
"listFilter_latestMessages": "Najnovejše sporočilo",
|
||||||
"listFilter_heardRecently": "Nedavno slišan",
|
"listFilter_heardRecently": "Nedavno slišan",
|
||||||
"listFilter_az": "A-Z",
|
"listFilter_az": "A do Z",
|
||||||
"listFilter_filters": "Filtri",
|
"listFilter_filters": "Filtri",
|
||||||
"listFilter_all": "Vse",
|
"listFilter_all": "Vse",
|
||||||
"listFilter_users": "Uporabniki",
|
"listFilter_users": "Uporabniki",
|
||||||
@@ -1859,5 +1948,372 @@
|
|||||||
"usbStatus_connecting": "Povezava z USB napravo...",
|
"usbStatus_connecting": "Povezava z USB napravo...",
|
||||||
"usbStatus_searching": "Iskanje USB naprav...",
|
"usbStatus_searching": "Iskanje USB naprav...",
|
||||||
"usbConnectionFailed": "Napaka pri povezavi preko USB: {error}",
|
"usbConnectionFailed": "Napaka pri povezavi preko USB: {error}",
|
||||||
"usbErrorConnectTimedOut": "Vzpostavitve ni bilo mogo. Prosimo, da se prepričate, da ima naprave trenutno nameštan firmware USB Companion."
|
"usbErrorConnectTimedOut": "Vzpostavitve ni bilo mogo. Prosimo, da se prepričate, da ima naprave trenutno nameštan firmware USB Companion.",
|
||||||
|
"@tcpStatus_connectingTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"endpoint": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tcpConnectionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"connectionChoiceTcpLabel": "TCP",
|
||||||
|
"tcpHostLabel": "IP naslov",
|
||||||
|
"tcpHostHint": "192.168.40.10",
|
||||||
|
"tcpScreenTitle": "Komunicirajte preko protokola TCP",
|
||||||
|
"tcpPortLabel": "Vrata",
|
||||||
|
"tcpPortHint": "5000",
|
||||||
|
"tcpStatus_notConnected": "Vnesite končni naslov in se povežite",
|
||||||
|
"tcpStatus_connectingTo": "Povezava z {endpoint}...",
|
||||||
|
"tcpErrorHostRequired": "Potrebna je IP-naslov.",
|
||||||
|
"tcpErrorPortInvalid": "Port mora biti med 1 in 65535.",
|
||||||
|
"tcpErrorUnsupported": "Transport preko protokola TCP ni podprt na tej platformi.",
|
||||||
|
"tcpErrorTimedOut": "Povezava TCP je presegla časovno obdobje.",
|
||||||
|
"tcpConnectionFailed": "Napaka pri povezavi TCP: {error}",
|
||||||
|
"map_showDiscoveryContacts": "Prikaži odkritja kontaktov",
|
||||||
|
"map_setAsMyLocation": "Nastavite to kot mojo lokacijo",
|
||||||
|
"@path_routeWeight": {
|
||||||
|
"placeholders": {
|
||||||
|
"weight": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings_privacy": "Nastavitve zasebnosti",
|
||||||
|
"settings_privacySettingsDescription": "Izberite, katere informacije vaš naprava deli z drugimi.",
|
||||||
|
"settings_telemetryBaseMode": "Osnovni način telemetrije",
|
||||||
|
"settings_telemetryLocationMode": "Način delovanja telemetrije",
|
||||||
|
"settings_telemetryEnvironmentMode": "Način delovanja okolja telemetrije",
|
||||||
|
"settings_advertLocation": "Lokacija oglasa",
|
||||||
|
"settings_allowByContact": "Dovoli po kontaktnih zastavah",
|
||||||
|
"settings_denyAll": "Zavrniti vse",
|
||||||
|
"settings_allowAll": "Dovoli vse",
|
||||||
|
"settings_privacySubtitle": "Kontrolirajte, katere informacije so deljene.",
|
||||||
|
"contact_info": "Kontaktni podatki",
|
||||||
|
"contact_teleBase": "Baza telemetrije",
|
||||||
|
"contact_teleBaseSubtitle": "Dovoli deljenje stanja baterije in osnovne telemetrije",
|
||||||
|
"contact_teleLoc": "Lokacija telemetrije",
|
||||||
|
"contact_lastSeen": "Zadnjič videno",
|
||||||
|
"contact_settings": "Nastavitve stika",
|
||||||
|
"settings_advertLocationSubtitle": "Vključi lokacijo v oglas.",
|
||||||
|
"contact_telemetry": "Telemetrija",
|
||||||
|
"contact_clearChat": "Počisti klepet",
|
||||||
|
"contact_teleEnv": "Okolje telemetrije",
|
||||||
|
"contact_teleEnvSubtitle": "Dovoli deljenje podatkov okoljskih senzorjev",
|
||||||
|
"contact_teleLocSubtitle": "Dovoli deljenje podatkov o lokaciji",
|
||||||
|
"appSettings_maxRouteWeightSubtitle": "Največja teža, ki jo lahko pot doseže s uspešnimi dostavnami.",
|
||||||
|
"appSettings_initialRouteWeight": "Izvirna teža poti",
|
||||||
|
"appSettings_initialRouteWeightSubtitle": "Izguba teže za nove, odkriti poti",
|
||||||
|
"appSettings_maxRouteWeight": "Največja dovoljena teža poti",
|
||||||
|
"appSettings_routeWeightSuccessIncrement": "Učinkovitost: povečanje",
|
||||||
|
"appSettings_routeWeightSuccessIncrementSubtitle": "Težava, dodana poti po uspešni dostavi",
|
||||||
|
"appSettings_routeWeightFailureDecrement": "Zmanjšanje teže, ki je povezana s pomanjkanjem",
|
||||||
|
"appSettings_routeWeightFailureDecrementSubtitle": "Težo, ki ni bila uspešno dostavljena, odstranili s poti.",
|
||||||
|
"appSettings_maxMessageRetries": "Najve število poskusov pošiljanja sporočil",
|
||||||
|
"appSettings_maxMessageRetriesSubtitle": "Število poskusov ponovnega poslanja, preden se sporočilo označuje kot neuspešno",
|
||||||
|
"path_routeWeight": "{weight}/{max}",
|
||||||
|
"settings_telemetryModeUpdated": "Način telemetrije posodobljen",
|
||||||
|
"map_showOverlaps": "Prekrivanje ključa ponovnega predvajanja",
|
||||||
|
"map_runTraceWithReturnPath": "Vrni se nazaj po isti poti.",
|
||||||
|
"@radioStats_noiseFloor": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastRssi": {
|
||||||
|
"placeholders": {
|
||||||
|
"rssiDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastSnr": {
|
||||||
|
"placeholders": {
|
||||||
|
"snr": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_txAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_rxAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_stripNoise": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_languageHu": "Madžarski",
|
||||||
|
"appSettings_jumpToOldestUnreadSubtitle": "Ko odpirate klepet z neprebranimi sporočili, se premaknite na prvo neprebrano sporočilo, namesto najnovejšega.",
|
||||||
|
"chat_sendCooldown": "Prosimo, počakajte trenutek, preden pošljete ponovno.",
|
||||||
|
"appSettings_jumpToOldestUnread": "Pritisnite za najstarejše nepročitano sporočilo",
|
||||||
|
"appSettings_languageJa": "Japonski",
|
||||||
|
"appSettings_languageKo": "Korejski",
|
||||||
|
"radioStats_tooltip": "Statistike za radio in mrežo",
|
||||||
|
"radioStats_notConnected": "Povežite se z napravo, da si ogledate statistiko o radiju.",
|
||||||
|
"radioStats_screenTitle": "Radijske statistike",
|
||||||
|
"radioStats_firmwareTooOld": "Statistika za radio zahteva združljivo programsko opremo v8 ali kasnejše.",
|
||||||
|
"radioStats_waiting": "Čakam na podatke…",
|
||||||
|
"radioStats_noiseFloor": "Število šuma: {noiseDbm} dBm",
|
||||||
|
"radioStats_lastRssi": "Najkasnejše vrednost RSSI: {rssiDbm} dBm",
|
||||||
|
"radioStats_lastSnr": "Najkasnejše vrednost SNR: {snr} dB",
|
||||||
|
"radioStats_txAir": "Čas na TX (skupno): {seconds} s",
|
||||||
|
"radioStats_rxAir": "Čas, namenjen RX-ju (skupno): {seconds} s",
|
||||||
|
"radioStats_chartCaption": "Ravnovredna raven šuma (dBm) za nedavne vzorce.",
|
||||||
|
"radioStats_stripNoise": "Število šuma: {noiseDbm} dBm",
|
||||||
|
"radioStats_stripWaiting": "Prejemanje statistike o radiju…",
|
||||||
|
"radioStats_settingsTile": "Radijske statistike",
|
||||||
|
"radioStats_settingsSubtitle": "Število šumov, RSSI, SNR in čas, ki ga je napolnila oprema",
|
||||||
|
"@translation_downloadFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_composerTitle": "Preprištejte, preden pošljete",
|
||||||
|
"translation_title": "Prevod",
|
||||||
|
"translation_enableSubtitle": "Prevedite vstopne sporočila in omogočite predhodno prevajanje.",
|
||||||
|
"translation_enableTitle": "Omogočite prevod",
|
||||||
|
"translation_composerSubtitle": "Ureja privzeto stanje ikone za prevod, ki jo uporablja avtor.",
|
||||||
|
"translation_targetLanguage": "Ciljna jezika",
|
||||||
|
"translation_useAppLanguage": "Uporabite jezik aplikacije",
|
||||||
|
"translation_downloadedModelLabel": "Naložen model",
|
||||||
|
"translation_presetModelLabel": "Prednastavljeni model Hugging Face",
|
||||||
|
"translation_manualUrlLabel": "URL za ročni model",
|
||||||
|
"translation_downloadModel": "Prenesite model",
|
||||||
|
"translation_downloading": "Izvajanje...",
|
||||||
|
"translation_working": "Delo...",
|
||||||
|
"translation_stop": "Prekliji",
|
||||||
|
"translation_mergingChunks": "Sklapljanje prenesenih delov v končni datoteko...",
|
||||||
|
"translation_downloadedModels": "Naloženi modeli",
|
||||||
|
"translation_deleteModel": "Izbrisati model",
|
||||||
|
"translation_modelDownloaded": "Model za prevajanje je bil naložen.",
|
||||||
|
"translation_downloadStopped": "Prenos je bil prekinjen.",
|
||||||
|
"translation_downloadFailed": "Izgovoritev ni bila uspešna: {error}",
|
||||||
|
"translation_enterUrlFirst": "Najprej vnesite URL model.",
|
||||||
|
"@scanner_linuxPairingPinPrompt": {
|
||||||
|
"placeholders": {
|
||||||
|
"deviceName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@translation_translateTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"language": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_translateBeforeSending": "Preprištejte, preden pošljete",
|
||||||
|
"translation_composerDisabledHint": "Pošljite sporočila v originalnem tipkanem jeziku.",
|
||||||
|
"translation_composerEnabledHint": "Vsebina sporočil bo prevedena, preden jih pošljemo.",
|
||||||
|
"translation_messageTranslation": "Prevod sporočila",
|
||||||
|
"translation_translateTo": "Prevesti v {language}",
|
||||||
|
"translation_translationOptions": "Možnosti prevoda",
|
||||||
|
"translation_systemLanguage": "Jezik sistema",
|
||||||
|
"scanner_linuxPairingShowPin": "Prikaži PIN",
|
||||||
|
"scanner_linuxPairingHidePin": "Skrij PIN",
|
||||||
|
"scanner_linuxPairingPinPrompt": "Vnesite PIN za {deviceName} (pustite prazno, če ga ni).",
|
||||||
|
"scanner_linuxPairingPinTitle": "Bluetooth PIN za seznanjanje",
|
||||||
|
"repeater_cliQuickDiscovery": "Odkrijte sosede",
|
||||||
|
"repeater_cliQuickClockSync": "Usklajevanje ure",
|
||||||
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Samodejno po uspešnem vstopu pošljite obvestilo o sinhronizaciji časa.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Sinhronizacija ure po prijavi",
|
||||||
|
"repeater_guest": "Informacije o ponovljalniku",
|
||||||
|
"chat_sendMessage": "Pošlji sporočilo",
|
||||||
|
"room_guest": "Informacije o strežniku",
|
||||||
|
"repeater_guestTools": "Naložila za goste",
|
||||||
|
"repeater_getCategory": "Dobite vrednosti",
|
||||||
|
"repeater_powerMgmt": "Upravljanje z energijo",
|
||||||
|
"repeater_sensors": "Senzori",
|
||||||
|
"repeater_cliHelpPowerOff": "Izklopi naprave. (ne pričakujemo odziva)",
|
||||||
|
"repeater_cliHelpClkReboot": "Ponovno nastavi uro na znano točko in ponovno vklopi naprave.",
|
||||||
|
"repeater_cliHelpAdvertZeroHop": "Pošlje oglas, ki doseže samo neposredne sosede (brez posredovanja).",
|
||||||
|
"repeater_cliHelpStartOta": "Začne nadstrekovno ažuriranje programne opreme na podprtih ploščah.",
|
||||||
|
"repeater_cliHelpTime": "Nastavi časovni ukaz naprave na podano število sekund od Unixovega začetka. Časovni ukaz ne more iti nazaj.",
|
||||||
|
"repeater_cliHelpBoard": "Prikaže proizvajalca plošče / identifikator strojne opreme.",
|
||||||
|
"repeater_cliHelpDiscoverNeighbors": "Pošlje zahtevo za odkrivanje sosednjih naprav. (Samo za repeatere)",
|
||||||
|
"repeater_cliHelpPowersaving": "Prikaže, ali je vklopljen način varčevanja z energijo.",
|
||||||
|
"repeater_cliHelpPowersavingOnOff": "Omogoča ali onemogoča način varčevanja z energijo (če je podprt).",
|
||||||
|
"repeater_cliHelpErase": "(Samo za serijske naprave) Formira datotapno sistemsko okolje. Izbriše vse nastavitve in kontakte.",
|
||||||
|
"repeater_cliHelpSetDutyCycle": "Določi maksimalni dovoljeni čas, ki ga naprave lahko posredujejo, v odstotkih (1-100). Samodejno prilagodi faktor, ki odvisen je od časa, ki ga naprave lahko posredujejo.",
|
||||||
|
"repeater_cliHelpSetPrvKey": "(Samo za serijske naprave) Nadomesti zasebni ključ za identifikacijo naprave. Za uporabo je potrebna ponovna aktivacija. Ustvari nov javni ključ.",
|
||||||
|
"repeater_cliHelpSetRadioRxGain": "(Samo za SX126x) Vklopi povečano občutljivost RX za izboljšano delovanje pri večjih navorih.",
|
||||||
|
"repeater_cliHelpSetOwnerInfo": "Določi niz z informacijami o kontaktni osebi, ki je v oglasih. Za uporabo novih vrstic uporabite '|'.",
|
||||||
|
"repeater_cliHelpSetPathHashMode": "Nastavlja način \"hash poti\". 0 = za stare sisteme, 1 = za standard, 2 = za stroge. Vpliva na to, kako so poti uskladene.",
|
||||||
|
"repeater_cliHelpSetLoopDetect": "Nastavlja občutljivost detekcije ponavljajočih se povezav: izklopljeno, minimalno, umeren, ali strogo.",
|
||||||
|
"repeater_cliHelpSetFreq": "(Samo za serijske naprave) Hitro nastavi samo frekvenco. Potrebna je ponovna aktivacija. Za popolno nastavitev radio parametrov je priporočljivo uporabiti možnost \"nastavitev radia\".",
|
||||||
|
"repeater_cliHelpSetBridgeChannel": "(Samo za most ESPNow) Nastavlja kanal WiFi-ja (1-14), ki ga uporablja most.",
|
||||||
|
"repeater_cliHelpGetName": "Prikaže ime konfigurirane notranje.",
|
||||||
|
"repeater_cliHelpGetRole": "Prikaže vlogo programskega oprema (repeater, strežnik za sobo itd.).",
|
||||||
|
"repeater_cliHelpGetPublicKey": "Prikazuje javni ključ naprave.",
|
||||||
|
"repeater_cliHelpGetPrvKey": "(Samo za serijske naprave) Prikazuje zasebni ključ naprave. Sprejemajte ga kot skrivno informacijo.",
|
||||||
|
"repeater_cliHelpGetRepeat": "Pokaže, ali je omogočeno posredovanje paketov (delovanje kot repetitor).",
|
||||||
|
"repeater_cliHelpGetTx": "Prikazuje trenutno moč TX v dBm.",
|
||||||
|
"repeater_cliHelpGetFreq": "Prikaže nastavljeno frekvenco v MHz.",
|
||||||
|
"repeater_cliHelpGetRadio": "Prikaže vse parametre radija: frekvenco, širino pasu, faktor razširjanja, raven kodiranja.",
|
||||||
|
"repeater_cliHelpGetRadioRxGain": "(Samo za SX126x) Prikazuje stanje povečanega dobiča na RX.",
|
||||||
|
"repeater_cliHelpGetAf": "Prikazuje trenutni faktor, ki določa časovno obdobje.",
|
||||||
|
"repeater_cliHelpGetDutyCycle": "Prikazuje trenutno dovoljeno stopnjo delovanja kot odstotek.",
|
||||||
|
"repeater_cliHelpGetIntThresh": "Prikazuje prag medsebojnega vpliva kanala v dB.",
|
||||||
|
"repeater_cliHelpGetAgcResetInterval": "Prikazuje interval ponovne kalibracije AGC v sekundah.",
|
||||||
|
"repeater_cliHelpGetMultiAcks": "Pokaže, ali je vklopljen način dvojnega potrdila (1) ali je izklopljen (0).",
|
||||||
|
"repeater_cliHelpGetAllowReadOnly": "Pokaže, ali je omogočen le brani dostop za goste.",
|
||||||
|
"repeater_cliHelpGetAdvertInterval": "Prikazuje časovno obdobje lokalne reklame v minutah.",
|
||||||
|
"repeater_cliHelpGetFloodAdvertInterval": "Prikaže časovno obdobje, ko se prikazuje oglas o poplavah, v urah.",
|
||||||
|
"repeater_cliHelpGetGuestPassword": "Prikaže nastavljeno geslo za gostitelja.",
|
||||||
|
"repeater_cliHelpGetLat": "Prikaže določeno zemljepisno širino.",
|
||||||
|
"repeater_cliHelpGetLon": "Prikaže določeno merilo dolžine.",
|
||||||
|
"repeater_cliHelpGetRxDelay": "Prikazuje osnovno vrednost RX odlašanja.",
|
||||||
|
"repeater_cliHelpGetTxDelay": "Prikazuje faktor zamude v načinu delovanja pri plavlju.",
|
||||||
|
"repeater_cliHelpGetDirectTxDelay": "Prikazuje faktor odlašanja signala v režimu neposredne komunikacije.",
|
||||||
|
"repeater_cliHelpGetFloodMax": "Prikazuje največjo število, kolikokrat lahko voda doseže najvišjo višino.",
|
||||||
|
"repeater_cliHelpGetOwnerInfo": "Prikazuje niz z informacijami o lastniku.",
|
||||||
|
"repeater_cliHelpGetPathHashMode": "Prikaže način delovanja z hashjem poti (0/1/2).",
|
||||||
|
"repeater_cliHelpGetLoopDetect": "Prikazuje občutljivost na zaznavanje ciklov.",
|
||||||
|
"repeater_cliHelpGetAcl": "(Samo za serije) Navaja vnos za nadzor dostopa na ponovljalniku.",
|
||||||
|
"repeater_cliHelpGetBridgeEnabled": "Pokaže, ali je most omogočen.",
|
||||||
|
"repeater_cliHelpGetBridgeDelay": "Prikazuje zamik mosta v milisekundah.",
|
||||||
|
"repeater_cliHelpGetBridgeSource": "Pokaže, ali most prenaša pakete RX ali TX.",
|
||||||
|
"repeater_cliHelpGetBridgeBaud": "(Samo za most RS232) Prikazuje hitrost prenosa podatkov na mostu.",
|
||||||
|
"repeater_cliHelpGetBridgeChannel": "(Samo za most ESPNow) Prikazuje kanal WiFi mosta.",
|
||||||
|
"repeater_cliHelpGetBridgeSecret": "(Samo za most ESPNow) Prikazuje tajno, ki jo deli most.",
|
||||||
|
"repeater_cliHelpGetBootloaderVer": "(Samo za NRF52) Prikazuje različico programskega orodja.",
|
||||||
|
"repeater_cliHelpGetAdcMultiplier": "Prikazuje pomnoževalnik ADC (skaliranje napetosti baterije).",
|
||||||
|
"repeater_cliHelpGetPwrMgtSupport": "Navaja, ali ima uprava področje za upravljanje z energijo.",
|
||||||
|
"repeater_cliHelpGetPwrMgtSource": "Prikaže trenutni vir napajanja: zunanji ali baterija.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootReason": "Prikazuje najnovejšo razlog za ponovno nastavitve in izklop.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootMv": "Prikazuje napetost baterije v mV ob zagonu.",
|
||||||
|
"repeater_cliHelpSensorGet": "Prebere določeno vrednost senzorja preko tipke.",
|
||||||
|
"repeater_cliHelpSensorSet": "Ustvari prilagojeno nastavitev za senzor.",
|
||||||
|
"repeater_cliHelpSensorList": "Navaja vse nastavitve za uporabniške senzorje, razvrščene po želeni začetni indeksu.",
|
||||||
|
"repeater_cliHelpRegionDefault": "Prikaže trenutno privzeto območje.",
|
||||||
|
"repeater_cliHelpRegionDefaultSet": "Določi privzeto območje. Za izbris uporabite \"<null>\".",
|
||||||
|
"repeater_cliHelpRegionListAllowed": "Navaja regije, ki dovoljujejo promet v času poplav.",
|
||||||
|
"repeater_cliHelpRegionListDenied": "Navaja regije, ki preprečujejo promet zaradi poplav.",
|
||||||
|
"repeater_cliHelpStatsPackets": "(Samo za serijske povezave) Prikazuje statistiko na nivoju paketov.",
|
||||||
|
"repeater_cliHelpStatsRadio": "(Samo za serije) Prikazuje statistične podatke o radiju.",
|
||||||
|
"repeater_cliHelpStatsCore": "(Samo za serijske naprave) Prikazuje osnovne statistične podatke.",
|
||||||
|
"common_done": "Done",
|
||||||
|
"background_serviceTitle": "MeshCore running",
|
||||||
|
"background_serviceText": "Keeping BLE connected",
|
||||||
|
"appSettings_translationModelDeleted": "Deleted {name}",
|
||||||
|
"@appSettings_translationModelDeleted": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
|
||||||
|
"@appSettings_translationModelDeleteFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channels_channelUpdateFailed": "Failed to update channel: {error}",
|
||||||
|
"@channels_channelUpdateFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map_type": "Type",
|
||||||
|
"map_path": "Path",
|
||||||
|
"map_location": "Location",
|
||||||
|
"map_estLocation": "Est. Location",
|
||||||
|
"map_publicKey": "Public Key",
|
||||||
|
"map_publicKeyPrefixHint": "e.g. ab12",
|
||||||
|
"contact_typeChat": "Chat",
|
||||||
|
"contact_typeRepeater": "Repeater",
|
||||||
|
"contact_typeRoom": "Room",
|
||||||
|
"contact_typeSensor": "Sensor",
|
||||||
|
"contact_typeUnknown": "Unknown",
|
||||||
|
"channels_via": "via {path}",
|
||||||
|
"chat_score": "Score",
|
||||||
|
"settings_multiAck": "Več potrdil",
|
||||||
|
"map_sharedAt": "Deljeno",
|
||||||
|
"@losBlockedSpotChip": {
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@losSelectedObstructionDetails": {
|
||||||
|
"placeholders": {
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromA": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromB": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"losBlockedSpotsHint": "Dotaknite blokirano točko, da jo označite na zemljeplati.",
|
||||||
|
"losSelectedObstructionTitle": "Izbrano ovire",
|
||||||
|
"losBlockedSpotsTitle": "Zasedena parkirišča",
|
||||||
|
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
|
||||||
|
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).",
|
||||||
|
"settings_companionDebugLog": "Log zapis za odpravljanje napak",
|
||||||
|
"chat_markAsUnread": "Označiti kot neneobdelano",
|
||||||
|
"chat_newMessages": "Nove novice",
|
||||||
|
"settings_companionDebugLogSubtitle": "Navodila, odgovori in surova podatka za BLE/TCP/USB.",
|
||||||
|
"repeater_chanUtil": "Uporaba kanala",
|
||||||
|
"dialog_connectCompanion": "Povežite se s spremljevalnikom za dostop do funkcij ponavljalnika in strežnika sob.",
|
||||||
|
"dialog_disconnectedTitle": "Prekinjeno",
|
||||||
|
"dialog_disconnectedMessage": "Prekinjena povezava s vašim spre伴ovalcem.",
|
||||||
|
"contact_connectCompanion": "Povežite se s ponсоbnikom za dostop do funkcij pon 반복nika in strežnika prostorov."
|
||||||
}
|
}
|
||||||
|
|||||||
+488
-32
@@ -52,7 +52,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scanner_title": "MeshCore Open",
|
"scanner_title": "MeshCore – Öppen version",
|
||||||
"scanner_scanning": "Söker efter enheter...",
|
"scanner_scanning": "Söker efter enheter...",
|
||||||
"scanner_connecting": "Anslutning...",
|
"scanner_connecting": "Anslutning...",
|
||||||
"scanner_disconnecting": "Anslutning bryts...",
|
"scanner_disconnecting": "Anslutning bryts...",
|
||||||
@@ -104,6 +104,8 @@
|
|||||||
"settings_privacyModeEnabled": "Privatläget är aktiverat",
|
"settings_privacyModeEnabled": "Privatläget är aktiverat",
|
||||||
"settings_privacyModeDisabled": "Privatläge är avstängt",
|
"settings_privacyModeDisabled": "Privatläge är avstängt",
|
||||||
"settings_actions": "Åtgärder",
|
"settings_actions": "Åtgärder",
|
||||||
|
"settings_deleteAllPaths": "Delete All Paths",
|
||||||
|
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
|
||||||
"settings_sendAdvertisement": "Skicka Annons",
|
"settings_sendAdvertisement": "Skicka Annons",
|
||||||
"settings_sendAdvertisementSubtitle": "Sändning finns nu",
|
"settings_sendAdvertisementSubtitle": "Sändning finns nu",
|
||||||
"settings_advertisementSent": "Annons skickad",
|
"settings_advertisementSent": "Annons skickad",
|
||||||
@@ -121,7 +123,7 @@
|
|||||||
"settings_appDebugLog": "Appfelsökning",
|
"settings_appDebugLog": "Appfelsökning",
|
||||||
"settings_appDebugLogSubtitle": "Applikations felsökningsmeddelanden",
|
"settings_appDebugLogSubtitle": "Applikations felsökningsmeddelanden",
|
||||||
"settings_about": "Om",
|
"settings_about": "Om",
|
||||||
"settings_aboutVersion": "MeshCore Open v{version}",
|
"settings_aboutVersion": "MeshCore Open version {version}",
|
||||||
"@settings_aboutVersion": {
|
"@settings_aboutVersion": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"version": {
|
"version": {
|
||||||
@@ -146,7 +148,7 @@
|
|||||||
"settings_spreadingFactor": "Spreadingfaktor",
|
"settings_spreadingFactor": "Spreadingfaktor",
|
||||||
"settings_codingRate": "Kodningsgrad",
|
"settings_codingRate": "Kodningsgrad",
|
||||||
"settings_txPower": "TX-effekt (dBm)",
|
"settings_txPower": "TX-effekt (dBm)",
|
||||||
"settings_txPowerHelper": "0 - 22",
|
"settings_txPowerHelper": "0 – 22",
|
||||||
"settings_txPowerInvalid": "Ogiltig TX-effekt (0-22 dBm)",
|
"settings_txPowerInvalid": "Ogiltig TX-effekt (0-22 dBm)",
|
||||||
"settings_error": "Fel: {message}",
|
"settings_error": "Fel: {message}",
|
||||||
"@settings_error": {
|
"@settings_error": {
|
||||||
@@ -164,19 +166,19 @@
|
|||||||
"appSettings_themeDark": "Mörk",
|
"appSettings_themeDark": "Mörk",
|
||||||
"appSettings_language": "Språk",
|
"appSettings_language": "Språk",
|
||||||
"appSettings_languageSystem": "Systemstandard",
|
"appSettings_languageSystem": "Systemstandard",
|
||||||
"appSettings_languageEn": "English",
|
"appSettings_languageEn": "Engelska",
|
||||||
"appSettings_languageFr": "Français",
|
"appSettings_languageFr": "Franska",
|
||||||
"appSettings_languageEs": "Español",
|
"appSettings_languageEs": "Spanska",
|
||||||
"appSettings_languageDe": "Deutsch",
|
"appSettings_languageDe": "Tyskt",
|
||||||
"appSettings_languagePl": "Polski",
|
"appSettings_languagePl": "Polsk",
|
||||||
"appSettings_languageSl": "Slovenščina",
|
"appSettings_languageSl": "Sloveniska",
|
||||||
"appSettings_languagePt": "Português",
|
"appSettings_languagePt": "Portugisiska",
|
||||||
"appSettings_languageIt": "Italiano",
|
"appSettings_languageIt": "Italienska",
|
||||||
"appSettings_languageZh": "中文",
|
"appSettings_languageZh": "Kinesiska",
|
||||||
"appSettings_languageSv": "Svenska",
|
"appSettings_languageSv": "Svenska",
|
||||||
"appSettings_languageNl": "Nederlands",
|
"appSettings_languageNl": "Nederländska",
|
||||||
"appSettings_languageSk": "Slovenčina",
|
"appSettings_languageSk": "Sloveniska",
|
||||||
"appSettings_languageBg": "Български",
|
"appSettings_languageBg": "Bulgariska",
|
||||||
"appSettings_notifications": "Meddelanden",
|
"appSettings_notifications": "Meddelanden",
|
||||||
"appSettings_enableNotifications": "Aktivera Notifikationer",
|
"appSettings_enableNotifications": "Aktivera Notifikationer",
|
||||||
"appSettings_enableNotificationsSubtitle": "Ta emot notiser för meddelanden och reklam",
|
"appSettings_enableNotificationsSubtitle": "Ta emot notiser för meddelanden och reklam",
|
||||||
@@ -209,9 +211,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"appSettings_batteryChemistryConnectFirst": "Anslut till en enhet för att välja",
|
"appSettings_batteryChemistryConnectFirst": "Anslut till en enhet för att välja",
|
||||||
"appSettings_batteryNmc": "18650 NMC (3.0-4.2V)",
|
"appSettings_batteryNmc": "18650 NMC (3,0-4,2V)",
|
||||||
"appSettings_batteryLifepo4": "LiFePO4 (2,6–3,65V)",
|
"appSettings_batteryLifepo4": "LiFePO4 (2,6–3,65V)",
|
||||||
"appSettings_batteryLipo": "LiPo (3.0-4.2V)",
|
"appSettings_batteryLipo": "LiPo (3,0-4,2V)",
|
||||||
"appSettings_mapDisplay": "Kartvisning",
|
"appSettings_mapDisplay": "Kartvisning",
|
||||||
"appSettings_showRepeaters": "Visa återuppslag",
|
"appSettings_showRepeaters": "Visa återuppslag",
|
||||||
"appSettings_showRepeatersSubtitle": "Visa återspelsnoder på kartan",
|
"appSettings_showRepeatersSubtitle": "Visa återspelsnoder på kartan",
|
||||||
@@ -285,6 +287,7 @@
|
|||||||
"contacts_newGroup": "Ny grupp",
|
"contacts_newGroup": "Ny grupp",
|
||||||
"contacts_groupName": "Gruppnamn",
|
"contacts_groupName": "Gruppnamn",
|
||||||
"contacts_groupNameRequired": "Gruppnamnet är obligatoriskt",
|
"contacts_groupNameRequired": "Gruppnamnet är obligatoriskt",
|
||||||
|
"contacts_groupNameReserved": "Detta gruppnamn är reserverat",
|
||||||
"contacts_groupAlreadyExists": "Gruppen \"{name}\" finns redan.",
|
"contacts_groupAlreadyExists": "Gruppen \"{name}\" finns redan.",
|
||||||
"@contacts_groupAlreadyExists": {
|
"@contacts_groupAlreadyExists": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -336,11 +339,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_hashtagChannel": "Hashtagkanal",
|
|
||||||
"channels_public": "Offentligt",
|
"channels_public": "Offentligt",
|
||||||
"channels_private": "Privat",
|
"channels_private": "Privat",
|
||||||
"channels_publicChannel": "Allmänt kanal",
|
|
||||||
"channels_privateChannel": "Privat kanal",
|
|
||||||
"channels_editChannel": "Redigera kanal",
|
"channels_editChannel": "Redigera kanal",
|
||||||
"channels_muteChannel": "Tysta kanal",
|
"channels_muteChannel": "Tysta kanal",
|
||||||
"channels_unmuteChannel": "Slå på ljud för kanal",
|
"channels_unmuteChannel": "Slå på ljud för kanal",
|
||||||
@@ -366,7 +366,7 @@
|
|||||||
"channels_channelName": "Kanalnamn",
|
"channels_channelName": "Kanalnamn",
|
||||||
"channels_usePublicChannel": "Använd Publikkanal",
|
"channels_usePublicChannel": "Använd Publikkanal",
|
||||||
"channels_standardPublicPsk": "Standard allmän PSK",
|
"channels_standardPublicPsk": "Standard allmän PSK",
|
||||||
"channels_pskHex": "PSK (Hex)",
|
"channels_pskHex": "PSK (heks)",
|
||||||
"channels_generateRandomPsk": "Generera slumpmässig PSK",
|
"channels_generateRandomPsk": "Generera slumpmässig PSK",
|
||||||
"channels_enterChannelName": "Ange en kanalnamn",
|
"channels_enterChannelName": "Ange en kanalnamn",
|
||||||
"channels_pskMustBe32Hex": "PSK måste vara 32 hexadecimala tecken",
|
"channels_pskMustBe32Hex": "PSK måste vara 32 hexadecimala tecken",
|
||||||
@@ -387,6 +387,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_smazCompression": "SMAZ-komprimering",
|
"channels_smazCompression": "SMAZ-komprimering",
|
||||||
|
"channels_cyr2latCompression": "Cyr2Lat-komprimering",
|
||||||
|
"channels_cyr2latCompressionDscr": "Ersätter vissa kyrilliska tecken med latinska tecken när du skickar.",
|
||||||
|
"channels_cyr2latSettingsHeading": "Inställningar för Cyr2Lat",
|
||||||
|
"channels_cyr2latSettingsSubheading": "Ersättningslista",
|
||||||
|
"channels_cyr2latSettingsDscr": "Redigera JSON-konfigurationen för teckenersättning",
|
||||||
|
"channels_cyr2latSettingsDialogHint": "JSON-ersättningskarta",
|
||||||
|
"channels_cyr2latSettingsDialogWrongJSON": "Felaktig JSON: {error}",
|
||||||
|
"settings_cyr2latProfileAdd": "Lägg till Cyr2Lat-profil",
|
||||||
|
"settings_cyr2latProfileName": "Profilnamn",
|
||||||
|
"settings_cyr2latProfileNameEmpty": "Profilnamnet får inte vara tomt",
|
||||||
|
"settings_cyr2latProfileAdded": "Profilen har lagts till",
|
||||||
|
"settings_cyr2latProfileUpdated": "Profilen har uppdaterats",
|
||||||
|
"settings_cyr2latProfileEdit": "Redigera Cyr2Lat-profil",
|
||||||
|
"settings_cyr2latProfileDelete": "Ta bort Cyr2Lat-profil",
|
||||||
|
"settings_cyr2latProfileDeleted": "Profilen har tagits bort",
|
||||||
|
"settings_cyr2latProfileDeleteDscr": "Är du säker på att du vill ta bort profilen \"{name}\"?",
|
||||||
"channels_channelUpdated": "Kanalen \"{name}\" har uppdaterats",
|
"channels_channelUpdated": "Kanalen \"{name}\" har uppdaterats",
|
||||||
"@channels_channelUpdated": {
|
"@channels_channelUpdated": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -530,7 +546,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debugFrame_textTypeCli": "CLI",
|
"debugFrame_textTypeCli": "Kommandorad",
|
||||||
"debugFrame_textTypePlain": "Enkel",
|
"debugFrame_textTypePlain": "Enkel",
|
||||||
"debugFrame_text": "- Text: \"{text}\"",
|
"debugFrame_text": "- Text: \"{text}\"",
|
||||||
"@debugFrame_text": {
|
"@debugFrame_text": {
|
||||||
@@ -549,7 +565,7 @@
|
|||||||
"chat_pathHistoryFull": "Historisk sökväg är full. Ta bort poster för att lägga till nya.",
|
"chat_pathHistoryFull": "Historisk sökväg är full. Ta bort poster för att lägga till nya.",
|
||||||
"chat_hopSingular": "hoppa",
|
"chat_hopSingular": "hoppa",
|
||||||
"chat_hopPlural": "hoppar",
|
"chat_hopPlural": "hoppar",
|
||||||
"chat_hopsCount": "{count} {count, plural, =1{hop} other{hops}}",
|
"chat_hopsCount": "{count} {count, plural, =1{hopp} other{hopp} }",
|
||||||
"@chat_hopsCount": {
|
"@chat_hopsCount": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {
|
"count": {
|
||||||
@@ -880,9 +896,9 @@
|
|||||||
"repeater_managementTools": "Administrationsverktyg",
|
"repeater_managementTools": "Administrationsverktyg",
|
||||||
"repeater_status": "Status",
|
"repeater_status": "Status",
|
||||||
"repeater_statusSubtitle": "Visa återspolningsstatus, statistik och grannar",
|
"repeater_statusSubtitle": "Visa återspolningsstatus, statistik och grannar",
|
||||||
"repeater_telemetry": "Telemetry",
|
"repeater_telemetry": "Telemetri",
|
||||||
"repeater_telemetrySubtitle": "Visa telemetri för sensorer och systemstatistik",
|
"repeater_telemetrySubtitle": "Visa telemetri för sensorer och systemstatistik",
|
||||||
"repeater_cli": "CLI",
|
"repeater_cli": "Kommandoradgränssnitt",
|
||||||
"repeater_cliSubtitle": "Skicka kommandon till repetitorn",
|
"repeater_cliSubtitle": "Skicka kommandon till repetitorn",
|
||||||
"repeater_settings": "Inställningar",
|
"repeater_settings": "Inställningar",
|
||||||
"repeater_settingsSubtitle": "Konfigurera återspolarparametrar",
|
"repeater_settingsSubtitle": "Konfigurera återspolarparametrar",
|
||||||
@@ -991,7 +1007,7 @@
|
|||||||
"repeater_guestPasswordHelper": "Läs-skyddspassord",
|
"repeater_guestPasswordHelper": "Läs-skyddspassord",
|
||||||
"repeater_radioSettings": "Radioinställningar",
|
"repeater_radioSettings": "Radioinställningar",
|
||||||
"repeater_frequencyMhz": "Frekvens (MHz)",
|
"repeater_frequencyMhz": "Frekvens (MHz)",
|
||||||
"repeater_frequencyHelper": "300-2500 MHz",
|
"repeater_frequencyHelper": "300–2500 MHz",
|
||||||
"repeater_txPower": "TX Effekt",
|
"repeater_txPower": "TX Effekt",
|
||||||
"repeater_txPowerHelper": "1-30 dBm",
|
"repeater_txPowerHelper": "1-30 dBm",
|
||||||
"repeater_bandwidth": "Bandbredd",
|
"repeater_bandwidth": "Bandbredd",
|
||||||
@@ -1058,6 +1074,81 @@
|
|||||||
},
|
},
|
||||||
"repeater_confirm": "Bekräfta",
|
"repeater_confirm": "Bekräfta",
|
||||||
"repeater_settingsSaved": "Inställningarna sparades framgångsrikt.",
|
"repeater_settingsSaved": "Inställningarna sparades framgångsrikt.",
|
||||||
|
"repeater_rxGain": "Ökad RX-vinst",
|
||||||
|
"repeater_rxGainHelper": "Ökad känslighet, högre strömförbrukning (endast för SX1262/SX1268)",
|
||||||
|
"repeater_refreshRxGain": "Återställ förbättrad RX-signalstyrka",
|
||||||
|
"repeater_multiAcks": "Flera bekräftelser",
|
||||||
|
"repeater_multiAcksSubtitle": "Bekräfta meddelanden via flera olika kanaler för bättre leverans.",
|
||||||
|
"repeater_refreshMultiAcks": "Återställ flera ACK-meddelanden",
|
||||||
|
"repeater_networkHealth": "Nätverkets hälsa",
|
||||||
|
"repeater_loopDetect": "Identifiering av loopar",
|
||||||
|
"repeater_loopDetectHelper": "Skapa \"flödespaket\" som ser ut som att de bildar en loop (en återkommande krets).",
|
||||||
|
"repeater_loopDetectOff": "Av",
|
||||||
|
"repeater_loopDetectMinimal": "Minimal",
|
||||||
|
"repeater_loopDetectModerate": "Måttlig",
|
||||||
|
"repeater_loopDetectStrict": "Strikt",
|
||||||
|
"repeater_dutyCycle": "Arbetscykel",
|
||||||
|
"repeater_dutyCycleHelper": "Maximal procentandel av sändningstid",
|
||||||
|
"repeater_dutyCyclePercent": "{percent}%",
|
||||||
|
"@repeater_dutyCyclePercent": {
|
||||||
|
"placeholders": {
|
||||||
|
"percent": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_ownerInfo": "Information om operatören",
|
||||||
|
"repeater_ownerInfoHelper": "Offentliga metadata för denna sändare",
|
||||||
|
"repeater_refreshOwnerInfo": "Uppdatera information om personal",
|
||||||
|
"repeater_floodMax": "Maximala mängden humle",
|
||||||
|
"repeater_floodMaxHelper": "Maximalt antal hopp en paket kan färdas (0-64)",
|
||||||
|
"repeater_advancedSettings": "Avancerad",
|
||||||
|
"repeater_advancedSettingsSubtitle": "Ställjusteringsknappar för erfarna användare",
|
||||||
|
"repeater_pathHashMode": "Hash-läge för sökväg",
|
||||||
|
"repeater_pathHashModeHelper": "Byte används för att koda denna repeaters ID i taggar för att upptäcka loopar/flödesvägar. 0=1 byte (256 ID:n, upp till 64 hopp), 1=2 byte (65 000 ID:n, upp till 32 hopp), 2=3 byte (16 miljoner ID:n, upp till 21 hopp). Versioner 1.13 och äldre har stöd för multi-byte-vägar – endast en gång när nätverket är aktiverat (från och med version 1.14).",
|
||||||
|
"repeater_txDelay": "Försening i Flood TX",
|
||||||
|
"repeater_txDelayHelper": "Återöverföringsintervall för trafik under perioder med hög belastning, som en multiplikator av paketets överföringstid (0-2, standard 0,5). Högre värde = färre kollisioner, men långsammare leverans.",
|
||||||
|
"repeater_directTxDelay": "Direkt TX-fördröjning",
|
||||||
|
"repeater_directTxDelayHelper": "Återöverföringsintervall för direkt (icke-översvämmande) trafik, som en multiplikator av paketets överföringstid (0-2, standard 0,3).",
|
||||||
|
"repeater_intThresh": "Tröskelvärde för störning",
|
||||||
|
"repeater_intThreshHelper": "Tröskelvärdet har ställts in så att den filtrerar bort störningar som överstiger detta värde. 0 stänger av – aktivera endast om du ser RX-fel i ett störningsfyllt frekvensområde.",
|
||||||
|
"repeater_agcResetInterval": "Återställningsintervall för AGC",
|
||||||
|
"repeater_agcResetIntervalHelper": "Hur ofta ska man återställa radioens automatiska förstärkning för att återhämta sig från ett tillstånd där förstärkningen är fast? Sekunder, inställda till en multipel av 4. 0 stänger av periodiska återställningar.",
|
||||||
|
"repeater_actionsTitle": "Åtgärder",
|
||||||
|
"repeater_sendAdvert": "Skicka annons om översvämning",
|
||||||
|
"repeater_sendAdvertSubtitle": "Sänd en reklamfilm om översvämningar via nätverket.",
|
||||||
|
"repeater_sendAdvertZeroHop": "Skicka en annons som inte kräver någon mellanstopp",
|
||||||
|
"repeater_sendAdvertZeroHopSubtitle": "Sänd en reklamkampanj med en enda sändare (utan mellanliggande sändare).",
|
||||||
|
"repeater_clockSync": "Synkronisera klockan nu",
|
||||||
|
"repeater_clockSyncSubtitle": "Ställ din telefons tid till repeatern.",
|
||||||
|
"repeater_actionSucceeded": "{action} lyckades",
|
||||||
|
"@repeater_actionSucceeded": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_actionFailed": "{action} misslyckades: {error}",
|
||||||
|
"@repeater_actionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_settingsSavedRebootNeeded": "Inställningar sparade – starta om repetern för att tillämpa dem",
|
||||||
|
"repeater_settingsPartialFailure": "Vissa inställningar misslyckades: {failures}",
|
||||||
|
"@repeater_settingsPartialFailure": {
|
||||||
|
"placeholders": {
|
||||||
|
"failures": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repeater_errorSavingSettings": "Fel vid sparande av inställningar: {error}",
|
"repeater_errorSavingSettings": "Fel vid sparande av inställningar: {error}",
|
||||||
"@repeater_errorSavingSettings": {
|
"@repeater_errorSavingSettings": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1069,11 +1160,9 @@
|
|||||||
"repeater_refreshBasicSettings": "Återställ Grundläggande Inställningar",
|
"repeater_refreshBasicSettings": "Återställ Grundläggande Inställningar",
|
||||||
"repeater_refreshRadioSettings": "Återställ Radiosinställningar",
|
"repeater_refreshRadioSettings": "Återställ Radiosinställningar",
|
||||||
"repeater_refreshTxPower": "Återställ TX-effekt",
|
"repeater_refreshTxPower": "Återställ TX-effekt",
|
||||||
"repeater_refreshLocationSettings": "Uppdatera Lokationsinställningar",
|
|
||||||
"repeater_refreshPacketForwarding": "Återställ Paketväxling",
|
"repeater_refreshPacketForwarding": "Återställ Paketväxling",
|
||||||
"repeater_refreshGuestAccess": "Återställ Gäståtkomst",
|
"repeater_refreshGuestAccess": "Återställ Gäståtkomst",
|
||||||
"repeater_refreshPrivacyMode": "Återställ Sekretessläge",
|
"repeater_refreshPrivacyMode": "Återställ Sekretessläge",
|
||||||
"repeater_refreshAdvertisementSettings": "Återställ Annonsinställningar",
|
|
||||||
"repeater_refreshed": "{label} har uppdaterats",
|
"repeater_refreshed": "{label} har uppdaterats",
|
||||||
"@repeater_refreshed": {
|
"@repeater_refreshed": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1476,7 +1565,7 @@
|
|||||||
"community_qrTitle": "Dela Gemenskap",
|
"community_qrTitle": "Dela Gemenskap",
|
||||||
"community_qrInstructions": "Skanna denna QR-kod för att gå med i \"{name}\"",
|
"community_qrInstructions": "Skanna denna QR-kod för att gå med i \"{name}\"",
|
||||||
"community_hashtagPrivacyHint": "Community-hashtagkanaler kan endast nås av medlemmar i communityn",
|
"community_hashtagPrivacyHint": "Community-hashtagkanaler kan endast nås av medlemmar i communityn",
|
||||||
"community_hashtagChannel": "Community Hashtag",
|
"community_hashtagChannel": "Hashtag för gemenskapen",
|
||||||
"community_invalidQrCode": "Ogiltig community QR-kod",
|
"community_invalidQrCode": "Ogiltig community QR-kod",
|
||||||
"community_alreadyMember": "Är redan medlem",
|
"community_alreadyMember": "Är redan medlem",
|
||||||
"community_alreadyMemberMessage": "Du är redan medlem av \"{name}\".",
|
"community_alreadyMemberMessage": "Du är redan medlem av \"{name}\".",
|
||||||
@@ -1503,7 +1592,7 @@
|
|||||||
"community_regularHashtagDesc": "Offentlig hashtag (alla kan gå med)",
|
"community_regularHashtagDesc": "Offentlig hashtag (alla kan gå med)",
|
||||||
"community_communityHashtagDesc": "Endast för medlemmar",
|
"community_communityHashtagDesc": "Endast för medlemmar",
|
||||||
"community_forCommunity": "För {name}",
|
"community_forCommunity": "För {name}",
|
||||||
"community_communityHashtag": "Community Hashtag",
|
"community_communityHashtag": "Gemenskaps-hashtag",
|
||||||
"@community_regenerateSecretConfirm": {
|
"@community_regenerateSecretConfirm": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"name": {
|
"name": {
|
||||||
@@ -1550,7 +1639,7 @@
|
|||||||
"pathTrace_failed": "Sökvägsföljning misslyckades.",
|
"pathTrace_failed": "Sökvägsföljning misslyckades.",
|
||||||
"pathTrace_notAvailable": "Path trace ej tillgänglig.",
|
"pathTrace_notAvailable": "Path trace ej tillgänglig.",
|
||||||
"pathTrace_refreshTooltip": "Uppdatera Path Trace",
|
"pathTrace_refreshTooltip": "Uppdatera Path Trace",
|
||||||
"contacts_pathTrace": "Path Trace",
|
"contacts_pathTrace": "Spårning",
|
||||||
"contacts_ping": "Ping",
|
"contacts_ping": "Ping",
|
||||||
"contacts_repeaterPathTrace": "Vägspårning till repeater",
|
"contacts_repeaterPathTrace": "Vägspårning till repeater",
|
||||||
"contacts_repeaterPing": "Ping-repeater",
|
"contacts_repeaterPing": "Ping-repeater",
|
||||||
@@ -1859,5 +1948,372 @@
|
|||||||
"usbStatus_notConnected": "Välj en USB-enhet",
|
"usbStatus_notConnected": "Välj en USB-enhet",
|
||||||
"usbConnectionFailed": "Fel vid USB-anslutning: {error}",
|
"usbConnectionFailed": "Fel vid USB-anslutning: {error}",
|
||||||
"usbStatus_searching": "Söker efter USB-enheter...",
|
"usbStatus_searching": "Söker efter USB-enheter...",
|
||||||
"usbErrorConnectTimedOut": "Anslutningen har tidsutgått. Se till att enheten har rätt USB-firmware."
|
"usbErrorConnectTimedOut": "Anslutningen har tidsutgått. Se till att enheten har rätt USB-firmware.",
|
||||||
|
"@tcpStatus_connectingTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"endpoint": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tcpConnectionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tcpHostHint": "192.168.40.10",
|
||||||
|
"tcpHostLabel": "IP-adress",
|
||||||
|
"tcpScreenTitle": "Anslut via TCP",
|
||||||
|
"connectionChoiceTcpLabel": "TCP",
|
||||||
|
"tcpPortLabel": "Hamn",
|
||||||
|
"tcpPortHint": "5000",
|
||||||
|
"tcpStatus_notConnected": "Ange slutpunkt och anslut",
|
||||||
|
"tcpStatus_connectingTo": "Anslutning till {endpoint}...",
|
||||||
|
"tcpErrorHostRequired": "IP-adress krävs.",
|
||||||
|
"tcpErrorPortInvalid": "Porten måste vara mellan 1 och 65535.",
|
||||||
|
"tcpErrorUnsupported": "TCP-transport fungerar inte på denna plattform.",
|
||||||
|
"tcpErrorTimedOut": "TCP-anslutningen har tidsut gått.",
|
||||||
|
"tcpConnectionFailed": "Fel vid TCP-anslutning: {error}",
|
||||||
|
"map_showDiscoveryContacts": "Visa Discovery-kontakter",
|
||||||
|
"map_setAsMyLocation": "Ange som min plats",
|
||||||
|
"@path_routeWeight": {
|
||||||
|
"placeholders": {
|
||||||
|
"weight": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings_privacy": "Inställningar för sekretess",
|
||||||
|
"settings_allowAll": "Tillåt alla",
|
||||||
|
"settings_privacySubtitle": "Kontrollera vilken information som delas.",
|
||||||
|
"settings_telemetryEnvironmentMode": "Telemetri miljöläge",
|
||||||
|
"settings_telemetryBaseMode": "Telemetribasläge",
|
||||||
|
"settings_telemetryLocationMode": "Telemetritillstånd för plats",
|
||||||
|
"settings_advertLocation": "Annonsplacering",
|
||||||
|
"contact_info": "Kontaktinformation",
|
||||||
|
"contact_settings": "Kontaktinställningar",
|
||||||
|
"contact_telemetry": "Telemetri",
|
||||||
|
"settings_denyAll": "Neka alla",
|
||||||
|
"settings_allowByContact": "Tillåt via kontaktflaggor",
|
||||||
|
"settings_privacySettingsDescription": "Välj vilken information din enhet delar med andra.",
|
||||||
|
"contact_lastSeen": "Senast sedd",
|
||||||
|
"contact_clearChat": "Rensa Chatt",
|
||||||
|
"contact_teleEnv": "Telemetri Miljö",
|
||||||
|
"settings_advertLocationSubtitle": "Inkludera plats i annonsen",
|
||||||
|
"contact_teleEnvSubtitle": "Tillåt delning av miljösensordata",
|
||||||
|
"contact_teleBase": "Telemetribas",
|
||||||
|
"contact_teleBaseSubtitle": "Tillåt delning av batterinivå och grundläggande telemetri",
|
||||||
|
"contact_teleLoc": "Telemetridata plats",
|
||||||
|
"contact_teleLocSubtitle": "Tillåt delning av platsdata",
|
||||||
|
"appSettings_initialRouteWeightSubtitle": "Initial vikt för nyligen upptäckta vägar",
|
||||||
|
"appSettings_maxRouteWeight": "Maximalt tillåtet vikt för rutten",
|
||||||
|
"appSettings_maxRouteWeightSubtitle": "Maximal vikt som en leveransväg kan ackumulera från framgångsrika leveranser.",
|
||||||
|
"appSettings_initialRouteWeight": "Initial vikt för rutt",
|
||||||
|
"appSettings_routeWeightSuccessIncrement": "Ökning av vikt för framgång",
|
||||||
|
"appSettings_routeWeightSuccessIncrementSubtitle": "Vikt läggs till en väg efter en lyckad leverans.",
|
||||||
|
"appSettings_routeWeightFailureDecrement": "Minskning av vikten för misslyckande",
|
||||||
|
"appSettings_routeWeightFailureDecrementSubtitle": "Vikt som tagits bort från en väg efter ett misslyckat leveransförsök",
|
||||||
|
"appSettings_maxMessageRetries": "Maximalt antal försök",
|
||||||
|
"appSettings_maxMessageRetriesSubtitle": "Antal försök att skicka om ett meddelande innan det markeras som misslyckat.",
|
||||||
|
"path_routeWeight": "{weight}/{max}",
|
||||||
|
"settings_telemetryModeUpdated": "Telemetri-läge uppdaterat",
|
||||||
|
"settings_multiAck": "Flera bekräftelser",
|
||||||
|
"map_showOverlaps": "Repeater-nyckelöverlappningar",
|
||||||
|
"map_runTraceWithReturnPath": "Gå tillbaka på samma väg",
|
||||||
|
"@radioStats_noiseFloor": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastRssi": {
|
||||||
|
"placeholders": {
|
||||||
|
"rssiDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastSnr": {
|
||||||
|
"placeholders": {
|
||||||
|
"snr": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_txAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_rxAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_stripNoise": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_jumpToOldestUnreadSubtitle": "När du öppnar en chatt med oinlästa meddelanden, scrolla till det första oinlästa meddelandet istället för det senaste.",
|
||||||
|
"chat_sendCooldown": "Vänligen vänta en stund innan du skickar igen.",
|
||||||
|
"appSettings_jumpToOldestUnread": "Gå direkt till det äldsta, obesvarade meddelandet",
|
||||||
|
"appSettings_languageHu": "Ungerskt",
|
||||||
|
"appSettings_languageJa": "Japanska",
|
||||||
|
"appSettings_languageKo": "Koreanska",
|
||||||
|
"radioStats_tooltip": "Radio- och mesh-statistik",
|
||||||
|
"radioStats_screenTitle": "Radiostation",
|
||||||
|
"radioStats_notConnected": "Anslut till en enhet för att visa radiostatistik.",
|
||||||
|
"radioStats_firmwareTooOld": "Radio statistik kräver kompatibel firmware version 8 eller senare.",
|
||||||
|
"radioStats_waiting": "Väntar på data…",
|
||||||
|
"radioStats_noiseFloor": "Bakgrundsnivå: {noiseDbm} dBm",
|
||||||
|
"radioStats_lastRssi": "Senaste RSSI-värde: {rssiDbm} dBm",
|
||||||
|
"radioStats_lastSnr": "Senaste SNR: {snr} dB",
|
||||||
|
"radioStats_txAir": "TX-tid (total): {seconds} sekunder",
|
||||||
|
"radioStats_rxAir": "RX-tid (total): {seconds} s",
|
||||||
|
"radioStats_chartCaption": "Ljudnivå (dBm) baserat på de senaste mätningarna.",
|
||||||
|
"radioStats_stripNoise": "Bakgrundsnivå: {noiseDbm} dBm",
|
||||||
|
"radioStats_stripWaiting": "Hämtar radiostatistik…",
|
||||||
|
"radioStats_settingsTile": "Radiostation",
|
||||||
|
"radioStats_settingsSubtitle": "Bakgrundsnivå, RSSI, SNR och tillgänglig tid",
|
||||||
|
"@translation_downloadFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_enableSubtitle": "Översätt inkommande meddelanden och möjliggör översättning före avsändning.",
|
||||||
|
"translation_enableTitle": "Aktivera översättning",
|
||||||
|
"translation_title": "Översättning",
|
||||||
|
"translation_composerTitle": "Översätt innan du skickar",
|
||||||
|
"translation_composerSubtitle": "Styr standardtillståndet för kompositorns översättningsikon.",
|
||||||
|
"translation_targetLanguage": "Målmedvetet språk",
|
||||||
|
"translation_useAppLanguage": "Använd appens språk",
|
||||||
|
"translation_downloadedModelLabel": "Nedladdad modell",
|
||||||
|
"translation_presetModelLabel": "Fördefinierat Hugging Face-modell",
|
||||||
|
"translation_manualUrlLabel": "Manualens URL",
|
||||||
|
"translation_downloadModel": "Ladda ner modellen",
|
||||||
|
"translation_downloading": "Nedladdning...",
|
||||||
|
"translation_working": "Arbeta...",
|
||||||
|
"translation_stop": "Stopp",
|
||||||
|
"translation_mergingChunks": "Slå samman de nedladdade delarna till en slutlig fil...",
|
||||||
|
"translation_downloadedModels": "Nedladdade modeller",
|
||||||
|
"translation_deleteModel": "Ta bort modell",
|
||||||
|
"translation_modelDownloaded": "Översättningsmodellen har laddats ner.",
|
||||||
|
"translation_downloadStopped": "Nedladdningen avbruten.",
|
||||||
|
"translation_downloadFailed": "Nedladdning misslyckades: {error}",
|
||||||
|
"translation_enterUrlFirst": "Ange först en URL för en specifik modell.",
|
||||||
|
"@scanner_linuxPairingPinPrompt": {
|
||||||
|
"placeholders": {
|
||||||
|
"deviceName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@translation_translateTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"language": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_composerDisabledHint": "Skicka meddelanden på det ursprungliga, stavade språket.",
|
||||||
|
"translation_translateBeforeSending": "Översätt innan du skickar",
|
||||||
|
"translation_composerEnabledHint": "Meddelandena kommer att översättas innan de skickas.",
|
||||||
|
"translation_messageTranslation": "Meddelandets översättning",
|
||||||
|
"translation_translateTo": "Översätt till {language}",
|
||||||
|
"translation_translationOptions": "Översättningsalternativ",
|
||||||
|
"translation_systemLanguage": "Språk för systemet",
|
||||||
|
"scanner_linuxPairingShowPin": "Visa PIN",
|
||||||
|
"scanner_linuxPairingPinTitle": "Bluetooth‑parnings‑PIN",
|
||||||
|
"scanner_linuxPairingPinPrompt": "Ange PIN för {deviceName} (lämna tomt om ingen).",
|
||||||
|
"scanner_linuxPairingHidePin": "Dölj PIN",
|
||||||
|
"repeater_cliQuickDiscovery": "Upptäck grannar",
|
||||||
|
"repeater_cliQuickClockSync": "Synkronisera klocka",
|
||||||
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "Automatiskt skicka \"klocksynkronisering\" efter en lyckad inloggning.",
|
||||||
|
"repeater_clockSyncAfterLogin": "Synkronisera klockan efter inloggning",
|
||||||
|
"repeater_guest": "Information om repetorer",
|
||||||
|
"chat_sendMessage": "Skicka meddelande",
|
||||||
|
"repeater_guestTools": "Gästverktyg",
|
||||||
|
"room_guest": "Information om servern",
|
||||||
|
"repeater_getCategory": "Hämta värden",
|
||||||
|
"repeater_powerMgmt": "Effektstyrning",
|
||||||
|
"repeater_sensors": "Sensorer",
|
||||||
|
"repeater_cliHelpPowerOff": "Stänger av enheten. (ingen respons förväntas)",
|
||||||
|
"repeater_cliHelpClkReboot": "Återställer klockan till en känd tidpunkt och startar om enheten.",
|
||||||
|
"repeater_cliHelpAdvertZeroHop": "Skickar en annons som når endast direkt grannar (endast närmaste grannar).",
|
||||||
|
"repeater_cliHelpStartOta": "Startar en firmware-uppdatering via luft, på kompatibla enheter.",
|
||||||
|
"repeater_cliHelpTime": "Ställer enheten till den angivna Unix-epokens tid. Klockan kan inte gå bakåt.",
|
||||||
|
"repeater_cliHelpBoard": "Visar tillverkaren av moderkortet / hårdvaru-identifieraren.",
|
||||||
|
"repeater_cliHelpDiscoverNeighbors": "Skickar en förfrågan om att upptäcka närliggande noder. (Endast för repetrar)",
|
||||||
|
"repeater_cliHelpPowersaving": "Visar om energisparläget är aktiverat eller avstängt.",
|
||||||
|
"repeater_cliHelpPowersavingOnOff": "Aktiverar eller inaktiverar energisparläget (om det stöds).",
|
||||||
|
"repeater_cliHelpErase": "(Endast för seriell kommunikation) Formaterar enhetens filsystem. Raderar alla inställningar och kontakter.",
|
||||||
|
"repeater_cliHelpSetDutyCycle": "Anger den maximala tillåtna överföringsfrekvensen som en procent (1-100). Justerar automatiskt tidsfaktorn.",
|
||||||
|
"repeater_cliHelpSetPrvKey": "(Endast för seriell användning) Ersätter enhetsens privata nyckel. Återstart krävs för att tillämpa. Genererar en ny publik nyckel.",
|
||||||
|
"repeater_cliHelpSetRadioRxGain": "(Endast SX126x) Aktiverar förstärkt mottagargain för förbättrad känslighet vid högre strömförbrukning.",
|
||||||
|
"repeater_cliHelpSetOwnerInfo": "Anger kontaktinformationen som ska inkluderas i annonserna. Använd '|' för att separera olika fält.",
|
||||||
|
"repeater_cliHelpSetPathHashMode": "Definierar läget för hash-baserad ruttning. 0 = äldre läge, 1 = standard, 2 = strikt. Påverkar hur ruttvägar matchas.",
|
||||||
|
"repeater_cliHelpSetLoopDetect": "Ställer in känsligheten för att detektera loopar i routningen: av, minimal, måttlig eller strikt.",
|
||||||
|
"repeater_cliHelpSetFreq": "(Endast för seriell kommunikation) Ställer snabbt bara frekvensen. Kräver omstart. Föredrar \"ställ radio\" för att få full kontroll över radioinställningarna.",
|
||||||
|
"repeater_cliHelpSetBridgeChannel": "(Endast ESPNow-brygga) Anger WiFi-kanalen (1-14) som används av bryggan.",
|
||||||
|
"repeater_cliHelpGetName": "Visar det konfigurerade nodnamnet.",
|
||||||
|
"repeater_cliHelpGetRole": "Visar firmware-funktionen (Repeater, Room Server, etc.).",
|
||||||
|
"repeater_cliHelpGetPublicKey": "Visar enhetens publika nyckel.",
|
||||||
|
"repeater_cliHelpGetPrvKey": "(Endast för seriell användning) Visar enheters privata nyckel. Behandla detta som en hemlighet.",
|
||||||
|
"repeater_cliHelpGetRepeat": "Visar om funktionen för att vidarebefordra paket (som en repeater) är aktiverad eller inaktiverad.",
|
||||||
|
"repeater_cliHelpGetTx": "Visar aktuell TX-effekt i dBm.",
|
||||||
|
"repeater_cliHelpGetFreq": "Visar den konfigurerade radiovågen i MHz.",
|
||||||
|
"repeater_cliHelpGetRadio": "Visar alla radioparametrar: frekvens, bandbredd, spridningsfaktor, kodningshastighet.",
|
||||||
|
"repeater_cliHelpGetRadioRxGain": "(Endast för SX126x) Visar RX:s förstärkningstillstånd.",
|
||||||
|
"repeater_cliHelpGetAf": "Visar aktuell tidssats.",
|
||||||
|
"repeater_cliHelpGetDutyCycle": "Visar den aktuella tillåtna arbetscykeln i procent.",
|
||||||
|
"repeater_cliHelpGetIntThresh": "Visar gränsen för kanalinterferens i dB.",
|
||||||
|
"repeater_cliHelpGetAgcResetInterval": "Visar återställningsintervallet för AGC i sekunder.",
|
||||||
|
"repeater_cliHelpGetMultiAcks": "Visar om dubbelbekräftelseläget är aktiverat (1) eller avstängt (0).",
|
||||||
|
"repeater_cliHelpGetAllowReadOnly": "Visar om gäst har tillåtelse att endast läsa.",
|
||||||
|
"repeater_cliHelpGetAdvertInterval": "Visar den lokala reklampausens längd i minuter.",
|
||||||
|
"repeater_cliHelpGetFloodAdvertInterval": "Visar tidsintervallet för reklamsegmentet under översvämningen, i timmar.",
|
||||||
|
"repeater_cliHelpGetGuestPassword": "Visar det angivna gästlösen.",
|
||||||
|
"repeater_cliHelpGetLat": "Visar den angivna latituden.",
|
||||||
|
"repeater_cliHelpGetLon": "Visar den angivna longituden.",
|
||||||
|
"repeater_cliHelpGetRxDelay": "Visar grundvärdet för rxdelay.",
|
||||||
|
"repeater_cliHelpGetTxDelay": "Visar faktor för fördröjning i flödesläge.",
|
||||||
|
"repeater_cliHelpGetDirectTxDelay": "Visar faktorn för fördröjning i direktläge.",
|
||||||
|
"repeater_cliHelpGetFloodMax": "Visar det maximala antalet gånger en översvämning har inträffat.",
|
||||||
|
"repeater_cliHelpGetOwnerInfo": "Visar strängen med kontaktinformation för ägaren.",
|
||||||
|
"repeater_cliHelpGetPathHashMode": "Visar hash-läge (0/1/2).",
|
||||||
|
"repeater_cliHelpGetLoopDetect": "Visar känsligheten för att detektera loopar.",
|
||||||
|
"repeater_cliHelpGetAcl": "(Endast för serier) Visar åtkomstkontrollinställningarna för en repeater.",
|
||||||
|
"repeater_cliHelpGetBridgeEnabled": "Visar om bron är aktiverad.",
|
||||||
|
"repeater_cliHelpGetBridgeDelay": "Visar fördröjningen i bron i millisekunder.",
|
||||||
|
"repeater_cliHelpGetBridgeSource": "Visar om bron skickar RX- eller TX-paket.",
|
||||||
|
"repeater_cliHelpGetBridgeBaud": "(Enbart RS232-brygga) Visar bryggans baud-hastighet.",
|
||||||
|
"repeater_cliHelpGetBridgeChannel": "(Endast ESPNow-brygga) Visar WiFi-kanal för bryggan.",
|
||||||
|
"repeater_cliHelpGetBridgeSecret": "(Endast ESPNow-brygga) Visar bryggans delade hemlighet.",
|
||||||
|
"repeater_cliHelpGetBootloaderVer": "(Endast för NRF52) Visar versionen av bootloadern.",
|
||||||
|
"repeater_cliHelpGetAdcMultiplier": "Visar ADC-multiplikatorn (skalning av batterispänning).",
|
||||||
|
"repeater_cliHelpGetPwrMgtSupport": "Anger om styrelsen har stöd för energihantering.",
|
||||||
|
"repeater_cliHelpGetPwrMgtSource": "Visar aktuell strömkälla: extern eller batteri.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootReason": "Visar de senaste orsakerna till återställning och avstängning.",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootMv": "Visar batterispänningen vid start i millivolt (mV).",
|
||||||
|
"repeater_cliHelpSensorGet": "Läser en anpassad sensorinställning via tangentbordet.",
|
||||||
|
"repeater_cliHelpSensorSet": "Skapar en anpassad inställning för en sensor.",
|
||||||
|
"repeater_cliHelpSensorList": "Visar alla anpassade sensorinställningar, sorterade från ett valfritt startindex.",
|
||||||
|
"repeater_cliHelpRegionDefault": "Visar det aktuella standardområde.",
|
||||||
|
"repeater_cliHelpRegionDefaultSet": "Definierar standardområde. Använd \"<null>\" för att återställa till standard.",
|
||||||
|
"repeater_cliHelpRegionListAllowed": "Lista områden där det är tillåtet med trafik under översvämningsförhållanden.",
|
||||||
|
"repeater_cliHelpRegionListDenied": "Listar områden där trafik på grund av översvämningar är förbjuden.",
|
||||||
|
"repeater_cliHelpStatsPackets": "(Endast för seriell kommunikation) Visar statistik på paketnivå.",
|
||||||
|
"repeater_cliHelpStatsRadio": "(Enbart för serier) Visar radiostatistik.",
|
||||||
|
"repeater_cliHelpStatsCore": "(Enbart för seriell kommunikation) Visar grundläggande firmware-statistik.",
|
||||||
|
"common_done": "Done",
|
||||||
|
"background_serviceTitle": "MeshCore running",
|
||||||
|
"background_serviceText": "Keeping BLE connected",
|
||||||
|
"appSettings_translationModelDeleted": "Deleted {name}",
|
||||||
|
"@appSettings_translationModelDeleted": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
|
||||||
|
"@appSettings_translationModelDeleteFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channels_channelUpdateFailed": "Failed to update channel: {error}",
|
||||||
|
"@channels_channelUpdateFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map_type": "Type",
|
||||||
|
"map_path": "Path",
|
||||||
|
"map_location": "Location",
|
||||||
|
"map_estLocation": "Est. Location",
|
||||||
|
"map_publicKey": "Public Key",
|
||||||
|
"map_publicKeyPrefixHint": "e.g. ab12",
|
||||||
|
"contact_typeChat": "Chat",
|
||||||
|
"contact_typeRepeater": "Repeater",
|
||||||
|
"contact_typeRoom": "Room",
|
||||||
|
"contact_typeSensor": "Sensor",
|
||||||
|
"contact_typeUnknown": "Unknown",
|
||||||
|
"channels_via": "via {path}",
|
||||||
|
"chat_score": "Score",
|
||||||
|
"map_sharedAt": "Delad",
|
||||||
|
"@losBlockedSpotChip": {
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@losSelectedObstructionDetails": {
|
||||||
|
"placeholders": {
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromA": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromB": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
|
||||||
|
"losBlockedSpotsTitle": "Reserverade platser",
|
||||||
|
"losSelectedObstructionTitle": "Vald hinder",
|
||||||
|
"losBlockedSpotsHint": "Klicka på en markerad plats för att framhäva den på kartan.",
|
||||||
|
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).",
|
||||||
|
"chat_markAsUnread": "Markera som oläst",
|
||||||
|
"settings_companionDebugLog": "Följande felsökningslogg",
|
||||||
|
"chat_newMessages": "Nya meddelanden",
|
||||||
|
"settings_companionDebugLogSubtitle": "BLE/TCP/USB-kommandon, svar och rådata",
|
||||||
|
"repeater_chanUtil": "Användning av kanal",
|
||||||
|
"dialog_connectCompanion": "Anslut till en sällskapstjänst för att komma åt upprepning och rumsserverfunktioner.",
|
||||||
|
"dialog_disconnectedTitle": "Ansluten ej",
|
||||||
|
"dialog_disconnectedMessage": "Du har kopplats från din companion.",
|
||||||
|
"contact_connectCompanion": "Anslut till en companion för att få tillgång till repeater- och rumsserverfunktioner."
|
||||||
}
|
}
|
||||||
|
|||||||
+605
-169
File diff suppressed because it is too large
Load Diff
+468
-12
@@ -79,7 +79,7 @@
|
|||||||
"scanner_stop": "停止",
|
"scanner_stop": "停止",
|
||||||
"scanner_scan": "扫描",
|
"scanner_scan": "扫描",
|
||||||
"device_quickSwitch": "快速切换",
|
"device_quickSwitch": "快速切换",
|
||||||
"device_meshcore": "MeshCore",
|
"device_meshcore": "网格核心",
|
||||||
"settings_title": "设置",
|
"settings_title": "设置",
|
||||||
"settings_deviceInfo": "设备信息",
|
"settings_deviceInfo": "设备信息",
|
||||||
"settings_appSettings": "应用设置",
|
"settings_appSettings": "应用设置",
|
||||||
@@ -109,6 +109,8 @@
|
|||||||
"settings_privacyModeEnabled": "隐私模式已启用",
|
"settings_privacyModeEnabled": "隐私模式已启用",
|
||||||
"settings_privacyModeDisabled": "隐私模式已关闭",
|
"settings_privacyModeDisabled": "隐私模式已关闭",
|
||||||
"settings_actions": "操作",
|
"settings_actions": "操作",
|
||||||
|
"settings_deleteAllPaths": "Delete All Paths",
|
||||||
|
"settings_deleteAllPathsSubtitle": "Clear all path data from contacts.",
|
||||||
"settings_sendAdvertisement": "发送广播",
|
"settings_sendAdvertisement": "发送广播",
|
||||||
"settings_sendAdvertisementSubtitle": "立即发送广播",
|
"settings_sendAdvertisementSubtitle": "立即发送广播",
|
||||||
"settings_advertisementSent": "已发送广播",
|
"settings_advertisementSent": "已发送广播",
|
||||||
@@ -300,6 +302,7 @@
|
|||||||
"contacts_newGroup": "新建群聊",
|
"contacts_newGroup": "新建群聊",
|
||||||
"contacts_groupName": "群聊名称",
|
"contacts_groupName": "群聊名称",
|
||||||
"contacts_groupNameRequired": "请输入群聊名称",
|
"contacts_groupNameRequired": "请输入群聊名称",
|
||||||
|
"contacts_groupNameReserved": "该群组名称已被保留",
|
||||||
"contacts_groupAlreadyExists": "名为 \"{name}\" 的群聊已存在",
|
"contacts_groupAlreadyExists": "名为 \"{name}\" 的群聊已存在",
|
||||||
"@contacts_groupAlreadyExists": {
|
"@contacts_groupAlreadyExists": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -351,11 +354,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_hashtagChannel": "标签频道",
|
|
||||||
"channels_public": "公共",
|
"channels_public": "公共",
|
||||||
"channels_private": "私有",
|
"channels_private": "私有",
|
||||||
"channels_publicChannel": "公共频道",
|
|
||||||
"channels_privateChannel": "私有频道",
|
|
||||||
"channels_editChannel": "编辑频道",
|
"channels_editChannel": "编辑频道",
|
||||||
"channels_muteChannel": "静音频道",
|
"channels_muteChannel": "静音频道",
|
||||||
"channels_unmuteChannel": "取消静音频道",
|
"channels_unmuteChannel": "取消静音频道",
|
||||||
@@ -402,6 +402,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"channels_smazCompression": "SMAZ 压缩",
|
"channels_smazCompression": "SMAZ 压缩",
|
||||||
|
"channels_cyr2latCompression": "Cyr2Lat 压缩",
|
||||||
|
"channels_cyr2latCompressionDscr": "发送时将一些西里尔字符替换为拉丁字符。",
|
||||||
|
"channels_cyr2latSettingsHeading": "Cyr2Lat 設定",
|
||||||
|
"channels_cyr2latSettingsSubheading": "替換清單",
|
||||||
|
"channels_cyr2latSettingsDscr": "編輯 JSON 字元替換設定檔",
|
||||||
|
"channels_cyr2latSettingsDialogHint": "JSON 替換映射表",
|
||||||
|
"channels_cyr2latSettingsDialogWrongJSON": "JSON 格式錯誤:{error}",
|
||||||
|
"settings_cyr2latProfileAdd": "新增 Cyr2Lat 設定檔",
|
||||||
|
"settings_cyr2latProfileName": "設定檔名稱",
|
||||||
|
"settings_cyr2latProfileNameEmpty": "設定檔名稱不能為空",
|
||||||
|
"settings_cyr2latProfileAdded": "設定檔已成功新增",
|
||||||
|
"settings_cyr2latProfileUpdated": "設定檔已成功更新",
|
||||||
|
"settings_cyr2latProfileEdit": "編輯 Cyr2Lat 設定檔",
|
||||||
|
"settings_cyr2latProfileDelete": "刪除 Cyr2Lat 設定檔",
|
||||||
|
"settings_cyr2latProfileDeleted": "設定檔已成功刪除",
|
||||||
|
"settings_cyr2latProfileDeleteDscr": "您確定要刪除設定檔 \"{name}\" 嗎?",
|
||||||
"channels_channelUpdated": "频道 \"{name}\" 已更新",
|
"channels_channelUpdated": "频道 \"{name}\" 已更新",
|
||||||
"@channels_channelUpdated": {
|
"@channels_channelUpdated": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -413,7 +429,7 @@
|
|||||||
"channels_publicChannelAdded": "已添加公共频道",
|
"channels_publicChannelAdded": "已添加公共频道",
|
||||||
"channels_sortBy": "排序方式",
|
"channels_sortBy": "排序方式",
|
||||||
"channels_sortManual": "手动",
|
"channels_sortManual": "手动",
|
||||||
"channels_sortAZ": "A-Z",
|
"channels_sortAZ": "A到Z",
|
||||||
"channels_sortLatestMessages": "最新消息",
|
"channels_sortLatestMessages": "最新消息",
|
||||||
"channels_sortUnread": "未读",
|
"channels_sortUnread": "未读",
|
||||||
"channels_createPrivateChannel": "创建私有频道",
|
"channels_createPrivateChannel": "创建私有频道",
|
||||||
@@ -1021,7 +1037,7 @@
|
|||||||
"repeater_guestPasswordHelper": "只读访问密码",
|
"repeater_guestPasswordHelper": "只读访问密码",
|
||||||
"repeater_radioSettings": "无线电设置",
|
"repeater_radioSettings": "无线电设置",
|
||||||
"repeater_frequencyMhz": "频率 (MHz)",
|
"repeater_frequencyMhz": "频率 (MHz)",
|
||||||
"repeater_frequencyHelper": "300-2500 MHz",
|
"repeater_frequencyHelper": "300-2500 兆赫",
|
||||||
"repeater_txPower": "TX 功率",
|
"repeater_txPower": "TX 功率",
|
||||||
"repeater_txPowerHelper": "1-30 dBm",
|
"repeater_txPowerHelper": "1-30 dBm",
|
||||||
"repeater_bandwidth": "带宽",
|
"repeater_bandwidth": "带宽",
|
||||||
@@ -1088,6 +1104,81 @@
|
|||||||
},
|
},
|
||||||
"repeater_confirm": "确认",
|
"repeater_confirm": "确认",
|
||||||
"repeater_settingsSaved": "设置保存成功",
|
"repeater_settingsSaved": "设置保存成功",
|
||||||
|
"repeater_rxGain": "增强的 RX 增益",
|
||||||
|
"repeater_rxGainHelper": "更高的灵敏度,更大的电流消耗(仅适用于 SX1262/SX1268)",
|
||||||
|
"repeater_refreshRxGain": "重新启动增强型 RX 功能",
|
||||||
|
"repeater_multiAcks": "多重确认",
|
||||||
|
"repeater_multiAcksSubtitle": "通过多个路径确认消息,以提高传递效率。",
|
||||||
|
"repeater_refreshMultiAcks": "刷新多个确认",
|
||||||
|
"repeater_networkHealth": "网络健康",
|
||||||
|
"repeater_loopDetect": "循环检测",
|
||||||
|
"repeater_loopDetectHelper": "创建看起来像路由环的“洪水包”",
|
||||||
|
"repeater_loopDetectOff": "离开",
|
||||||
|
"repeater_loopDetectMinimal": "最少",
|
||||||
|
"repeater_loopDetectModerate": "适度的",
|
||||||
|
"repeater_loopDetectStrict": "严格",
|
||||||
|
"repeater_dutyCycle": "工作周期",
|
||||||
|
"repeater_dutyCycleHelper": "最大可使用的空闲时间百分比",
|
||||||
|
"repeater_dutyCyclePercent": "{percent}%",
|
||||||
|
"@repeater_dutyCyclePercent": {
|
||||||
|
"placeholders": {
|
||||||
|
"percent": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_ownerInfo": "运营商信息",
|
||||||
|
"repeater_ownerInfoHelper": "此复播器的公共元数据",
|
||||||
|
"repeater_refreshOwnerInfo": "刷新操作员信息",
|
||||||
|
"repeater_floodMax": "最大跳跃次数",
|
||||||
|
"repeater_floodMaxHelper": "一个洪水包中,最大可以传输的跳数 (0-64)",
|
||||||
|
"repeater_advancedSettings": "高级",
|
||||||
|
"repeater_advancedSettingsSubtitle": "高级操作员使用的调节旋钮",
|
||||||
|
"repeater_pathHashMode": "路径哈希模式",
|
||||||
|
"repeater_pathHashModeHelper": "用于编码此复用器的 ID 的字节数,在“洪流路径/环检测”标签中使用。 0=1 字节(256 个 ID,最多 64 个跳跃),1=2 字节(65K 个 ID,最多 32 个跳跃),2=3 字节(16M 个 ID,最多 21 个跳跃)。 v1.13 及更早版本的固件会使用多字节路径——只有在您的网络升级到 v1.14 或更高版本后才会生效。",
|
||||||
|
"repeater_txDelay": "洪水(德克萨斯州)延误",
|
||||||
|
"repeater_txDelayHelper": "对于洪水流量,重新传输间隔应设置为包的传输时间(0-2,默认值为0.5)的倍数。 较高的值意味着更少的冲突,但传输速度会变慢。",
|
||||||
|
"repeater_directTxDelay": "直接的 TX 延迟",
|
||||||
|
"repeater_directTxDelayHelper": "对于直接(非广播)流量,重新传输间隔应设置为包的传输时间(0-2,默认值为0.3)的倍数。",
|
||||||
|
"repeater_intThresh": "干扰阈值",
|
||||||
|
"repeater_intThreshHelper": "将阈值传递给射频信号的噪声水平校准,使其能够拒绝高于该水平的干扰。 0 表示禁用——只有在您在嘈杂频段中看到 RX 错误时才启用。",
|
||||||
|
"repeater_agcResetInterval": "AGC 恢复间隔",
|
||||||
|
"repeater_agcResetIntervalHelper": "为了从失控的增益状态中恢复,应该多久重置收音机的自动增益控制?设置为“秒”,每次重置间隔为4秒。将此选项设置为“0”将禁用周期性重置。",
|
||||||
|
"repeater_actionsTitle": "行动",
|
||||||
|
"repeater_sendAdvert": "发布防洪广告",
|
||||||
|
"repeater_sendAdvertSubtitle": "通过网络播放防洪广告",
|
||||||
|
"repeater_sendAdvertZeroHop": "发送零跳广告",
|
||||||
|
"repeater_sendAdvertZeroHopSubtitle": "进行单跳广告广播(不使用中继)",
|
||||||
|
"repeater_clockSync": "现在同步时钟",
|
||||||
|
"repeater_clockSyncSubtitle": "将手机的时间设置为与中继器同步",
|
||||||
|
"repeater_actionSucceeded": "{action} 成功",
|
||||||
|
"@repeater_actionSucceeded": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_actionFailed": "{action} 失败:{error}",
|
||||||
|
"@repeater_actionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"action": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeater_settingsSavedRebootNeeded": "设置已保存 — 重启发射器以应用",
|
||||||
|
"repeater_settingsPartialFailure": "部分设置失败:{failures}",
|
||||||
|
"@repeater_settingsPartialFailure": {
|
||||||
|
"placeholders": {
|
||||||
|
"failures": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repeater_errorSavingSettings": "保存设置时出错:{error}",
|
"repeater_errorSavingSettings": "保存设置时出错:{error}",
|
||||||
"@repeater_errorSavingSettings": {
|
"@repeater_errorSavingSettings": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1099,11 +1190,9 @@
|
|||||||
"repeater_refreshBasicSettings": "刷新基本设置",
|
"repeater_refreshBasicSettings": "刷新基本设置",
|
||||||
"repeater_refreshRadioSettings": "刷新无线电设置",
|
"repeater_refreshRadioSettings": "刷新无线电设置",
|
||||||
"repeater_refreshTxPower": "刷新 TX 功率",
|
"repeater_refreshTxPower": "刷新 TX 功率",
|
||||||
"repeater_refreshLocationSettings": "刷新位置设置",
|
|
||||||
"repeater_refreshPacketForwarding": "刷新包转发",
|
"repeater_refreshPacketForwarding": "刷新包转发",
|
||||||
"repeater_refreshGuestAccess": "刷新访客权限",
|
"repeater_refreshGuestAccess": "刷新访客权限",
|
||||||
"repeater_refreshPrivacyMode": "刷新隐私模式",
|
"repeater_refreshPrivacyMode": "刷新隐私模式",
|
||||||
"repeater_refreshAdvertisementSettings": "刷新广播设置",
|
|
||||||
"repeater_refreshed": "{label} 已刷新",
|
"repeater_refreshed": "{label} 已刷新",
|
||||||
"@repeater_refreshed": {
|
"@repeater_refreshed": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -1246,7 +1335,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"telemetry_voltageValue": "{volts}V",
|
"telemetry_voltageValue": "{volts}伏",
|
||||||
"@telemetry_voltageValue": {
|
"@telemetry_voltageValue": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"volts": {
|
"volts": {
|
||||||
@@ -1540,7 +1629,7 @@
|
|||||||
"listFilter_sortBy": "排序方式",
|
"listFilter_sortBy": "排序方式",
|
||||||
"listFilter_latestMessages": "最新消息",
|
"listFilter_latestMessages": "最新消息",
|
||||||
"listFilter_heardRecently": "最近听到",
|
"listFilter_heardRecently": "最近听到",
|
||||||
"listFilter_az": "A-Z",
|
"listFilter_az": "A到Z",
|
||||||
"listFilter_filters": "筛选",
|
"listFilter_filters": "筛选",
|
||||||
"listFilter_all": "全部",
|
"listFilter_all": "全部",
|
||||||
"listFilter_users": "用户",
|
"listFilter_users": "用户",
|
||||||
@@ -1553,7 +1642,7 @@
|
|||||||
"pathTrace_notAvailable": "无法获取路径信息。",
|
"pathTrace_notAvailable": "无法获取路径信息。",
|
||||||
"pathTrace_refreshTooltip": "刷新路径追踪",
|
"pathTrace_refreshTooltip": "刷新路径追踪",
|
||||||
"contacts_pathTrace": "路径追踪",
|
"contacts_pathTrace": "路径追踪",
|
||||||
"contacts_ping": "Ping",
|
"contacts_ping": "乒",
|
||||||
"contacts_repeaterPathTrace": "Trace 转发节点",
|
"contacts_repeaterPathTrace": "Trace 转发节点",
|
||||||
"contacts_repeaterPing": "Ping 转发节点",
|
"contacts_repeaterPing": "Ping 转发节点",
|
||||||
"contacts_roomPathTrace": "Trace 房间服务器",
|
"contacts_roomPathTrace": "Trace 房间服务器",
|
||||||
@@ -1864,5 +1953,372 @@
|
|||||||
"usbStatus_connecting": "连接USB设备...",
|
"usbStatus_connecting": "连接USB设备...",
|
||||||
"usbStatus_notConnected": "选择一个 USB 设备",
|
"usbStatus_notConnected": "选择一个 USB 设备",
|
||||||
"usbConnectionFailed": "USB 连接失败:{error}",
|
"usbConnectionFailed": "USB 连接失败:{error}",
|
||||||
"usbErrorConnectTimedOut": "连接超时。请确保设备已安装 USB 伴侣固件。"
|
"usbErrorConnectTimedOut": "连接超时。请确保设备已安装 USB 伴侣固件。",
|
||||||
|
"@tcpStatus_connectingTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"endpoint": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tcpConnectionFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tcpHostLabel": "IP地址",
|
||||||
|
"tcpHostHint": "192.168.40.10",
|
||||||
|
"tcpScreenTitle": "通过 TCP 连接",
|
||||||
|
"connectionChoiceTcpLabel": "TCP",
|
||||||
|
"tcpPortLabel": "端口",
|
||||||
|
"tcpPortHint": "5000",
|
||||||
|
"tcpStatus_notConnected": "输入目标地址,然后连接",
|
||||||
|
"tcpStatus_connectingTo": "连接到 {endpoint}...",
|
||||||
|
"tcpErrorHostRequired": "需要提供IP地址。",
|
||||||
|
"tcpErrorPortInvalid": "端口号必须在 1 到 65535 之间。",
|
||||||
|
"tcpErrorUnsupported": "此平台不支持 TCP 传输。",
|
||||||
|
"tcpErrorTimedOut": "TCP 连接超时。",
|
||||||
|
"tcpConnectionFailed": "TCP 连接失败:{error}",
|
||||||
|
"map_showDiscoveryContacts": "显示发现联系人",
|
||||||
|
"map_setAsMyLocation": "设置为我的位置",
|
||||||
|
"@path_routeWeight": {
|
||||||
|
"placeholders": {
|
||||||
|
"weight": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings_privacySubtitle": "控制要共享的信息。",
|
||||||
|
"settings_privacySettingsDescription": "选择您的设备与他人共享的信息。",
|
||||||
|
"settings_telemetryBaseMode": "遥测基础模式",
|
||||||
|
"settings_telemetryLocationMode": "遥测位置模式",
|
||||||
|
"settings_advertLocation": "广告位置",
|
||||||
|
"settings_advertLocationSubtitle": "在广告中包含位置",
|
||||||
|
"settings_allowByContact": "按联系人标志允许",
|
||||||
|
"settings_denyAll": "拒绝所有",
|
||||||
|
"settings_privacy": "隐私设置",
|
||||||
|
"settings_allowAll": "允许全部",
|
||||||
|
"contact_info": "联系信息",
|
||||||
|
"contact_teleBase": "遥测基站",
|
||||||
|
"contact_teleBaseSubtitle": "允许共享电池电量和基本遥测数据",
|
||||||
|
"settings_telemetryEnvironmentMode": "遥测环境模式",
|
||||||
|
"contact_teleLoc": "遥测位置",
|
||||||
|
"contact_teleEnv": "遥测环境",
|
||||||
|
"contact_teleEnvSubtitle": "允许共享环境传感器数据",
|
||||||
|
"contact_clearChat": "清除聊天记录",
|
||||||
|
"contact_lastSeen": "最近出现",
|
||||||
|
"contact_settings": "联系人设置",
|
||||||
|
"contact_teleLocSubtitle": "允许共享位置数据",
|
||||||
|
"contact_telemetry": "遥测数据",
|
||||||
|
"appSettings_maxRouteWeight": "最大路径重量",
|
||||||
|
"appSettings_initialRouteWeightSubtitle": "新发现路径的初始重量",
|
||||||
|
"appSettings_initialRouteWeight": "初始路线权重",
|
||||||
|
"appSettings_maxRouteWeightSubtitle": "一条路径可以累积的最大重量,取决于成功交付的数量。",
|
||||||
|
"appSettings_routeWeightSuccessIncrement": "成功权重增加",
|
||||||
|
"appSettings_routeWeightSuccessIncrementSubtitle": "在成功交付后,将重量添加到路径中",
|
||||||
|
"appSettings_routeWeightFailureDecrement": "失败权重降低",
|
||||||
|
"appSettings_routeWeightFailureDecrementSubtitle": "从一条路径上移除的货物,由于无法成功交付而移除。",
|
||||||
|
"appSettings_maxMessageRetries": "最大消息重试次数",
|
||||||
|
"appSettings_maxMessageRetriesSubtitle": "在将消息标记为失败之前,允许尝试的次数",
|
||||||
|
"path_routeWeight": "{weight}/{max}",
|
||||||
|
"settings_multiAck": "多重ACK",
|
||||||
|
"settings_telemetryModeUpdated": "遥测模式已更新",
|
||||||
|
"map_showOverlaps": "重复键重叠",
|
||||||
|
"map_runTraceWithReturnPath": "沿着相同的路径返回",
|
||||||
|
"@radioStats_noiseFloor": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastRssi": {
|
||||||
|
"placeholders": {
|
||||||
|
"rssiDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_lastSnr": {
|
||||||
|
"placeholders": {
|
||||||
|
"snr": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_txAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_rxAir": {
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@radioStats_stripNoise": {
|
||||||
|
"placeholders": {
|
||||||
|
"noiseDbm": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chat_sendCooldown": "请稍等片刻后再尝试发送。",
|
||||||
|
"appSettings_jumpToOldestUnreadSubtitle": "在打开包含未读消息的聊天时,请滚动到第一个未读消息,而不是最新的消息。",
|
||||||
|
"appSettings_jumpToOldestUnread": "跳转到最旧、未读的文章",
|
||||||
|
"appSettings_languageHu": "匈牙利",
|
||||||
|
"appSettings_languageJa": "日语",
|
||||||
|
"appSettings_languageKo": "韩语",
|
||||||
|
"radioStats_tooltip": "无线电和网状结构统计数据",
|
||||||
|
"radioStats_screenTitle": "广播统计数据",
|
||||||
|
"radioStats_notConnected": "连接到设备以查看收音机统计信息。",
|
||||||
|
"radioStats_firmwareTooOld": "使用无线电统计功能需要配合使用 v8 或更高版本的固件。",
|
||||||
|
"radioStats_waiting": "正在等待数据…",
|
||||||
|
"radioStats_noiseFloor": "噪声水平:{noiseDbm} dBm",
|
||||||
|
"radioStats_lastRssi": "上次 RSSI 值:{rssiDbm} dBm",
|
||||||
|
"radioStats_lastSnr": "上次 SNR:{snr} dB",
|
||||||
|
"radioStats_txAir": "TX 频道播出时间(总时长):{seconds} 秒",
|
||||||
|
"radioStats_rxAir": "RX 使用时长(总时长):{seconds} 秒",
|
||||||
|
"radioStats_chartCaption": "近期的噪声水平(dBm)。",
|
||||||
|
"radioStats_stripNoise": "噪声水平:{noiseDbm} dBm",
|
||||||
|
"radioStats_stripWaiting": "正在获取收音机数据…",
|
||||||
|
"radioStats_settingsTile": "广播统计数据",
|
||||||
|
"radioStats_settingsSubtitle": "噪声水平、RSSI、信噪比和空中时间",
|
||||||
|
"@translation_downloadFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_title": "翻译",
|
||||||
|
"translation_enableSubtitle": "翻译收到的消息,并允许在发送前进行翻译。",
|
||||||
|
"translation_composerTitle": "在发送之前进行翻译",
|
||||||
|
"translation_enableTitle": "启用翻译功能",
|
||||||
|
"translation_composerSubtitle": "控制作曲家翻译图标的默认状态。",
|
||||||
|
"translation_targetLanguage": "目标语言",
|
||||||
|
"translation_useAppLanguage": "使用应用程序语言",
|
||||||
|
"translation_downloadedModelLabel": "下载的模型",
|
||||||
|
"translation_presetModelLabel": "预设的 Hugging Face 模型",
|
||||||
|
"translation_downloadModel": "下载模型",
|
||||||
|
"translation_manualUrlLabel": "手动模型网址",
|
||||||
|
"translation_downloading": "正在下载...",
|
||||||
|
"translation_working": "工作中...",
|
||||||
|
"translation_stop": "停止",
|
||||||
|
"translation_mergingChunks": "将下载的片段合并成最终文件...",
|
||||||
|
"translation_downloadedModels": "下载的模型",
|
||||||
|
"translation_deleteModel": "删除模型",
|
||||||
|
"translation_modelDownloaded": "翻译模型已下载。",
|
||||||
|
"translation_downloadStopped": "下载已停止。",
|
||||||
|
"translation_downloadFailed": "下载失败:{error}",
|
||||||
|
"translation_enterUrlFirst": "首先,请输入模型的 URL。",
|
||||||
|
"@scanner_linuxPairingPinPrompt": {
|
||||||
|
"placeholders": {
|
||||||
|
"deviceName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scanner_linuxPairingPinTitle": "蓝牙配对 PIN",
|
||||||
|
"scanner_linuxPairingPinPrompt": "输入 {deviceName} 的 PIN 码(如果为空,则留空)。",
|
||||||
|
"scanner_linuxPairingHidePin": "隐藏 PIN",
|
||||||
|
"scanner_linuxPairingShowPin": "显示PIN码",
|
||||||
|
"@translation_translateTo": {
|
||||||
|
"placeholders": {
|
||||||
|
"language": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation_composerDisabledHint": "使用原始的打字方式发送消息。",
|
||||||
|
"translation_messageTranslation": "消息翻译",
|
||||||
|
"translation_composerEnabledHint": "消息将在发送前进行翻译。",
|
||||||
|
"translation_translateBeforeSending": "在发送前进行翻译",
|
||||||
|
"translation_translateTo": "翻译成 {language}",
|
||||||
|
"translation_translationOptions": "翻译选项",
|
||||||
|
"translation_systemLanguage": "系统语言",
|
||||||
|
"repeater_cliQuickDiscovery": "发现邻居",
|
||||||
|
"repeater_cliQuickClockSync": "同步时钟",
|
||||||
|
"@repeater_clockSyncAfterLogin": {
|
||||||
|
"description": "Repeater setting: auto sync device clock after successful login"
|
||||||
|
},
|
||||||
|
"@repeater_clockSyncAfterLoginSubtitle": {
|
||||||
|
"description": "Repeater setting subtitle: describes the clock sync after login behavior"
|
||||||
|
},
|
||||||
|
"repeater_clockSyncAfterLogin": "登录后,自动同步时钟",
|
||||||
|
"repeater_clockSyncAfterLoginSubtitle": "在成功登录后,自动发送“时钟同步”指令。",
|
||||||
|
"repeater_guestTools": "访客工具",
|
||||||
|
"repeater_guest": "重复器信息",
|
||||||
|
"chat_sendMessage": "发送消息",
|
||||||
|
"room_guest": "服务器信息",
|
||||||
|
"repeater_getCategory": "获取值",
|
||||||
|
"repeater_powerMgmt": "电源管理",
|
||||||
|
"repeater_sensors": "传感器",
|
||||||
|
"repeater_cliHelpPowerOff": "关闭设备。(不应有任何响应)",
|
||||||
|
"repeater_cliHelpClkReboot": "将时钟重置为已知的时间点,并重启设备。",
|
||||||
|
"repeater_cliHelpAdvertZeroHop": "发送无中继广告(仅限于邻居)。",
|
||||||
|
"repeater_cliHelpStartOta": "在支持的板上启动通过空中进行固件更新。",
|
||||||
|
"repeater_cliHelpTime": "将设备时钟设置为给定的 Unix 纪元秒。时钟不能倒退。",
|
||||||
|
"repeater_cliHelpBoard": "显示制造商/硬件标识。",
|
||||||
|
"repeater_cliHelpDiscoverNeighbors": "向附近的邻居发送节点发现请求。(仅限中继器)",
|
||||||
|
"repeater_cliHelpPowersaving": "显示节能模式是否已开启或已关闭。",
|
||||||
|
"repeater_cliHelpPowersavingOnOff": "启用或禁用节能模式(如果支持)。",
|
||||||
|
"repeater_cliHelpErase": "(仅适用于序列模式)格式化设备的文件系统。清除所有设置和联系人。",
|
||||||
|
"repeater_cliHelpSetDutyCycle": "设定允许的最大传输时段百分比(1-100)。内部调整空闲时间因子。",
|
||||||
|
"repeater_cliHelpSetPrvKey": "(仅适用于序列号)替换设备身份私钥。需要重启才能应用。生成一个新的公钥。",
|
||||||
|
"repeater_cliHelpSetRadioRxGain": "(仅适用于 SX126x 芯片) 启用增强型 RX 增益,以在较高电流下提高灵敏度。",
|
||||||
|
"repeater_cliHelpSetOwnerInfo": "设置广告中包含的联系人信息字符串。使用 '|' 作为换行符。",
|
||||||
|
"repeater_cliHelpSetPathHashMode": "设置路径哈希模式。 0 = 传统模式,1 = 标准模式,2 = 严格模式。 影响路由路径的匹配方式。",
|
||||||
|
"repeater_cliHelpSetLoopDetect": "设置路由环检测的灵敏度:关闭、低、中、或高。",
|
||||||
|
"repeater_cliHelpSetFreq": "(仅限串行模式)快速设置频率。需要重启。 建议使用“设置收音机参数”功能,以便设置完整的收音机参数。",
|
||||||
|
"repeater_cliHelpSetBridgeChannel": "(仅适用于 ESPNow 桥)设置桥使用的 WiFi 频道(1-14)。",
|
||||||
|
"repeater_cliHelpGetName": "显示配置的节点名称。",
|
||||||
|
"repeater_cliHelpGetRole": "显示固件的功能(如:中继器、房间服务器等)。",
|
||||||
|
"repeater_cliHelpGetPublicKey": "显示设备的公钥。",
|
||||||
|
"repeater_cliHelpGetPrvKey": "(仅适用于序列号)显示设备的私钥。请将其视为机密信息。",
|
||||||
|
"repeater_cliHelpGetRepeat": "显示数据包转发(作为中继器)是否已启用或已禁用。",
|
||||||
|
"repeater_cliHelpGetTx": "显示当前的发射功率(以dBm为单位)。",
|
||||||
|
"repeater_cliHelpGetFreq": "显示配置的射频频率(以兆赫兹为单位)。",
|
||||||
|
"repeater_cliHelpGetRadio": "显示完整的无线电参数:频率、带宽、扩频因子、编码速率。",
|
||||||
|
"repeater_cliHelpGetRadioRxGain": "(仅适用于 SX126x 模块)显示 RX 放大器的状态。",
|
||||||
|
"repeater_cliHelpGetAf": "显示当前的空闲时间系数。",
|
||||||
|
"repeater_cliHelpGetDutyCycle": "显示当前允许的占空比(以百分比表示)。",
|
||||||
|
"repeater_cliHelpGetIntThresh": "显示信道干扰阈值(以dB为单位)。",
|
||||||
|
"repeater_cliHelpGetAgcResetInterval": "显示 AGC 重置的间隔时间(以秒为单位)。",
|
||||||
|
"repeater_cliHelpGetMultiAcks": "显示双重确认模式是否已启用(1)或已禁用(0)。",
|
||||||
|
"repeater_cliHelpGetAllowReadOnly": "显示是否允许访客仅限查看权限。",
|
||||||
|
"repeater_cliHelpGetAdvertInterval": "显示本地广告的时间间隔,单位为分钟。",
|
||||||
|
"repeater_cliHelpGetFloodAdvertInterval": "显示洪水广告的播放时间间隔,以小时为单位。",
|
||||||
|
"repeater_cliHelpGetGuestPassword": "显示已配置的访客密码。",
|
||||||
|
"repeater_cliHelpGetLat": "显示已配置的纬度。",
|
||||||
|
"repeater_cliHelpGetLon": "显示已配置的经度。",
|
||||||
|
"repeater_cliHelpGetRxDelay": "显示 rxdelay 的基本值。",
|
||||||
|
"repeater_cliHelpGetTxDelay": "显示洪水模式下的传输延迟系数。",
|
||||||
|
"repeater_cliHelpGetDirectTxDelay": "显示直接模式下的时延系数。",
|
||||||
|
"repeater_cliHelpGetFloodMax": "显示最大洪水传播次数。",
|
||||||
|
"repeater_cliHelpGetOwnerInfo": "显示所有者的联系信息。",
|
||||||
|
"repeater_cliHelpGetPathHashMode": "显示哈希模式(0/1/2)。",
|
||||||
|
"repeater_cliHelpGetLoopDetect": "显示循环检测的灵敏度。",
|
||||||
|
"repeater_cliHelpGetAcl": "(仅适用于序列号)列出复用器上的访问控制条目。",
|
||||||
|
"repeater_cliHelpGetBridgeEnabled": "显示桥是否已启用。",
|
||||||
|
"repeater_cliHelpGetBridgeDelay": "显示桥梁延迟的时间,单位为毫秒。",
|
||||||
|
"repeater_cliHelpGetBridgeSource": "显示桥接设备是否接收或发送 RX 或 TX 类型的数据包。",
|
||||||
|
"repeater_cliHelpGetBridgeBaud": "(仅限 RS232 桥)显示桥的波特率。",
|
||||||
|
"repeater_cliHelpGetBridgeChannel": "(仅适用于 ESPNow 桥)显示桥的 WiFi 通道。",
|
||||||
|
"repeater_cliHelpGetBridgeSecret": "(仅适用于 ESPNow 桥)显示桥的共享密钥。",
|
||||||
|
"repeater_cliHelpGetBootloaderVer": "(仅适用于NRF52)显示引导程序版本。",
|
||||||
|
"repeater_cliHelpGetAdcMultiplier": "显示 ADC 乘数(电池电压缩放)。",
|
||||||
|
"repeater_cliHelpGetPwrMgtSupport": "报告董事会是否支持电源管理功能。",
|
||||||
|
"repeater_cliHelpGetPwrMgtSource": "显示当前的电源:外部电源或电池。",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootReason": "显示最近的重置和关闭原因。",
|
||||||
|
"repeater_cliHelpGetPwrMgtBootMv": "显示启动时的电池电压,单位为毫伏 (mV)。",
|
||||||
|
"repeater_cliHelpSensorGet": "通过按键读取自定义传感器设置。",
|
||||||
|
"repeater_cliHelpSensorSet": "编写自定义传感器设置。",
|
||||||
|
"repeater_cliHelpSensorList": "列出所有自定义传感器设置,并按可选的起始索引进行分页显示。",
|
||||||
|
"repeater_cliHelpRegionDefault": "显示当前默认的区域范围。",
|
||||||
|
"repeater_cliHelpRegionDefaultSet": "设置默认的区域范围。使用 \"<null>\" 可以清除。",
|
||||||
|
"repeater_cliHelpRegionListAllowed": "列出允许洪水交通的区域。",
|
||||||
|
"repeater_cliHelpRegionListDenied": "列出禁止洪水交通的区域。",
|
||||||
|
"repeater_cliHelpStatsPackets": "(仅显示序列信息)显示数据包级别的统计信息。",
|
||||||
|
"repeater_cliHelpStatsRadio": "(仅显示序列信息)显示收音机相关统计数据。",
|
||||||
|
"repeater_cliHelpStatsCore": "(仅显示序列号)显示核心固件统计信息。",
|
||||||
|
"common_done": "Done",
|
||||||
|
"background_serviceTitle": "MeshCore running",
|
||||||
|
"background_serviceText": "Keeping BLE connected",
|
||||||
|
"appSettings_translationModelDeleted": "Deleted {name}",
|
||||||
|
"@appSettings_translationModelDeleted": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appSettings_translationModelDeleteFailed": "Failed to delete: {error}",
|
||||||
|
"@appSettings_translationModelDeleteFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channels_channelUpdateFailed": "Failed to update channel: {error}",
|
||||||
|
"@channels_channelUpdateFailed": {
|
||||||
|
"placeholders": {
|
||||||
|
"error": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"map_type": "Type",
|
||||||
|
"map_path": "Path",
|
||||||
|
"map_location": "Location",
|
||||||
|
"map_estLocation": "Est. Location",
|
||||||
|
"map_publicKey": "Public Key",
|
||||||
|
"map_publicKeyPrefixHint": "e.g. ab12",
|
||||||
|
"contact_typeChat": "Chat",
|
||||||
|
"contact_typeRepeater": "Repeater",
|
||||||
|
"contact_typeRoom": "Room",
|
||||||
|
"contact_typeSensor": "Sensor",
|
||||||
|
"contact_typeUnknown": "Unknown",
|
||||||
|
"channels_via": "via {path}",
|
||||||
|
"chat_score": "Score",
|
||||||
|
"map_sharedAt": "已分享",
|
||||||
|
"@losBlockedSpotChip": {
|
||||||
|
"placeholders": {
|
||||||
|
"distance": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@losSelectedObstructionDetails": {
|
||||||
|
"placeholders": {
|
||||||
|
"obstruction": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"heightUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromA": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceUnit": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"distanceFromB": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"losBlockedSpotsTitle": "被占用区域",
|
||||||
|
"losBlockedSpotsHint": "点击地图上的某个被遮盖的区域,以突出显示该区域。",
|
||||||
|
"losSelectedObstructionTitle": "选择性阻碍",
|
||||||
|
"losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}",
|
||||||
|
"losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).",
|
||||||
|
"chat_markAsUnread": "标记为未读",
|
||||||
|
"settings_companionDebugLog": "调试日志",
|
||||||
|
"chat_newMessages": "新的消息",
|
||||||
|
"settings_companionDebugLogSubtitle": "BLE/TCP/USB 协议、响应和原始数据",
|
||||||
|
"repeater_chanUtil": "频道利用率",
|
||||||
|
"dialog_connectCompanion": "连接伴机以访问中继器和房间服务器功能。",
|
||||||
|
"dialog_disconnectedTitle": "已断开连接",
|
||||||
|
"dialog_disconnectedMessage": "你已与你的伙伴断开连接。",
|
||||||
|
"contact_connectCompanion": "连接至伴侣设备以访问中继器和房间服务器功能。"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import '../connector/meshcore_protocol.dart';
|
||||||
|
import '../models/contact.dart';
|
||||||
|
import 'app_localizations.dart';
|
||||||
|
|
||||||
|
/// UI-level localization helpers for [Contact].
|
||||||
|
///
|
||||||
|
/// Kept out of the model layer so `Contact` does not depend on
|
||||||
|
/// `AppLocalizations`. Use these from widgets/screens; for logs and
|
||||||
|
/// non-UI export use `Contact.typeLabelRaw`.
|
||||||
|
extension ContactLocalization on Contact {
|
||||||
|
String typeLabel(AppLocalizations l10n) {
|
||||||
|
switch (type) {
|
||||||
|
case advTypeChat:
|
||||||
|
return l10n.contact_typeChat;
|
||||||
|
case advTypeRepeater:
|
||||||
|
return l10n.contact_typeRepeater;
|
||||||
|
case advTypeRoom:
|
||||||
|
return l10n.contact_typeRoom;
|
||||||
|
case advTypeSensor:
|
||||||
|
return l10n.contact_typeSensor;
|
||||||
|
default:
|
||||||
|
return l10n.contact_typeUnknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String pathLabel(AppLocalizations l10n) {
|
||||||
|
if (pathOverride != null) {
|
||||||
|
if (pathOverride! < 0) return l10n.chat_floodForced;
|
||||||
|
if (pathOverride == 0) return l10n.chat_directForced;
|
||||||
|
return l10n.chat_hopsForced(pathOverride!);
|
||||||
|
}
|
||||||
|
if (pathLength < 0) return l10n.channelPath_floodPath;
|
||||||
|
if (pathLength == 0) return l10n.chat_direct;
|
||||||
|
return l10n.chat_hopsCount(pathLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
-9
@@ -5,10 +5,10 @@ import 'l10n/app_localizations.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'screens/chrome_required_screen.dart';
|
import 'screens/chrome_required_screen.dart';
|
||||||
|
import 'screens/contacts_screen.dart';
|
||||||
import 'utils/platform_info.dart';
|
import 'utils/platform_info.dart';
|
||||||
|
|
||||||
import 'connector/meshcore_connector.dart';
|
import 'connector/meshcore_connector.dart';
|
||||||
import 'screens/scanner_screen.dart';
|
|
||||||
import 'services/storage_service.dart';
|
import 'services/storage_service.dart';
|
||||||
import 'services/message_retry_service.dart';
|
import 'services/message_retry_service.dart';
|
||||||
import 'services/path_history_service.dart';
|
import 'services/path_history_service.dart';
|
||||||
@@ -19,6 +19,9 @@ import 'services/app_debug_log_service.dart';
|
|||||||
import 'services/background_service.dart';
|
import 'services/background_service.dart';
|
||||||
import 'services/map_tile_cache_service.dart';
|
import 'services/map_tile_cache_service.dart';
|
||||||
import 'services/chat_text_scale_service.dart';
|
import 'services/chat_text_scale_service.dart';
|
||||||
|
import 'services/translation_service.dart';
|
||||||
|
import 'services/ui_view_state_service.dart';
|
||||||
|
import 'services/timeout_prediction_service.dart';
|
||||||
import 'storage/prefs_manager.dart';
|
import 'storage/prefs_manager.dart';
|
||||||
import 'utils/app_logger.dart';
|
import 'utils/app_logger.dart';
|
||||||
|
|
||||||
@@ -39,6 +42,9 @@ void main() async {
|
|||||||
final backgroundService = BackgroundService();
|
final backgroundService = BackgroundService();
|
||||||
final mapTileCacheService = MapTileCacheService();
|
final mapTileCacheService = MapTileCacheService();
|
||||||
final chatTextScaleService = ChatTextScaleService();
|
final chatTextScaleService = ChatTextScaleService();
|
||||||
|
final translationService = TranslationService(appSettingsService);
|
||||||
|
final uiViewStateService = UiViewStateService();
|
||||||
|
final timeoutPredictionService = TimeoutPredictionService(storage);
|
||||||
|
|
||||||
// Load settings
|
// Load settings
|
||||||
await appSettingsService.loadSettings();
|
await appSettingsService.loadSettings();
|
||||||
@@ -53,27 +59,30 @@ void main() async {
|
|||||||
final notificationService = NotificationService();
|
final notificationService = NotificationService();
|
||||||
await notificationService.initialize();
|
await notificationService.initialize();
|
||||||
await backgroundService.initialize();
|
await backgroundService.initialize();
|
||||||
|
backgroundService.setLanguageOverrideProvider(
|
||||||
|
() => appSettingsService.settings.languageOverride,
|
||||||
|
);
|
||||||
_registerThirdPartyLicenses();
|
_registerThirdPartyLicenses();
|
||||||
|
|
||||||
await chatTextScaleService.initialize();
|
await chatTextScaleService.initialize();
|
||||||
|
await translationService.refreshDownloadedModels();
|
||||||
|
await uiViewStateService.initialize();
|
||||||
|
await timeoutPredictionService.initialize();
|
||||||
|
|
||||||
// Wire up connector with services
|
// Wire up connector with services
|
||||||
connector.initialize(
|
connector.initialize(
|
||||||
retryService: retryService,
|
retryService: retryService,
|
||||||
pathHistoryService: pathHistoryService,
|
pathHistoryService: pathHistoryService,
|
||||||
appSettingsService: appSettingsService,
|
appSettingsService: appSettingsService,
|
||||||
|
translationService: translationService,
|
||||||
bleDebugLogService: bleDebugLogService,
|
bleDebugLogService: bleDebugLogService,
|
||||||
appDebugLogService: appDebugLogService,
|
appDebugLogService: appDebugLogService,
|
||||||
backgroundService: backgroundService,
|
backgroundService: backgroundService,
|
||||||
|
timeoutPredictionService: timeoutPredictionService,
|
||||||
);
|
);
|
||||||
|
|
||||||
await connector.loadContactCache();
|
await connector.restoreLastCompanionScope();
|
||||||
await connector.loadChannelSettings();
|
await connector.loadAllCachedDataForCurrentCompanion();
|
||||||
await connector.loadCachedChannels();
|
|
||||||
|
|
||||||
// Load persisted channel messages
|
|
||||||
await connector.loadAllChannelMessages();
|
|
||||||
await connector.loadUnreadState();
|
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MeshCoreApp(
|
MeshCoreApp(
|
||||||
@@ -86,6 +95,9 @@ void main() async {
|
|||||||
appDebugLogService: appDebugLogService,
|
appDebugLogService: appDebugLogService,
|
||||||
mapTileCacheService: mapTileCacheService,
|
mapTileCacheService: mapTileCacheService,
|
||||||
chatTextScaleService: chatTextScaleService,
|
chatTextScaleService: chatTextScaleService,
|
||||||
|
translationService: translationService,
|
||||||
|
uiViewStateService: uiViewStateService,
|
||||||
|
timeoutPredictionService: timeoutPredictionService,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -121,6 +133,9 @@ class MeshCoreApp extends StatelessWidget {
|
|||||||
final AppDebugLogService appDebugLogService;
|
final AppDebugLogService appDebugLogService;
|
||||||
final MapTileCacheService mapTileCacheService;
|
final MapTileCacheService mapTileCacheService;
|
||||||
final ChatTextScaleService chatTextScaleService;
|
final ChatTextScaleService chatTextScaleService;
|
||||||
|
final TranslationService translationService;
|
||||||
|
final UiViewStateService uiViewStateService;
|
||||||
|
final TimeoutPredictionService timeoutPredictionService;
|
||||||
|
|
||||||
const MeshCoreApp({
|
const MeshCoreApp({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -133,6 +148,9 @@ class MeshCoreApp extends StatelessWidget {
|
|||||||
required this.appDebugLogService,
|
required this.appDebugLogService,
|
||||||
required this.mapTileCacheService,
|
required this.mapTileCacheService,
|
||||||
required this.chatTextScaleService,
|
required this.chatTextScaleService,
|
||||||
|
required this.translationService,
|
||||||
|
required this.uiViewStateService,
|
||||||
|
required this.timeoutPredictionService,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -146,8 +164,11 @@ class MeshCoreApp extends StatelessWidget {
|
|||||||
ChangeNotifierProvider.value(value: bleDebugLogService),
|
ChangeNotifierProvider.value(value: bleDebugLogService),
|
||||||
ChangeNotifierProvider.value(value: appDebugLogService),
|
ChangeNotifierProvider.value(value: appDebugLogService),
|
||||||
ChangeNotifierProvider.value(value: chatTextScaleService),
|
ChangeNotifierProvider.value(value: chatTextScaleService),
|
||||||
|
ChangeNotifierProvider.value(value: translationService),
|
||||||
|
ChangeNotifierProvider.value(value: uiViewStateService),
|
||||||
Provider.value(value: storage),
|
Provider.value(value: storage),
|
||||||
Provider.value(value: mapTileCacheService),
|
Provider.value(value: mapTileCacheService),
|
||||||
|
ChangeNotifierProvider.value(value: timeoutPredictionService),
|
||||||
],
|
],
|
||||||
child: Consumer<AppSettingsService>(
|
child: Consumer<AppSettingsService>(
|
||||||
builder: (context, settingsService, child) {
|
builder: (context, settingsService, child) {
|
||||||
@@ -192,7 +213,7 @@ class MeshCoreApp extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
home: (PlatformInfo.isWeb && !PlatformInfo.isChrome)
|
home: (PlatformInfo.isWeb && !PlatformInfo.isChrome)
|
||||||
? const ChromeRequiredScreen()
|
? const ChromeRequiredScreen()
|
||||||
: const ScannerScreen(),
|
: const ContactsScreen(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'translation_support.dart';
|
||||||
|
|
||||||
enum UnitSystem { metric, imperial }
|
enum UnitSystem { metric, imperial }
|
||||||
|
|
||||||
extension UnitSystemValue on UnitSystem {
|
extension UnitSystemValue on UnitSystem {
|
||||||
@@ -11,6 +13,67 @@ extension UnitSystemValue on UnitSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Map<String, String> defaultCyr2LatCharMap = {
|
||||||
|
'А': 'A',
|
||||||
|
'В': 'B',
|
||||||
|
'Е': 'E',
|
||||||
|
'Ё': 'E',
|
||||||
|
'З': '3',
|
||||||
|
'К': 'K',
|
||||||
|
'М': 'M',
|
||||||
|
'Н': 'H',
|
||||||
|
'О': 'O',
|
||||||
|
'Р': 'P',
|
||||||
|
'С': 'C',
|
||||||
|
'Т': 'T',
|
||||||
|
'Х': 'X',
|
||||||
|
'Ь': 'b',
|
||||||
|
'а': 'a',
|
||||||
|
'е': 'e',
|
||||||
|
'ё': 'e',
|
||||||
|
'о': 'o',
|
||||||
|
'р': 'p',
|
||||||
|
'с': 'c',
|
||||||
|
'у': 'y',
|
||||||
|
'х': 'x',
|
||||||
|
};
|
||||||
|
|
||||||
|
class Cyr2LatProfile {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final Map<String, String> charMap;
|
||||||
|
|
||||||
|
Cyr2LatProfile({required this.id, required this.name, required this.charMap});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {'id': id, 'name': name, 'char_map': charMap};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Cyr2LatProfile.fromJson(Map<String, dynamic> json) {
|
||||||
|
return Cyr2LatProfile(
|
||||||
|
id: json['id'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
charMap:
|
||||||
|
(json['char_map'] as Map?)?.map(
|
||||||
|
(key, value) => MapEntry(key.toString(), value.toString()),
|
||||||
|
) ??
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Cyr2LatProfile copyWith({
|
||||||
|
String? id,
|
||||||
|
String? name,
|
||||||
|
Map<String, String>? charMap,
|
||||||
|
}) {
|
||||||
|
return Cyr2LatProfile(
|
||||||
|
id: id ?? this.id,
|
||||||
|
name: name ?? this.name,
|
||||||
|
charMap: charMap ?? this.charMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class AppSettings {
|
class AppSettings {
|
||||||
static const Object _unset = Object();
|
static const Object _unset = Object();
|
||||||
|
|
||||||
@@ -18,6 +81,7 @@ class AppSettings {
|
|||||||
final bool mapShowRepeaters;
|
final bool mapShowRepeaters;
|
||||||
final bool mapShowChatNodes;
|
final bool mapShowChatNodes;
|
||||||
final bool mapShowOtherNodes;
|
final bool mapShowOtherNodes;
|
||||||
|
final bool mapShowOverlaps;
|
||||||
final double mapTimeFilterHours; // 0 = all time
|
final double mapTimeFilterHours; // 0 = all time
|
||||||
final bool mapKeyPrefixEnabled;
|
final bool mapKeyPrefixEnabled;
|
||||||
final String mapKeyPrefix;
|
final String mapKeyPrefix;
|
||||||
@@ -32,6 +96,11 @@ class AppSettings {
|
|||||||
final bool notifyOnNewChannelMessage;
|
final bool notifyOnNewChannelMessage;
|
||||||
final bool notifyOnNewAdvert;
|
final bool notifyOnNewAdvert;
|
||||||
final bool autoRouteRotationEnabled;
|
final bool autoRouteRotationEnabled;
|
||||||
|
final double maxRouteWeight;
|
||||||
|
final double initialRouteWeight;
|
||||||
|
final double routeWeightSuccessIncrement;
|
||||||
|
final double routeWeightFailureDecrement;
|
||||||
|
final int maxMessageRetries;
|
||||||
final String themeMode;
|
final String themeMode;
|
||||||
final String? languageOverride; // null = system default
|
final String? languageOverride; // null = system default
|
||||||
final bool appDebugLogEnabled;
|
final bool appDebugLogEnabled;
|
||||||
@@ -39,12 +108,33 @@ class AppSettings {
|
|||||||
final Map<String, String> batteryChemistryByRepeaterId;
|
final Map<String, String> batteryChemistryByRepeaterId;
|
||||||
final UnitSystem unitSystem;
|
final UnitSystem unitSystem;
|
||||||
final Set<String> mutedChannels;
|
final Set<String> mutedChannels;
|
||||||
|
final bool mapShowDiscoveryContacts;
|
||||||
|
final String tcpServerAddress;
|
||||||
|
final int tcpServerPort;
|
||||||
|
final bool jumpToOldestUnread;
|
||||||
|
final bool translationEnabled;
|
||||||
|
final String? translationTargetLanguageCode;
|
||||||
|
final bool composerTranslationEnabled;
|
||||||
|
final String? translationModelSourceUrl;
|
||||||
|
final String? translationSelectedModelId;
|
||||||
|
final List<TranslationModelRecord> translationDownloadedModels;
|
||||||
|
final List<Cyr2LatProfile> cyr2latProfiles;
|
||||||
|
final String selectedCyr2latProfileId;
|
||||||
|
|
||||||
|
Map<String, String> get cyr2latCharMap {
|
||||||
|
final profile = cyr2latProfiles.firstWhere(
|
||||||
|
(p) => p.id == selectedCyr2latProfileId,
|
||||||
|
orElse: () => cyr2latProfiles.first,
|
||||||
|
);
|
||||||
|
return profile.charMap;
|
||||||
|
}
|
||||||
|
|
||||||
AppSettings({
|
AppSettings({
|
||||||
this.clearPathOnMaxRetry = false,
|
this.clearPathOnMaxRetry = false,
|
||||||
this.mapShowRepeaters = true,
|
this.mapShowRepeaters = true,
|
||||||
this.mapShowChatNodes = true,
|
this.mapShowChatNodes = true,
|
||||||
this.mapShowOtherNodes = true,
|
this.mapShowOtherNodes = true,
|
||||||
|
this.mapShowOverlaps = false,
|
||||||
this.mapTimeFilterHours = 0, // Default to all time
|
this.mapTimeFilterHours = 0, // Default to all time
|
||||||
this.mapKeyPrefixEnabled = false,
|
this.mapKeyPrefixEnabled = false,
|
||||||
this.mapKeyPrefix = '',
|
this.mapKeyPrefix = '',
|
||||||
@@ -59,6 +149,11 @@ class AppSettings {
|
|||||||
this.notifyOnNewChannelMessage = true,
|
this.notifyOnNewChannelMessage = true,
|
||||||
this.notifyOnNewAdvert = true,
|
this.notifyOnNewAdvert = true,
|
||||||
this.autoRouteRotationEnabled = false,
|
this.autoRouteRotationEnabled = false,
|
||||||
|
this.maxRouteWeight = 5.0,
|
||||||
|
this.initialRouteWeight = 3.0,
|
||||||
|
this.routeWeightSuccessIncrement = 0.5,
|
||||||
|
this.routeWeightFailureDecrement = 0.2,
|
||||||
|
this.maxMessageRetries = 5,
|
||||||
this.themeMode = 'system',
|
this.themeMode = 'system',
|
||||||
this.languageOverride,
|
this.languageOverride,
|
||||||
this.appDebugLogEnabled = false,
|
this.appDebugLogEnabled = false,
|
||||||
@@ -66,9 +161,32 @@ class AppSettings {
|
|||||||
Map<String, String>? batteryChemistryByRepeaterId,
|
Map<String, String>? batteryChemistryByRepeaterId,
|
||||||
this.unitSystem = UnitSystem.metric,
|
this.unitSystem = UnitSystem.metric,
|
||||||
Set<String>? mutedChannels,
|
Set<String>? mutedChannels,
|
||||||
|
this.mapShowDiscoveryContacts = true,
|
||||||
|
this.tcpServerAddress = '',
|
||||||
|
this.tcpServerPort = 0,
|
||||||
|
this.jumpToOldestUnread = false,
|
||||||
|
this.translationEnabled = false,
|
||||||
|
this.translationTargetLanguageCode,
|
||||||
|
this.composerTranslationEnabled = false,
|
||||||
|
this.translationModelSourceUrl,
|
||||||
|
this.translationSelectedModelId,
|
||||||
|
List<TranslationModelRecord>? translationDownloadedModels,
|
||||||
|
List<Cyr2LatProfile>? cyr2latProfiles,
|
||||||
|
String? selectedCyr2latProfileId,
|
||||||
}) : batteryChemistryByDeviceId = batteryChemistryByDeviceId ?? {},
|
}) : batteryChemistryByDeviceId = batteryChemistryByDeviceId ?? {},
|
||||||
batteryChemistryByRepeaterId = batteryChemistryByRepeaterId ?? {},
|
batteryChemistryByRepeaterId = batteryChemistryByRepeaterId ?? {},
|
||||||
mutedChannels = mutedChannels ?? {};
|
mutedChannels = mutedChannels ?? {},
|
||||||
|
translationDownloadedModels = translationDownloadedModels ?? const [],
|
||||||
|
cyr2latProfiles =
|
||||||
|
cyr2latProfiles ??
|
||||||
|
[
|
||||||
|
Cyr2LatProfile(
|
||||||
|
id: 'default',
|
||||||
|
name: 'Default',
|
||||||
|
charMap: defaultCyr2LatCharMap,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selectedCyr2latProfileId = selectedCyr2latProfileId ?? 'default';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
@@ -76,6 +194,7 @@ class AppSettings {
|
|||||||
'map_show_repeaters': mapShowRepeaters,
|
'map_show_repeaters': mapShowRepeaters,
|
||||||
'map_show_chat_nodes': mapShowChatNodes,
|
'map_show_chat_nodes': mapShowChatNodes,
|
||||||
'map_show_other_nodes': mapShowOtherNodes,
|
'map_show_other_nodes': mapShowOtherNodes,
|
||||||
|
'map_show_overlaps': mapShowOverlaps,
|
||||||
'map_time_filter_hours': mapTimeFilterHours,
|
'map_time_filter_hours': mapTimeFilterHours,
|
||||||
'map_key_prefix_enabled': mapKeyPrefixEnabled,
|
'map_key_prefix_enabled': mapKeyPrefixEnabled,
|
||||||
'map_key_prefix': mapKeyPrefix,
|
'map_key_prefix': mapKeyPrefix,
|
||||||
@@ -90,6 +209,11 @@ class AppSettings {
|
|||||||
'notify_on_new_channel_message': notifyOnNewChannelMessage,
|
'notify_on_new_channel_message': notifyOnNewChannelMessage,
|
||||||
'notify_on_new_advert': notifyOnNewAdvert,
|
'notify_on_new_advert': notifyOnNewAdvert,
|
||||||
'auto_route_rotation_enabled': autoRouteRotationEnabled,
|
'auto_route_rotation_enabled': autoRouteRotationEnabled,
|
||||||
|
'max_route_weight': maxRouteWeight,
|
||||||
|
'initial_route_weight': initialRouteWeight,
|
||||||
|
'route_weight_success_increment': routeWeightSuccessIncrement,
|
||||||
|
'route_weight_failure_decrement': routeWeightFailureDecrement,
|
||||||
|
'max_message_retries': maxMessageRetries,
|
||||||
'theme_mode': themeMode,
|
'theme_mode': themeMode,
|
||||||
'language_override': languageOverride,
|
'language_override': languageOverride,
|
||||||
'app_debug_log_enabled': appDebugLogEnabled,
|
'app_debug_log_enabled': appDebugLogEnabled,
|
||||||
@@ -97,6 +221,22 @@ class AppSettings {
|
|||||||
'battery_chemistry_by_repeater_id': batteryChemistryByRepeaterId,
|
'battery_chemistry_by_repeater_id': batteryChemistryByRepeaterId,
|
||||||
'unit_system': unitSystem.value,
|
'unit_system': unitSystem.value,
|
||||||
'muted_channels': mutedChannels.toList(),
|
'muted_channels': mutedChannels.toList(),
|
||||||
|
'map_show_discovery_contacts': mapShowDiscoveryContacts,
|
||||||
|
'tcp_server_address': tcpServerAddress,
|
||||||
|
'tcp_server_port': tcpServerPort,
|
||||||
|
'jump_to_oldest_unread': jumpToOldestUnread,
|
||||||
|
'translation_enabled': translationEnabled,
|
||||||
|
'translation_target_language_code': translationTargetLanguageCode,
|
||||||
|
'composer_translation_enabled': composerTranslationEnabled,
|
||||||
|
'translation_model_source_url': translationModelSourceUrl,
|
||||||
|
'translation_selected_model_id': translationSelectedModelId,
|
||||||
|
'translation_downloaded_models': translationDownloadedModels
|
||||||
|
.map((model) => model.toJson())
|
||||||
|
.toList(),
|
||||||
|
'cyr2lat_profiles': cyr2latProfiles
|
||||||
|
.map((profile) => profile.toJson())
|
||||||
|
.toList(),
|
||||||
|
'selected_cyr2lat_profile_id': selectedCyr2latProfileId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +253,7 @@ class AppSettings {
|
|||||||
mapShowRepeaters: json['map_show_repeaters'] as bool? ?? true,
|
mapShowRepeaters: json['map_show_repeaters'] as bool? ?? true,
|
||||||
mapShowChatNodes: json['map_show_chat_nodes'] as bool? ?? true,
|
mapShowChatNodes: json['map_show_chat_nodes'] as bool? ?? true,
|
||||||
mapShowOtherNodes: json['map_show_other_nodes'] as bool? ?? true,
|
mapShowOtherNodes: json['map_show_other_nodes'] as bool? ?? true,
|
||||||
|
mapShowOverlaps: json['map_show_overlaps'] as bool? ?? false,
|
||||||
mapTimeFilterHours:
|
mapTimeFilterHours:
|
||||||
(json['map_time_filter_hours'] as num?)?.toDouble() ?? 0,
|
(json['map_time_filter_hours'] as num?)?.toDouble() ?? 0,
|
||||||
mapKeyPrefixEnabled: json['map_key_prefix_enabled'] as bool? ?? false,
|
mapKeyPrefixEnabled: json['map_key_prefix_enabled'] as bool? ?? false,
|
||||||
@@ -133,6 +274,14 @@ class AppSettings {
|
|||||||
notifyOnNewAdvert: json['notify_on_new_advert'] as bool? ?? true,
|
notifyOnNewAdvert: json['notify_on_new_advert'] as bool? ?? true,
|
||||||
autoRouteRotationEnabled:
|
autoRouteRotationEnabled:
|
||||||
json['auto_route_rotation_enabled'] as bool? ?? false,
|
json['auto_route_rotation_enabled'] as bool? ?? false,
|
||||||
|
maxRouteWeight: (json['max_route_weight'] as num?)?.toDouble() ?? 5.0,
|
||||||
|
initialRouteWeight:
|
||||||
|
(json['initial_route_weight'] as num?)?.toDouble() ?? 3.0,
|
||||||
|
routeWeightSuccessIncrement:
|
||||||
|
(json['route_weight_success_increment'] as num?)?.toDouble() ?? 0.5,
|
||||||
|
routeWeightFailureDecrement:
|
||||||
|
(json['route_weight_failure_decrement'] as num?)?.toDouble() ?? 0.2,
|
||||||
|
maxMessageRetries: json['max_message_retries'] as int? ?? 5,
|
||||||
themeMode: json['theme_mode'] as String? ?? 'system',
|
themeMode: json['theme_mode'] as String? ?? 'system',
|
||||||
languageOverride: json['language_override'] as String?,
|
languageOverride: json['language_override'] as String?,
|
||||||
appDebugLogEnabled: json['app_debug_log_enabled'] as bool? ?? false,
|
appDebugLogEnabled: json['app_debug_log_enabled'] as bool? ?? false,
|
||||||
@@ -152,6 +301,61 @@ class AppSettings {
|
|||||||
?.map((e) => e.toString())
|
?.map((e) => e.toString())
|
||||||
.toSet()) ??
|
.toSet()) ??
|
||||||
{},
|
{},
|
||||||
|
mapShowDiscoveryContacts:
|
||||||
|
json['map_show_discovery_contacts'] as bool? ?? true,
|
||||||
|
tcpServerAddress: json['tcp_server_address'] as String? ?? '',
|
||||||
|
tcpServerPort: json['tcp_server_port'] as int? ?? 0,
|
||||||
|
jumpToOldestUnread: json['jump_to_oldest_unread'] as bool? ?? false,
|
||||||
|
translationEnabled: json['translation_enabled'] as bool? ?? false,
|
||||||
|
translationTargetLanguageCode:
|
||||||
|
json['translation_target_language_code'] as String?,
|
||||||
|
composerTranslationEnabled:
|
||||||
|
json['composer_translation_enabled'] as bool? ?? false,
|
||||||
|
translationModelSourceUrl:
|
||||||
|
json['translation_model_source_url'] as String?,
|
||||||
|
translationSelectedModelId:
|
||||||
|
json['translation_selected_model_id'] as String?,
|
||||||
|
translationDownloadedModels:
|
||||||
|
(json['translation_downloaded_models'] as List<dynamic>?)
|
||||||
|
?.map(
|
||||||
|
(entry) => TranslationModelRecord.fromJson(
|
||||||
|
Map<String, dynamic>.from(entry as Map),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
|
cyr2latProfiles:
|
||||||
|
(json['cyr2lat_profiles'] as List<dynamic>?)
|
||||||
|
?.map(
|
||||||
|
(entry) => Cyr2LatProfile.fromJson(
|
||||||
|
Map<String, dynamic>.from(entry as Map),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList() ??
|
||||||
|
// Backward compatibility: if old cyr2lat_char_map exists, create a profile from it
|
||||||
|
(json['cyr2lat_char_map'] != null
|
||||||
|
? [
|
||||||
|
Cyr2LatProfile(
|
||||||
|
id: 'migrated',
|
||||||
|
name: 'Migrated Profile',
|
||||||
|
charMap:
|
||||||
|
(json['cyr2lat_char_map'] as Map?)?.map(
|
||||||
|
(key, value) =>
|
||||||
|
MapEntry(key.toString(), value.toString()),
|
||||||
|
) ??
|
||||||
|
defaultCyr2LatCharMap,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
Cyr2LatProfile(
|
||||||
|
id: 'default',
|
||||||
|
name: 'Default',
|
||||||
|
charMap: defaultCyr2LatCharMap,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
selectedCyr2latProfileId:
|
||||||
|
json['selected_cyr2lat_profile_id'] as String? ??
|
||||||
|
(json['cyr2lat_char_map'] != null ? 'migrated' : 'default'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +364,7 @@ class AppSettings {
|
|||||||
bool? mapShowRepeaters,
|
bool? mapShowRepeaters,
|
||||||
bool? mapShowChatNodes,
|
bool? mapShowChatNodes,
|
||||||
bool? mapShowOtherNodes,
|
bool? mapShowOtherNodes,
|
||||||
|
bool? mapShowOverlaps,
|
||||||
double? mapTimeFilterHours,
|
double? mapTimeFilterHours,
|
||||||
bool? mapKeyPrefixEnabled,
|
bool? mapKeyPrefixEnabled,
|
||||||
String? mapKeyPrefix,
|
String? mapKeyPrefix,
|
||||||
@@ -174,6 +379,11 @@ class AppSettings {
|
|||||||
bool? notifyOnNewChannelMessage,
|
bool? notifyOnNewChannelMessage,
|
||||||
bool? notifyOnNewAdvert,
|
bool? notifyOnNewAdvert,
|
||||||
bool? autoRouteRotationEnabled,
|
bool? autoRouteRotationEnabled,
|
||||||
|
double? maxRouteWeight,
|
||||||
|
double? initialRouteWeight,
|
||||||
|
double? routeWeightSuccessIncrement,
|
||||||
|
double? routeWeightFailureDecrement,
|
||||||
|
int? maxMessageRetries,
|
||||||
String? themeMode,
|
String? themeMode,
|
||||||
Object? languageOverride = _unset,
|
Object? languageOverride = _unset,
|
||||||
bool? appDebugLogEnabled,
|
bool? appDebugLogEnabled,
|
||||||
@@ -181,12 +391,25 @@ class AppSettings {
|
|||||||
Map<String, String>? batteryChemistryByRepeaterId,
|
Map<String, String>? batteryChemistryByRepeaterId,
|
||||||
UnitSystem? unitSystem,
|
UnitSystem? unitSystem,
|
||||||
Set<String>? mutedChannels,
|
Set<String>? mutedChannels,
|
||||||
|
bool? mapShowDiscoveryContacts,
|
||||||
|
String? tcpServerAddress,
|
||||||
|
int? tcpServerPort,
|
||||||
|
bool? jumpToOldestUnread,
|
||||||
|
bool? translationEnabled,
|
||||||
|
Object? translationTargetLanguageCode = _unset,
|
||||||
|
bool? composerTranslationEnabled,
|
||||||
|
Object? translationModelSourceUrl = _unset,
|
||||||
|
Object? translationSelectedModelId = _unset,
|
||||||
|
List<TranslationModelRecord>? translationDownloadedModels,
|
||||||
|
List<Cyr2LatProfile>? cyr2latProfiles,
|
||||||
|
String? selectedCyr2latProfileId,
|
||||||
}) {
|
}) {
|
||||||
return AppSettings(
|
return AppSettings(
|
||||||
clearPathOnMaxRetry: clearPathOnMaxRetry ?? this.clearPathOnMaxRetry,
|
clearPathOnMaxRetry: clearPathOnMaxRetry ?? this.clearPathOnMaxRetry,
|
||||||
mapShowRepeaters: mapShowRepeaters ?? this.mapShowRepeaters,
|
mapShowRepeaters: mapShowRepeaters ?? this.mapShowRepeaters,
|
||||||
mapShowChatNodes: mapShowChatNodes ?? this.mapShowChatNodes,
|
mapShowChatNodes: mapShowChatNodes ?? this.mapShowChatNodes,
|
||||||
mapShowOtherNodes: mapShowOtherNodes ?? this.mapShowOtherNodes,
|
mapShowOtherNodes: mapShowOtherNodes ?? this.mapShowOtherNodes,
|
||||||
|
mapShowOverlaps: mapShowOverlaps ?? this.mapShowOverlaps,
|
||||||
mapTimeFilterHours: mapTimeFilterHours ?? this.mapTimeFilterHours,
|
mapTimeFilterHours: mapTimeFilterHours ?? this.mapTimeFilterHours,
|
||||||
mapKeyPrefixEnabled: mapKeyPrefixEnabled ?? this.mapKeyPrefixEnabled,
|
mapKeyPrefixEnabled: mapKeyPrefixEnabled ?? this.mapKeyPrefixEnabled,
|
||||||
mapKeyPrefix: mapKeyPrefix ?? this.mapKeyPrefix,
|
mapKeyPrefix: mapKeyPrefix ?? this.mapKeyPrefix,
|
||||||
@@ -206,6 +429,13 @@ class AppSettings {
|
|||||||
notifyOnNewAdvert: notifyOnNewAdvert ?? this.notifyOnNewAdvert,
|
notifyOnNewAdvert: notifyOnNewAdvert ?? this.notifyOnNewAdvert,
|
||||||
autoRouteRotationEnabled:
|
autoRouteRotationEnabled:
|
||||||
autoRouteRotationEnabled ?? this.autoRouteRotationEnabled,
|
autoRouteRotationEnabled ?? this.autoRouteRotationEnabled,
|
||||||
|
maxRouteWeight: maxRouteWeight ?? this.maxRouteWeight,
|
||||||
|
initialRouteWeight: initialRouteWeight ?? this.initialRouteWeight,
|
||||||
|
routeWeightSuccessIncrement:
|
||||||
|
routeWeightSuccessIncrement ?? this.routeWeightSuccessIncrement,
|
||||||
|
routeWeightFailureDecrement:
|
||||||
|
routeWeightFailureDecrement ?? this.routeWeightFailureDecrement,
|
||||||
|
maxMessageRetries: maxMessageRetries ?? this.maxMessageRetries,
|
||||||
themeMode: themeMode ?? this.themeMode,
|
themeMode: themeMode ?? this.themeMode,
|
||||||
languageOverride: languageOverride == _unset
|
languageOverride: languageOverride == _unset
|
||||||
? this.languageOverride
|
? this.languageOverride
|
||||||
@@ -217,6 +447,28 @@ class AppSettings {
|
|||||||
batteryChemistryByRepeaterId ?? this.batteryChemistryByRepeaterId,
|
batteryChemistryByRepeaterId ?? this.batteryChemistryByRepeaterId,
|
||||||
unitSystem: unitSystem ?? this.unitSystem,
|
unitSystem: unitSystem ?? this.unitSystem,
|
||||||
mutedChannels: mutedChannels ?? this.mutedChannels,
|
mutedChannels: mutedChannels ?? this.mutedChannels,
|
||||||
|
mapShowDiscoveryContacts:
|
||||||
|
mapShowDiscoveryContacts ?? this.mapShowDiscoveryContacts,
|
||||||
|
tcpServerAddress: tcpServerAddress ?? this.tcpServerAddress,
|
||||||
|
tcpServerPort: tcpServerPort ?? this.tcpServerPort,
|
||||||
|
jumpToOldestUnread: jumpToOldestUnread ?? this.jumpToOldestUnread,
|
||||||
|
translationEnabled: translationEnabled ?? this.translationEnabled,
|
||||||
|
translationTargetLanguageCode: translationTargetLanguageCode == _unset
|
||||||
|
? this.translationTargetLanguageCode
|
||||||
|
: translationTargetLanguageCode as String?,
|
||||||
|
composerTranslationEnabled:
|
||||||
|
composerTranslationEnabled ?? this.composerTranslationEnabled,
|
||||||
|
translationModelSourceUrl: translationModelSourceUrl == _unset
|
||||||
|
? this.translationModelSourceUrl
|
||||||
|
: translationModelSourceUrl as String?,
|
||||||
|
translationSelectedModelId: translationSelectedModelId == _unset
|
||||||
|
? this.translationSelectedModelId
|
||||||
|
: translationSelectedModelId as String?,
|
||||||
|
translationDownloadedModels:
|
||||||
|
translationDownloadedModels ?? this.translationDownloadedModels,
|
||||||
|
cyr2latProfiles: cyr2latProfiles ?? this.cyr2latProfiles,
|
||||||
|
selectedCyr2latProfileId:
|
||||||
|
selectedCyr2latProfileId ?? this.selectedCyr2latProfileId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-9
@@ -4,6 +4,9 @@ import 'dart:typed_data';
|
|||||||
import 'package:crypto/crypto.dart' as crypto;
|
import 'package:crypto/crypto.dart' as crypto;
|
||||||
|
|
||||||
import '../connector/meshcore_protocol.dart';
|
import '../connector/meshcore_protocol.dart';
|
||||||
|
import 'community.dart';
|
||||||
|
|
||||||
|
enum ChannelType { public, private, hashtag, communityPublic, communityHashtag }
|
||||||
|
|
||||||
class Channel {
|
class Channel {
|
||||||
final int index;
|
final int index;
|
||||||
@@ -24,20 +27,23 @@ class Channel {
|
|||||||
|
|
||||||
bool get isPublicChannel => pskHex == publicChannelPsk;
|
bool get isPublicChannel => pskHex == publicChannelPsk;
|
||||||
|
|
||||||
static Channel? fromFrame(Uint8List data) {
|
static Channel? fromFrame(Uint8List frame) {
|
||||||
// CHANNEL_INFO format:
|
// CHANNEL_INFO format:
|
||||||
// [0] = RESP_CODE_CHANNEL_INFO (18)
|
// [0] = RESP_CODE_CHANNEL_INFO (18)
|
||||||
// [1] = channel_idx
|
// [1] = channel_idx
|
||||||
// [2-33] = name (32 bytes, null-terminated)
|
// [2-33] = name (32 bytes, null-terminated)
|
||||||
// [34-49] = psk (16 bytes)
|
// [34-49] = psk (16 bytes)
|
||||||
if (data.length < 50) return null;
|
if (frame.length < 50) return null;
|
||||||
if (data[0] != respCodeChannelInfo) return null;
|
final reader = BufferReader(frame);
|
||||||
|
try {
|
||||||
final index = data[1];
|
if (reader.readByte() != respCodeChannelInfo) return null;
|
||||||
final name = readCString(data, 2, 32);
|
final index = reader.readByte();
|
||||||
final psk = Uint8List.fromList(data.sublist(34, 50));
|
final name = reader.readCStringGreedy(32);
|
||||||
|
final psk = reader.readBytes(16);
|
||||||
return Channel(index: index, name: name, psk: psk);
|
return Channel(index: index, name: name, psk: psk);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Channel empty(int index) {
|
static Channel empty(int index) {
|
||||||
@@ -108,5 +114,36 @@ class Channel {
|
|||||||
return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isCommunityChannel(ChannelType channelType) {
|
||||||
|
switch (channelType) {
|
||||||
|
case ChannelType.communityPublic:
|
||||||
|
case ChannelType.communityHashtag:
|
||||||
|
return true;
|
||||||
|
case ChannelType.public:
|
||||||
|
case ChannelType.private:
|
||||||
|
case ChannelType.hashtag:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ChannelType getChannelType(
|
||||||
|
Channel channel,
|
||||||
|
CommunityPskIndex communityIndex,
|
||||||
|
) {
|
||||||
|
Community? community = communityIndex.getCommunityForChannel(channel);
|
||||||
|
if (community != null) {
|
||||||
|
if (Community.isCommunityPublicChannel(channel, community)) {
|
||||||
|
return ChannelType.communityPublic;
|
||||||
|
}
|
||||||
|
return ChannelType.communityHashtag;
|
||||||
|
}
|
||||||
|
if (channel.isPublicChannel) {
|
||||||
|
return ChannelType.public;
|
||||||
|
} else if (channel.name.startsWith('#')) {
|
||||||
|
return ChannelType.hashtag;
|
||||||
|
}
|
||||||
|
return ChannelType.private;
|
||||||
|
}
|
||||||
|
|
||||||
static const String publicChannelPsk = '8b3387e9c5cdea6ac9e5edbaa115cd72';
|
static const String publicChannelPsk = '8b3387e9c5cdea6ac9e5edbaa115cd72';
|
||||||
}
|
}
|
||||||
|
|||||||
+114
-79
@@ -2,6 +2,8 @@ import 'dart:typed_data';
|
|||||||
import '../connector/meshcore_protocol.dart';
|
import '../connector/meshcore_protocol.dart';
|
||||||
import '../helpers/reaction_helper.dart';
|
import '../helpers/reaction_helper.dart';
|
||||||
import '../helpers/smaz.dart';
|
import '../helpers/smaz.dart';
|
||||||
|
import 'translation_support.dart';
|
||||||
|
import '../utils/app_logger.dart';
|
||||||
|
|
||||||
enum ChannelMessageStatus { pending, sent, failed }
|
enum ChannelMessageStatus { pending, sent, failed }
|
||||||
|
|
||||||
@@ -23,9 +25,16 @@ class Repeat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ChannelMessage {
|
class ChannelMessage {
|
||||||
|
static const Object _unset = Object();
|
||||||
|
|
||||||
final Uint8List? senderKey;
|
final Uint8List? senderKey;
|
||||||
final String senderName;
|
final String senderName;
|
||||||
final String text;
|
final String text;
|
||||||
|
final String? originalText;
|
||||||
|
final String? translatedText;
|
||||||
|
final String? translatedLanguageCode;
|
||||||
|
final MessageTranslationStatus translationStatus;
|
||||||
|
final String? translationModelId;
|
||||||
final DateTime timestamp;
|
final DateTime timestamp;
|
||||||
final bool isOutgoing;
|
final bool isOutgoing;
|
||||||
final ChannelMessageStatus status;
|
final ChannelMessageStatus status;
|
||||||
@@ -36,6 +45,7 @@ class ChannelMessage {
|
|||||||
final List<Uint8List> pathVariants;
|
final List<Uint8List> pathVariants;
|
||||||
final int? channelIndex;
|
final int? channelIndex;
|
||||||
final String messageId;
|
final String messageId;
|
||||||
|
final String? packetHash;
|
||||||
final String? replyToMessageId;
|
final String? replyToMessageId;
|
||||||
final String? replyToSenderName;
|
final String? replyToSenderName;
|
||||||
final String? replyToText;
|
final String? replyToText;
|
||||||
@@ -45,6 +55,11 @@ class ChannelMessage {
|
|||||||
this.senderKey,
|
this.senderKey,
|
||||||
required this.senderName,
|
required this.senderName,
|
||||||
required this.text,
|
required this.text,
|
||||||
|
this.originalText,
|
||||||
|
this.translatedText,
|
||||||
|
this.translatedLanguageCode,
|
||||||
|
this.translationStatus = MessageTranslationStatus.none,
|
||||||
|
this.translationModelId,
|
||||||
required this.timestamp,
|
required this.timestamp,
|
||||||
required this.isOutgoing,
|
required this.isOutgoing,
|
||||||
this.status = ChannelMessageStatus.pending,
|
this.status = ChannelMessageStatus.pending,
|
||||||
@@ -55,6 +70,7 @@ class ChannelMessage {
|
|||||||
List<Uint8List>? pathVariants,
|
List<Uint8List>? pathVariants,
|
||||||
this.channelIndex,
|
this.channelIndex,
|
||||||
String? messageId,
|
String? messageId,
|
||||||
|
this.packetHash,
|
||||||
this.replyToMessageId,
|
this.replyToMessageId,
|
||||||
this.replyToSenderName,
|
this.replyToSenderName,
|
||||||
this.replyToText,
|
this.replyToText,
|
||||||
@@ -79,15 +95,34 @@ class ChannelMessage {
|
|||||||
int? pathLength,
|
int? pathLength,
|
||||||
Uint8List? pathBytes,
|
Uint8List? pathBytes,
|
||||||
List<Uint8List>? pathVariants,
|
List<Uint8List>? pathVariants,
|
||||||
|
String? packetHash,
|
||||||
String? replyToMessageId,
|
String? replyToMessageId,
|
||||||
String? replyToSenderName,
|
String? replyToSenderName,
|
||||||
String? replyToText,
|
String? replyToText,
|
||||||
|
Object? originalText = _unset,
|
||||||
|
Object? translatedText = _unset,
|
||||||
|
Object? translatedLanguageCode = _unset,
|
||||||
|
MessageTranslationStatus? translationStatus,
|
||||||
|
Object? translationModelId = _unset,
|
||||||
Map<String, int>? reactions,
|
Map<String, int>? reactions,
|
||||||
}) {
|
}) {
|
||||||
return ChannelMessage(
|
return ChannelMessage(
|
||||||
senderKey: senderKey,
|
senderKey: senderKey,
|
||||||
senderName: senderName,
|
senderName: senderName,
|
||||||
text: text,
|
text: text,
|
||||||
|
originalText: originalText == _unset
|
||||||
|
? this.originalText
|
||||||
|
: originalText as String?,
|
||||||
|
translatedText: translatedText == _unset
|
||||||
|
? this.translatedText
|
||||||
|
: translatedText as String?,
|
||||||
|
translatedLanguageCode: translatedLanguageCode == _unset
|
||||||
|
? this.translatedLanguageCode
|
||||||
|
: translatedLanguageCode as String?,
|
||||||
|
translationStatus: translationStatus ?? this.translationStatus,
|
||||||
|
translationModelId: translationModelId == _unset
|
||||||
|
? this.translationModelId
|
||||||
|
: translationModelId as String?,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
isOutgoing: isOutgoing,
|
isOutgoing: isOutgoing,
|
||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
@@ -98,6 +133,7 @@ class ChannelMessage {
|
|||||||
pathVariants: pathVariants ?? this.pathVariants,
|
pathVariants: pathVariants ?? this.pathVariants,
|
||||||
channelIndex: channelIndex,
|
channelIndex: channelIndex,
|
||||||
messageId: messageId,
|
messageId: messageId,
|
||||||
|
packetHash: packetHash ?? this.packetHash,
|
||||||
replyToMessageId: replyToMessageId ?? this.replyToMessageId,
|
replyToMessageId: replyToMessageId ?? this.replyToMessageId,
|
||||||
replyToSenderName: replyToSenderName ?? this.replyToSenderName,
|
replyToSenderName: replyToSenderName ?? this.replyToSenderName,
|
||||||
replyToText: replyToText ?? this.replyToText,
|
replyToText: replyToText ?? this.replyToText,
|
||||||
@@ -105,100 +141,99 @@ class ChannelMessage {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ChannelMessage? fromFrame(Uint8List data) {
|
static ChannelMessage? fromFrame(Uint8List frame) {
|
||||||
// CHANNEL_MSG_RECV format varies by version:
|
// CHANNEL_MSG_RECV format varies by version:
|
||||||
// V3: [0]=code [1]=SNR [2]=rsv1 [3]=rsv2 [4]=channel_idx [5]=path_len [path... optional] [txt_type] [timestamp x4] [text...]
|
// V3: [0]=code [1]=SNR [2]=rsv1 [3]=rsv2 [4]=channel_idx [5]=path_len [path... optional] [txt_type] [timestamp x4] [text...]
|
||||||
// Non-V3: [0]=code [1]=channel_idx [2]=path_len [3]=txt_type [4-7]=timestamp [8+]=text
|
// Non-V3: [0]=code [1]=channel_idx [2]=path_len [3]=txt_type [4-7]=timestamp [8+]=text
|
||||||
if (data.length < 8) return null;
|
if (frame.length < 8) return null;
|
||||||
|
try {
|
||||||
|
final reader = BufferReader(frame);
|
||||||
|
final code = reader.readByte();
|
||||||
|
if (code != respCodeChannelMsgRecv && code != respCodeChannelMsgRecvV3) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
final code = data[0];
|
int pathLen;
|
||||||
if (code != respCodeChannelMsgRecv && code != respCodeChannelMsgRecvV3) {
|
int txtType;
|
||||||
|
Uint8List pathBytes = Uint8List(0);
|
||||||
|
int channelIdx;
|
||||||
|
if (code == respCodeChannelMsgRecvV3) {
|
||||||
|
reader.skipBytes(1); // Skip SNR
|
||||||
|
final flags = reader.readByte();
|
||||||
|
final hasPath = (flags & 0x01) != 0;
|
||||||
|
reader.skipBytes(1); // Skip reserved byte
|
||||||
|
channelIdx = reader.readByte();
|
||||||
|
pathLen = reader.readInt8();
|
||||||
|
txtType = reader.readByte();
|
||||||
|
if (hasPath && pathLen > 0) {
|
||||||
|
reader.rewind(); // Rewind to read path length again for pathBytes
|
||||||
|
pathBytes = reader.readBytes(pathLen);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channelIdx = reader.readByte();
|
||||||
|
pathLen = reader.readInt8();
|
||||||
|
txtType = reader.readByte();
|
||||||
|
}
|
||||||
|
final timestampRaw = reader.readUInt32LE();
|
||||||
|
|
||||||
|
if (txtType != txtTypePlain) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final text = reader.readCString();
|
||||||
|
|
||||||
|
// Extract sender name and actual message from "name: msg" format
|
||||||
|
String senderName = 'Unknown';
|
||||||
|
String actualText = text;
|
||||||
|
|
||||||
|
final colonIndex = text.indexOf(':');
|
||||||
|
if (colonIndex > 0 && colonIndex < text.length - 1 && colonIndex < 50) {
|
||||||
|
final potentialSender = text.substring(0, colonIndex);
|
||||||
|
if (!RegExp(r'[:\[\]]').hasMatch(potentialSender)) {
|
||||||
|
senderName = potentialSender;
|
||||||
|
final offset =
|
||||||
|
(colonIndex + 1 < text.length && text[colonIndex + 1] == ' ')
|
||||||
|
? colonIndex + 2
|
||||||
|
: colonIndex + 1;
|
||||||
|
actualText = text.substring(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final decodedText = Smaz.tryDecodePrefixed(actualText) ?? actualText;
|
||||||
|
|
||||||
|
return ChannelMessage(
|
||||||
|
senderKey: null,
|
||||||
|
senderName: senderName,
|
||||||
|
text: decodedText,
|
||||||
|
timestamp: DateTime.fromMillisecondsSinceEpoch(timestampRaw * 1000),
|
||||||
|
isOutgoing: false,
|
||||||
|
status: ChannelMessageStatus.sent,
|
||||||
|
pathLength: pathLen,
|
||||||
|
pathBytes: pathBytes,
|
||||||
|
channelIndex: channelIdx,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
appLogger.error('Error parsing channel message frame: $e');
|
||||||
|
// If parsing fails, return null to avoid crashes
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
int timestampOffset, textOffset, pathLenOffset, txtTypeOffset;
|
|
||||||
Uint8List pathBytes = Uint8List(0);
|
|
||||||
int channelIdx;
|
|
||||||
|
|
||||||
if (code == respCodeChannelMsgRecvV3) {
|
|
||||||
channelIdx = data[4];
|
|
||||||
pathLenOffset = 5;
|
|
||||||
final pathLen = data[pathLenOffset].toSigned(8);
|
|
||||||
var cursor = 6;
|
|
||||||
final hasPathBytesFlag = (data[2] & 0x01) != 0;
|
|
||||||
final canFitPath = pathLen > 0 && data.length >= cursor + pathLen + 5;
|
|
||||||
final hasValidTxtType =
|
|
||||||
cursor < data.length &&
|
|
||||||
(data[cursor] == txtTypePlain || data[cursor] == txtTypeCliData);
|
|
||||||
if ((hasPathBytesFlag || (canFitPath && !hasValidTxtType)) &&
|
|
||||||
canFitPath) {
|
|
||||||
pathBytes = Uint8List.fromList(data.sublist(cursor, cursor + pathLen));
|
|
||||||
cursor += pathLen;
|
|
||||||
}
|
|
||||||
txtTypeOffset = cursor;
|
|
||||||
cursor += 1; // txt_type
|
|
||||||
timestampOffset = cursor;
|
|
||||||
textOffset = cursor + 4;
|
|
||||||
} else {
|
|
||||||
channelIdx = data[1];
|
|
||||||
pathLenOffset = 2;
|
|
||||||
txtTypeOffset = 3;
|
|
||||||
timestampOffset = 4;
|
|
||||||
textOffset = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.length < textOffset + 1) return null;
|
|
||||||
|
|
||||||
final txtType = data[txtTypeOffset];
|
|
||||||
if (txtType != txtTypePlain) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final pathLen = data[pathLenOffset].toSigned(8);
|
|
||||||
final timestampRaw = readUint32LE(data, timestampOffset);
|
|
||||||
final text = readCString(data, textOffset, data.length - textOffset);
|
|
||||||
|
|
||||||
// Extract sender name and actual message from "name: msg" format
|
|
||||||
String senderName = 'Unknown';
|
|
||||||
String actualText = text;
|
|
||||||
|
|
||||||
final colonIndex = text.indexOf(':');
|
|
||||||
if (colonIndex > 0 && colonIndex < text.length - 1 && colonIndex < 50) {
|
|
||||||
final potentialSender = text.substring(0, colonIndex);
|
|
||||||
if (!RegExp(r'[:\[\]]').hasMatch(potentialSender)) {
|
|
||||||
senderName = potentialSender;
|
|
||||||
final offset =
|
|
||||||
(colonIndex + 1 < text.length && text[colonIndex + 1] == ' ')
|
|
||||||
? colonIndex + 2
|
|
||||||
: colonIndex + 1;
|
|
||||||
actualText = text.substring(offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final decodedText = Smaz.tryDecodePrefixed(actualText) ?? actualText;
|
|
||||||
|
|
||||||
return ChannelMessage(
|
|
||||||
senderKey: null,
|
|
||||||
senderName: senderName,
|
|
||||||
text: decodedText,
|
|
||||||
timestamp: DateTime.fromMillisecondsSinceEpoch(timestampRaw * 1000),
|
|
||||||
isOutgoing: false,
|
|
||||||
status: ChannelMessageStatus.sent,
|
|
||||||
pathLength: pathLen,
|
|
||||||
pathBytes: pathBytes,
|
|
||||||
channelIndex: channelIdx,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ChannelMessage outgoing(
|
static ChannelMessage outgoing(
|
||||||
String text,
|
String text,
|
||||||
String senderName,
|
String senderName,
|
||||||
int channelIndex,
|
int channelIndex, {
|
||||||
) {
|
String? originalText,
|
||||||
|
String? translatedLanguageCode,
|
||||||
|
String? translationModelId,
|
||||||
|
}) {
|
||||||
return ChannelMessage(
|
return ChannelMessage(
|
||||||
senderKey: null,
|
senderKey: null,
|
||||||
senderName: senderName,
|
senderName: senderName,
|
||||||
text: text,
|
text: text,
|
||||||
|
originalText: originalText,
|
||||||
|
translatedLanguageCode: translatedLanguageCode,
|
||||||
|
translationModelId: translationModelId,
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
status: ChannelMessageStatus.pending,
|
status: ChannelMessageStatus.pending,
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'package:crypto/crypto.dart' as crypto;
|
import 'package:crypto/crypto.dart' as crypto;
|
||||||
|
|
||||||
|
import 'channel.dart';
|
||||||
|
|
||||||
/// Represents a community with a shared secret for deriving channel PSKs.
|
/// Represents a community with a shared secret for deriving channel PSKs.
|
||||||
///
|
///
|
||||||
/// A Community is a namespace with a shared secret K (32 random bytes),
|
/// A Community is a namespace with a shared secret K (32 random bytes),
|
||||||
@@ -162,6 +164,12 @@ class Community {
|
|||||||
return hashtag.replaceFirst(RegExp(r'^#'), '').toLowerCase().trim();
|
return hashtag.replaceFirst(RegExp(r'^#'), '').toLowerCase().trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if this is the community's public channel
|
||||||
|
static bool isCommunityPublicChannel(Channel channel, Community community) {
|
||||||
|
final publicPsk = community.deriveCommunityPublicPsk();
|
||||||
|
return channel.pskHex == Channel.formatPskHex(publicPsk);
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a hashtag channel to this community's list
|
/// Add a hashtag channel to this community's list
|
||||||
Community addHashtagChannel(String hashtag) {
|
Community addHashtagChannel(String hashtag) {
|
||||||
final normalized = _normalizeCommunityHashtag(hashtag);
|
final normalized = _normalizeCommunityHashtag(hashtag);
|
||||||
@@ -237,3 +245,28 @@ class Community {
|
|||||||
@override
|
@override
|
||||||
int get hashCode => id.hashCode;
|
int get hashCode => id.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CommunityPskIndex {
|
||||||
|
// Cache of PSK hex -> Community for quick lookup
|
||||||
|
final Map<String, Community> _pskToCommunity = {};
|
||||||
|
|
||||||
|
void initialize(List<Community> communities) {
|
||||||
|
_pskToCommunity.clear();
|
||||||
|
for (final community in communities) {
|
||||||
|
// Map the community public channel PSK
|
||||||
|
final publicPsk = community.deriveCommunityPublicPsk();
|
||||||
|
_pskToCommunity[Channel.formatPskHex(publicPsk)] = community;
|
||||||
|
|
||||||
|
// Map all known hashtag channel PSKs
|
||||||
|
for (final hashtag in community.hashtagChannels) {
|
||||||
|
final hashtagPsk = community.deriveCommunityHashtagPsk(hashtag);
|
||||||
|
_pskToCommunity[Channel.formatPskHex(hashtagPsk)] = community;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the community this channel belongs to, or null if not a community channel
|
||||||
|
Community? getCommunityForChannel(Channel channel) {
|
||||||
|
return _pskToCommunity[channel.pskHex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import '../connector/meshcore_protocol.dart';
|
||||||
|
import '../utils/app_logger.dart';
|
||||||
|
|
||||||
|
/// Parsed `RESP_CODE_STATS` + `STATS_TYPE_RADIO` (14 bytes total).
|
||||||
|
class CompanionRadioStats {
|
||||||
|
final int noiseFloorDbm;
|
||||||
|
final int lastRssiDbm;
|
||||||
|
final double lastSnrDb;
|
||||||
|
final int txAirSecs;
|
||||||
|
final int rxAirSecs;
|
||||||
|
final DateTime receivedAt;
|
||||||
|
|
||||||
|
const CompanionRadioStats({
|
||||||
|
required this.noiseFloorDbm,
|
||||||
|
required this.lastRssiDbm,
|
||||||
|
required this.lastSnrDb,
|
||||||
|
required this.txAirSecs,
|
||||||
|
required this.rxAirSecs,
|
||||||
|
required this.receivedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
static CompanionRadioStats? tryParse(Uint8List frame) {
|
||||||
|
if (frame.length < 14) return null;
|
||||||
|
if (frame[0] != respCodeStats || frame[1] != statsTypeRadio) return null;
|
||||||
|
try {
|
||||||
|
final reader = BufferReader(frame);
|
||||||
|
reader.skipBytes(2);
|
||||||
|
final noise = reader.readInt16LE();
|
||||||
|
final rssi = reader.readInt8();
|
||||||
|
final snrRaw = reader.readInt8();
|
||||||
|
final txAir = reader.readUInt32LE();
|
||||||
|
final rxAir = reader.readUInt32LE();
|
||||||
|
return CompanionRadioStats(
|
||||||
|
noiseFloorDbm: noise,
|
||||||
|
lastRssiDbm: rssi,
|
||||||
|
lastSnrDb: snrRaw / 4.0,
|
||||||
|
txAirSecs: txAir,
|
||||||
|
rxAirSecs: rxAir,
|
||||||
|
receivedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
appLogger.warn('CompanionRadioStats parse error: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+85
-63
@@ -17,6 +17,10 @@ class Contact {
|
|||||||
final double? longitude;
|
final double? longitude;
|
||||||
final DateTime lastSeen;
|
final DateTime lastSeen;
|
||||||
final DateTime lastMessageAt;
|
final DateTime lastMessageAt;
|
||||||
|
final DateTime? lastModified;
|
||||||
|
final bool isActive;
|
||||||
|
final bool wasPulled;
|
||||||
|
final Uint8List? rawPacket;
|
||||||
|
|
||||||
Contact({
|
Contact({
|
||||||
required this.publicKey,
|
required this.publicKey,
|
||||||
@@ -30,12 +34,19 @@ class Contact {
|
|||||||
this.latitude,
|
this.latitude,
|
||||||
this.longitude,
|
this.longitude,
|
||||||
required this.lastSeen,
|
required this.lastSeen,
|
||||||
|
this.lastModified,
|
||||||
DateTime? lastMessageAt,
|
DateTime? lastMessageAt,
|
||||||
|
this.isActive = true,
|
||||||
|
this.wasPulled = false,
|
||||||
|
this.rawPacket,
|
||||||
}) : lastMessageAt = lastMessageAt ?? lastSeen;
|
}) : lastMessageAt = lastMessageAt ?? lastSeen;
|
||||||
|
|
||||||
String get publicKeyHex => pubKeyToHex(publicKey);
|
String get publicKeyHex => pubKeyToHex(publicKey);
|
||||||
|
|
||||||
String get typeLabel {
|
/// Non-localized type label, intended for logs and non-UI exports
|
||||||
|
/// (e.g. GPX). For UI use the `typeLabel(l10n)` extension in
|
||||||
|
/// `lib/l10n/contact_localization.dart`.
|
||||||
|
String get typeLabelRaw {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case advTypeChat:
|
case advTypeChat:
|
||||||
return 'Chat';
|
return 'Chat';
|
||||||
@@ -50,18 +61,17 @@ class Contact {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String get pathLabel {
|
bool get hasLocation {
|
||||||
if (pathOverride != null) {
|
const double epsilon = 1e-6;
|
||||||
if (pathOverride! < 0) return 'Flood (forced)';
|
final lat = latitude ?? 0.0;
|
||||||
if (pathOverride == 0) return 'Direct (forced)';
|
final lon = longitude ?? 0.0;
|
||||||
return '$pathOverride hops (forced)';
|
return (lat.abs() > epsilon || lon.abs() > epsilon) &&
|
||||||
}
|
lat >= -90.0 &&
|
||||||
if (pathLength < 0) return 'Flood';
|
lat <= 90.0 &&
|
||||||
if (pathLength == 0) return 'Direct';
|
lon >= -180.0 &&
|
||||||
return '$pathLength hops';
|
lon <= 180.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get hasLocation => latitude != null && longitude != null;
|
|
||||||
bool get isFavorite => (flags & contactFlagFavorite) != 0;
|
bool get isFavorite => (flags & contactFlagFavorite) != 0;
|
||||||
|
|
||||||
Contact copyWith({
|
Contact copyWith({
|
||||||
@@ -78,6 +88,9 @@ class Contact {
|
|||||||
double? longitude,
|
double? longitude,
|
||||||
DateTime? lastSeen,
|
DateTime? lastSeen,
|
||||||
DateTime? lastMessageAt,
|
DateTime? lastMessageAt,
|
||||||
|
DateTime? lastModified,
|
||||||
|
bool? isActive,
|
||||||
|
Uint8List? rawPacket,
|
||||||
}) {
|
}) {
|
||||||
return Contact(
|
return Contact(
|
||||||
publicKey: publicKey ?? this.publicKey,
|
publicKey: publicKey ?? this.publicKey,
|
||||||
@@ -96,18 +109,20 @@ class Contact {
|
|||||||
longitude: longitude ?? this.longitude,
|
longitude: longitude ?? this.longitude,
|
||||||
lastSeen: lastSeen ?? this.lastSeen,
|
lastSeen: lastSeen ?? this.lastSeen,
|
||||||
lastMessageAt: lastMessageAt ?? this.lastMessageAt,
|
lastMessageAt: lastMessageAt ?? this.lastMessageAt,
|
||||||
|
lastModified: lastModified ?? this.lastModified,
|
||||||
|
isActive: isActive ?? this.isActive,
|
||||||
|
rawPacket: rawPacket ?? this.rawPacket,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String get pathIdList {
|
/// Formats path bytes into comma-separated hex groups of [hashByteWidth] bytes.
|
||||||
final pathBytes = _pathBytesForDisplay;
|
String pathFormattedIdList(int hashByteWidth) {
|
||||||
|
final pathBytes = pathBytesForDisplay;
|
||||||
if (pathBytes.isEmpty) return '';
|
if (pathBytes.isEmpty) return '';
|
||||||
|
final w = hashByteWidth.clamp(1, 8);
|
||||||
final parts = <String>[];
|
final parts = <String>[];
|
||||||
final groupSize = pathHashSize;
|
for (int i = 0; i < pathBytes.length; i += w) {
|
||||||
for (int i = 0; i < pathBytes.length; i += groupSize) {
|
final end = (i + w) <= pathBytes.length ? (i + w) : pathBytes.length;
|
||||||
final end = (i + groupSize) <= pathBytes.length
|
|
||||||
? (i + groupSize)
|
|
||||||
: pathBytes.length;
|
|
||||||
final chunk = pathBytes.sublist(i, end);
|
final chunk = pathBytes.sublist(i, end);
|
||||||
parts.add(
|
parts.add(
|
||||||
chunk
|
chunk
|
||||||
@@ -118,47 +133,14 @@ class Contact {
|
|||||||
return parts.join(',');
|
return parts.join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Default grouping uses legacy single-byte hop hash width.
|
||||||
|
String get pathIdList => pathFormattedIdList(pathHashSize);
|
||||||
|
|
||||||
String get shortPubKeyHex {
|
String get shortPubKeyHex {
|
||||||
return "<${publicKeyHex.substring(0, 8)}...${publicKeyHex.substring(publicKeyHex.length - 8)}>";
|
return "<${publicKeyHex.substring(0, 8)}...${publicKeyHex.substring(publicKeyHex.length - 8)}>";
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint8List? get traceRouteBytes {
|
Uint8List get pathBytesForDisplay {
|
||||||
final pathBytes = _pathBytesForDisplay;
|
|
||||||
Uint8List? traceBytes;
|
|
||||||
|
|
||||||
if (pathBytes.isEmpty) {
|
|
||||||
traceBytes = Uint8List(1);
|
|
||||||
traceBytes[0] = publicKey[0];
|
|
||||||
return traceBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == advTypeRepeater || type == advTypeRoom) {
|
|
||||||
final len = (pathBytes.length + pathBytes.length + 1);
|
|
||||||
traceBytes = Uint8List(len);
|
|
||||||
traceBytes[pathBytes.length] = publicKey[0];
|
|
||||||
for (int i = 0; i < pathBytes.length; i++) {
|
|
||||||
traceBytes[i] = pathBytes[i];
|
|
||||||
if (i < pathBytes.length) {
|
|
||||||
traceBytes[len - 1 - i] = pathBytes[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (pathBytes.length < 2) {
|
|
||||||
return pathBytes[0] == 0 ? null : pathBytes;
|
|
||||||
}
|
|
||||||
final len = (pathBytes.length + pathBytes.length - 1);
|
|
||||||
traceBytes = Uint8List(len);
|
|
||||||
for (int i = 0; i < pathBytes.length; i++) {
|
|
||||||
traceBytes[i] = pathBytes[i];
|
|
||||||
if (i < pathBytes.length - 1) {
|
|
||||||
traceBytes[len - 1 - i] = pathBytes[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return traceBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List get _pathBytesForDisplay {
|
|
||||||
if (pathOverride != null) {
|
if (pathOverride != null) {
|
||||||
if (pathOverride! < 0) return Uint8List(0);
|
if (pathOverride! < 0) return Uint8List(0);
|
||||||
return pathOverrideBytes ?? Uint8List(0);
|
return pathOverrideBytes ?? Uint8List(0);
|
||||||
@@ -175,6 +157,12 @@ class Contact {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final pubKey = reader.readBytes(pubKeySize);
|
final pubKey = reader.readBytes(pubKeySize);
|
||||||
|
|
||||||
|
// Guard: reject contacts with zeroed or mostly-zeroed public keys
|
||||||
|
// (indicates corrupt flash storage on the firmware side)
|
||||||
|
final zeroCount = pubKey.where((b) => b == 0).length;
|
||||||
|
if (zeroCount > pubKeySize ~/ 2) return null;
|
||||||
|
|
||||||
final type = reader.readByte();
|
final type = reader.readByte();
|
||||||
final flags = reader.readByte();
|
final flags = reader.readByte();
|
||||||
final pathLen = reader.readByte();
|
final pathLen = reader.readByte();
|
||||||
@@ -184,14 +172,40 @@ class Contact {
|
|||||||
final pathBytes = reader.readBytes(maxPathSize).sublist(0, safePathLen);
|
final pathBytes = reader.readBytes(maxPathSize).sublist(0, safePathLen);
|
||||||
final name = reader.readCStringGreedy(maxNameSize);
|
final name = reader.readCStringGreedy(maxNameSize);
|
||||||
|
|
||||||
final lastMod = reader.readUInt32LE();
|
// Guard: reject contacts with non-printable names (corrupt flash data)
|
||||||
|
if (name.isNotEmpty &&
|
||||||
|
name.codeUnits.every((c) => c < 0x20 || c == 0xFFFD)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mandatory last_advert_timestamp
|
||||||
|
final lastAdvertTimestamp = reader.readUInt32LE();
|
||||||
|
|
||||||
double? lat, lon;
|
double? lat, lon;
|
||||||
final latRaw = reader.readInt32LE();
|
DateTime? lastModified;
|
||||||
final lonRaw = reader.readInt32LE();
|
if (reader.remaining >= 12) {
|
||||||
if (latRaw != 0 || lonRaw != 0) {
|
final latRaw = reader.readInt32LE();
|
||||||
lat = latRaw / 1e6;
|
final lonRaw = reader.readInt32LE();
|
||||||
lon = lonRaw / 1e6;
|
final lastModRaw = reader.readUInt32LE();
|
||||||
|
// TODO: should this be &&?
|
||||||
|
if (latRaw != 0 || lonRaw != 0) {
|
||||||
|
lat = latRaw / 1e6;
|
||||||
|
lon = lonRaw / 1e6;
|
||||||
|
}
|
||||||
|
if (lastModRaw != 0) {
|
||||||
|
lastModified = DateTime.fromMillisecondsSinceEpoch(lastModRaw * 1000);
|
||||||
|
}
|
||||||
|
} else if (reader.remaining >= 8) {
|
||||||
|
// Old layout: gps without lastmod
|
||||||
|
final latRaw = reader.readInt32LE();
|
||||||
|
final lonRaw = reader.readInt32LE();
|
||||||
|
if (latRaw != 0 || lonRaw != 0) {
|
||||||
|
lat = latRaw / 1e6;
|
||||||
|
lon = lonRaw / 1e6;
|
||||||
|
}
|
||||||
|
appLogger.info(
|
||||||
|
'Contact ${pubKeyToHex(pubKey).substring(0, 8)} has gps but no lastmod (legacy firmware layout)',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Contact(
|
return Contact(
|
||||||
@@ -199,11 +213,16 @@ class Contact {
|
|||||||
name: name.isEmpty ? 'Unknown' : name,
|
name: name.isEmpty ? 'Unknown' : name,
|
||||||
type: type,
|
type: type,
|
||||||
flags: flags,
|
flags: flags,
|
||||||
pathLength: pathLen > 0 ? (pathLen > maxPathSize ? -1 : pathLen) : -1,
|
pathLength: (pathLen == 0xFF || pathLen > maxPathSize) ? -1 : pathLen,
|
||||||
path: pathBytes,
|
path: pathBytes,
|
||||||
latitude: lat,
|
latitude: lat,
|
||||||
longitude: lon,
|
longitude: lon,
|
||||||
lastSeen: DateTime.fromMillisecondsSinceEpoch(lastMod * 1000),
|
lastSeen: DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
lastAdvertTimestamp * 1000,
|
||||||
|
),
|
||||||
|
lastModified: lastModified,
|
||||||
|
isActive: true,
|
||||||
|
rawPacket: null,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
appLogger.error('Failed to parse contact frame: $e');
|
appLogger.error('Failed to parse contact frame: $e');
|
||||||
@@ -217,4 +236,7 @@ class Contact {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => publicKeyHex.hashCode;
|
int get hashCode => publicKeyHex.hashCode;
|
||||||
|
bool get teleBaseEnabled => (flags & contactFlagTeleBase) != 0;
|
||||||
|
bool get teleLocEnabled => (flags & contactFlagTeleLoc) != 0;
|
||||||
|
bool get teleEnvEnabled => (flags & contactFlagTeleEnv) != 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
class DeliveryObservation {
|
||||||
|
final String contactKey;
|
||||||
|
final int pathLength;
|
||||||
|
final int messageBytes;
|
||||||
|
final int secondsSinceLastRx;
|
||||||
|
final bool isFlood;
|
||||||
|
final int deliveryMs;
|
||||||
|
final DateTime timestamp;
|
||||||
|
|
||||||
|
DeliveryObservation({
|
||||||
|
required this.contactKey,
|
||||||
|
required this.pathLength,
|
||||||
|
required this.messageBytes,
|
||||||
|
required this.secondsSinceLastRx,
|
||||||
|
required this.isFlood,
|
||||||
|
required this.deliveryMs,
|
||||||
|
required this.timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'contact_key': contactKey,
|
||||||
|
'path_length': pathLength,
|
||||||
|
'message_bytes': messageBytes,
|
||||||
|
'seconds_since_last_rx': secondsSinceLastRx,
|
||||||
|
'is_flood': isFlood,
|
||||||
|
'delivery_ms': deliveryMs,
|
||||||
|
'timestamp': timestamp.toIso8601String(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory DeliveryObservation.fromJson(Map<String, dynamic> json) {
|
||||||
|
return DeliveryObservation(
|
||||||
|
contactKey: json['contact_key'] as String,
|
||||||
|
pathLength: json['path_length'] as int,
|
||||||
|
messageBytes: json['message_bytes'] as int,
|
||||||
|
secondsSinceLastRx: json['seconds_since_last_rx'] as int? ?? 0,
|
||||||
|
isFlood: json['is_flood'] as bool,
|
||||||
|
deliveryMs: json['delivery_ms'] as int,
|
||||||
|
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import 'dart:typed_data';
|
|
||||||
import '../connector/meshcore_protocol.dart';
|
|
||||||
|
|
||||||
class DiscoveryContact {
|
|
||||||
final Uint8List rawPacket;
|
|
||||||
final Uint8List publicKey;
|
|
||||||
final String name;
|
|
||||||
final int type;
|
|
||||||
final int pathLength; // -1 = flood, 0+ = direct hops (from device)
|
|
||||||
final Uint8List path; // Path bytes from device
|
|
||||||
final double? latitude;
|
|
||||||
final double? longitude;
|
|
||||||
final DateTime lastSeen;
|
|
||||||
|
|
||||||
DiscoveryContact({
|
|
||||||
required this.rawPacket,
|
|
||||||
required this.publicKey,
|
|
||||||
required this.name,
|
|
||||||
required this.type,
|
|
||||||
required this.pathLength,
|
|
||||||
required this.path,
|
|
||||||
this.latitude,
|
|
||||||
this.longitude,
|
|
||||||
required this.lastSeen,
|
|
||||||
});
|
|
||||||
|
|
||||||
String get publicKeyHex => pubKeyToHex(publicKey);
|
|
||||||
|
|
||||||
String get typeLabel {
|
|
||||||
switch (type) {
|
|
||||||
case advTypeChat:
|
|
||||||
return 'Chat';
|
|
||||||
case advTypeRepeater:
|
|
||||||
return 'Repeater';
|
|
||||||
case advTypeRoom:
|
|
||||||
return 'Room';
|
|
||||||
case advTypeSensor:
|
|
||||||
return 'Sensor';
|
|
||||||
default:
|
|
||||||
return 'Unknown';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String get pathLabel {
|
|
||||||
if (pathLength < 0) return 'Flood';
|
|
||||||
if (pathLength == 0) return 'Direct';
|
|
||||||
return '$pathLength hops';
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get hasLocation => latitude != null && longitude != null;
|
|
||||||
|
|
||||||
DiscoveryContact copyWith({
|
|
||||||
Uint8List? rawPacket,
|
|
||||||
Uint8List? publicKey,
|
|
||||||
String? name,
|
|
||||||
int? type,
|
|
||||||
int? pathLength,
|
|
||||||
Uint8List? path,
|
|
||||||
double? latitude,
|
|
||||||
double? longitude,
|
|
||||||
DateTime? lastSeen,
|
|
||||||
}) {
|
|
||||||
return DiscoveryContact(
|
|
||||||
rawPacket: rawPacket ?? this.rawPacket,
|
|
||||||
publicKey: publicKey ?? this.publicKey,
|
|
||||||
name: name ?? this.name,
|
|
||||||
type: type ?? this.type,
|
|
||||||
pathLength: pathLength ?? this.pathLength,
|
|
||||||
path: path ?? this.path,
|
|
||||||
latitude: latitude ?? this.latitude,
|
|
||||||
longitude: longitude ?? this.longitude,
|
|
||||||
lastSeen: lastSeen ?? this.lastSeen,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String get pathIdList {
|
|
||||||
final pathBytes = path;
|
|
||||||
if (pathBytes.isEmpty) return '';
|
|
||||||
final parts = <String>[];
|
|
||||||
final groupSize = pathHashSize;
|
|
||||||
for (int i = 0; i < pathBytes.length; i += groupSize) {
|
|
||||||
final end = (i + groupSize) <= pathBytes.length
|
|
||||||
? (i + groupSize)
|
|
||||||
: pathBytes.length;
|
|
||||||
final chunk = pathBytes.sublist(i, end);
|
|
||||||
parts.add(
|
|
||||||
chunk
|
|
||||||
.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase())
|
|
||||||
.join(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return parts.join(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
String get shortPubKeyHex {
|
|
||||||
return "<${publicKeyHex.substring(0, 8)}...${publicKeyHex.substring(publicKeyHex.length - 8)}>";
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
other is DiscoveryContact && publicKeyHex == other.publicKeyHex;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => publicKeyHex.hashCode;
|
|
||||||
}
|
|
||||||
+77
-30
@@ -1,28 +1,37 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import '../connector/meshcore_protocol.dart';
|
import '../connector/meshcore_protocol.dart';
|
||||||
import '../helpers/reaction_helper.dart';
|
import '../helpers/reaction_helper.dart';
|
||||||
|
import 'translation_support.dart';
|
||||||
|
|
||||||
enum MessageStatus { pending, sent, delivered, failed }
|
enum MessageStatus { pending, sent, delivered, failed }
|
||||||
|
|
||||||
class Message {
|
class Message {
|
||||||
|
static const Object _unset = Object();
|
||||||
|
|
||||||
final Uint8List senderKey;
|
final Uint8List senderKey;
|
||||||
final String text;
|
final String text;
|
||||||
final DateTime timestamp;
|
final DateTime timestamp;
|
||||||
final bool isOutgoing;
|
final bool isOutgoing;
|
||||||
final bool isCli;
|
final bool isCli;
|
||||||
final MessageStatus status;
|
final MessageStatus status;
|
||||||
|
final String? originalText;
|
||||||
|
final String? translatedText;
|
||||||
|
final String? translatedLanguageCode;
|
||||||
|
final MessageTranslationStatus translationStatus;
|
||||||
|
final String? translationModelId;
|
||||||
|
|
||||||
// NEW: Retry logic fields
|
// NEW: Retry logic fields
|
||||||
final String? messageId;
|
final String messageId;
|
||||||
final int retryCount;
|
final int retryCount;
|
||||||
final int? estimatedTimeoutMs;
|
final int? estimatedTimeoutMs;
|
||||||
final Uint8List? expectedAckHash;
|
final int? expectedAckHash;
|
||||||
final DateTime? sentAt;
|
final DateTime? sentAt;
|
||||||
final DateTime? deliveredAt;
|
final DateTime? deliveredAt;
|
||||||
final int? tripTimeMs;
|
final int? tripTimeMs;
|
||||||
final int? pathLength;
|
final int? pathLength;
|
||||||
final Uint8List pathBytes;
|
final Uint8List pathBytes;
|
||||||
final Map<String, int> reactions;
|
final Map<String, int> reactions;
|
||||||
|
final Map<String, MessageStatus> reactionStatuses;
|
||||||
final Uint8List fourByteRoomContactKey;
|
final Uint8List fourByteRoomContactKey;
|
||||||
|
|
||||||
Message({
|
Message({
|
||||||
@@ -32,7 +41,12 @@ class Message {
|
|||||||
required this.isOutgoing,
|
required this.isOutgoing,
|
||||||
this.isCli = false,
|
this.isCli = false,
|
||||||
this.status = MessageStatus.pending,
|
this.status = MessageStatus.pending,
|
||||||
this.messageId,
|
String? messageId,
|
||||||
|
this.originalText,
|
||||||
|
this.translatedText,
|
||||||
|
this.translatedLanguageCode,
|
||||||
|
this.translationStatus = MessageTranslationStatus.none,
|
||||||
|
this.translationModelId,
|
||||||
this.retryCount = 0,
|
this.retryCount = 0,
|
||||||
this.estimatedTimeoutMs,
|
this.estimatedTimeoutMs,
|
||||||
this.expectedAckHash,
|
this.expectedAckHash,
|
||||||
@@ -43,9 +57,14 @@ class Message {
|
|||||||
Uint8List? pathBytes,
|
Uint8List? pathBytes,
|
||||||
Uint8List? fourByteRoomContactKey,
|
Uint8List? fourByteRoomContactKey,
|
||||||
Map<String, int>? reactions,
|
Map<String, int>? reactions,
|
||||||
}) : pathBytes = pathBytes ?? Uint8List(0),
|
Map<String, MessageStatus>? reactionStatuses,
|
||||||
|
}) : messageId =
|
||||||
|
messageId ??
|
||||||
|
'${timestamp.millisecondsSinceEpoch}_${pubKeyToHex(senderKey)}_${text.hashCode}',
|
||||||
|
pathBytes = pathBytes ?? Uint8List(0),
|
||||||
fourByteRoomContactKey = fourByteRoomContactKey ?? Uint8List(0),
|
fourByteRoomContactKey = fourByteRoomContactKey ?? Uint8List(0),
|
||||||
reactions = reactions ?? {};
|
reactions = reactions ?? {},
|
||||||
|
reactionStatuses = reactionStatuses ?? {};
|
||||||
|
|
||||||
String get senderKeyHex => pubKeyToHex(senderKey);
|
String get senderKeyHex => pubKeyToHex(senderKey);
|
||||||
|
|
||||||
@@ -53,14 +72,20 @@ class Message {
|
|||||||
MessageStatus? status,
|
MessageStatus? status,
|
||||||
int? retryCount,
|
int? retryCount,
|
||||||
int? estimatedTimeoutMs,
|
int? estimatedTimeoutMs,
|
||||||
Uint8List? expectedAckHash,
|
int? expectedAckHash,
|
||||||
DateTime? sentAt,
|
DateTime? sentAt,
|
||||||
DateTime? deliveredAt,
|
DateTime? deliveredAt,
|
||||||
int? tripTimeMs,
|
int? tripTimeMs,
|
||||||
int? pathLength,
|
int? pathLength,
|
||||||
Uint8List? pathBytes,
|
Uint8List? pathBytes,
|
||||||
bool? isCli,
|
bool? isCli,
|
||||||
|
Object? originalText = _unset,
|
||||||
|
Object? translatedText = _unset,
|
||||||
|
Object? translatedLanguageCode = _unset,
|
||||||
|
MessageTranslationStatus? translationStatus,
|
||||||
|
Object? translationModelId = _unset,
|
||||||
Map<String, int>? reactions,
|
Map<String, int>? reactions,
|
||||||
|
Map<String, MessageStatus>? reactionStatuses,
|
||||||
Uint8List? fourByteRoomContactKey,
|
Uint8List? fourByteRoomContactKey,
|
||||||
}) {
|
}) {
|
||||||
return Message(
|
return Message(
|
||||||
@@ -71,6 +96,19 @@ class Message {
|
|||||||
isCli: isCli ?? this.isCli,
|
isCli: isCli ?? this.isCli,
|
||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
messageId: messageId,
|
messageId: messageId,
|
||||||
|
originalText: originalText == _unset
|
||||||
|
? this.originalText
|
||||||
|
: originalText as String?,
|
||||||
|
translatedText: translatedText == _unset
|
||||||
|
? this.translatedText
|
||||||
|
: translatedText as String?,
|
||||||
|
translatedLanguageCode: translatedLanguageCode == _unset
|
||||||
|
? this.translatedLanguageCode
|
||||||
|
: translatedLanguageCode as String?,
|
||||||
|
translationStatus: translationStatus ?? this.translationStatus,
|
||||||
|
translationModelId: translationModelId == _unset
|
||||||
|
? this.translationModelId
|
||||||
|
: translationModelId as String?,
|
||||||
retryCount: retryCount ?? this.retryCount,
|
retryCount: retryCount ?? this.retryCount,
|
||||||
estimatedTimeoutMs: estimatedTimeoutMs ?? this.estimatedTimeoutMs,
|
estimatedTimeoutMs: estimatedTimeoutMs ?? this.estimatedTimeoutMs,
|
||||||
expectedAckHash: expectedAckHash ?? this.expectedAckHash,
|
expectedAckHash: expectedAckHash ?? this.expectedAckHash,
|
||||||
@@ -80,49 +118,58 @@ class Message {
|
|||||||
pathLength: pathLength ?? this.pathLength,
|
pathLength: pathLength ?? this.pathLength,
|
||||||
pathBytes: pathBytes ?? this.pathBytes,
|
pathBytes: pathBytes ?? this.pathBytes,
|
||||||
reactions: reactions ?? this.reactions,
|
reactions: reactions ?? this.reactions,
|
||||||
|
reactionStatuses: reactionStatuses ?? this.reactionStatuses,
|
||||||
fourByteRoomContactKey:
|
fourByteRoomContactKey:
|
||||||
fourByteRoomContactKey ?? this.fourByteRoomContactKey,
|
fourByteRoomContactKey ?? this.fourByteRoomContactKey,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Message? fromFrame(Uint8List data, Uint8List selfPubKey) {
|
static Message? fromFrame(Uint8List frame, Uint8List selfPubKey) {
|
||||||
if (data.length < msgTextOffset + 1) return null;
|
if (frame.length < msgTextOffset + 1) return null;
|
||||||
|
final reader = BufferReader(frame);
|
||||||
|
try {
|
||||||
|
final code = reader.readByte();
|
||||||
|
if (code != respCodeContactMsgRecv && code != respCodeContactMsgRecvV3) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
final code = data[0];
|
final senderKey = reader.readBytes(pubKeySize);
|
||||||
if (code != respCodeContactMsgRecv && code != respCodeContactMsgRecvV3) {
|
final timestampRaw = reader.readInt32LE();
|
||||||
|
final flags = reader.readByte();
|
||||||
|
if ((flags >> 2) != txtTypePlain) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final text = reader.readCString();
|
||||||
|
|
||||||
|
return Message(
|
||||||
|
senderKey: senderKey,
|
||||||
|
text: text,
|
||||||
|
timestamp: DateTime.fromMillisecondsSinceEpoch(timestampRaw * 1000),
|
||||||
|
isOutgoing: false,
|
||||||
|
isCli: false,
|
||||||
|
status: MessageStatus.delivered,
|
||||||
|
pathBytes: Uint8List(0),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final senderKey = Uint8List.fromList(
|
|
||||||
data.sublist(msgPubKeyOffset, msgPubKeyOffset + pubKeySize),
|
|
||||||
);
|
|
||||||
final timestampRaw = readUint32LE(data, msgTimestampOffset);
|
|
||||||
final flags = data[msgFlagsOffset];
|
|
||||||
if ((flags >> 2) != txtTypePlain) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final text = readCString(data, msgTextOffset, data.length - msgTextOffset);
|
|
||||||
|
|
||||||
return Message(
|
|
||||||
senderKey: senderKey,
|
|
||||||
text: text,
|
|
||||||
timestamp: DateTime.fromMillisecondsSinceEpoch(timestampRaw * 1000),
|
|
||||||
isOutgoing: false,
|
|
||||||
isCli: false,
|
|
||||||
status: MessageStatus.delivered,
|
|
||||||
pathBytes: Uint8List(0),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Message outgoing(
|
static Message outgoing(
|
||||||
Uint8List recipientKey,
|
Uint8List recipientKey,
|
||||||
String text, {
|
String text, {
|
||||||
|
String? originalText,
|
||||||
|
String? translatedLanguageCode,
|
||||||
|
String? translationModelId,
|
||||||
int? pathLength,
|
int? pathLength,
|
||||||
Uint8List? pathBytes,
|
Uint8List? pathBytes,
|
||||||
}) {
|
}) {
|
||||||
return Message(
|
return Message(
|
||||||
senderKey: recipientKey,
|
senderKey: recipientKey,
|
||||||
text: text,
|
text: text,
|
||||||
|
originalText: originalText,
|
||||||
|
translatedLanguageCode: translatedLanguageCode,
|
||||||
|
translationModelId: translationModelId,
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
isCli: false,
|
isCli: false,
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
class PathRecord {
|
class PathRecord {
|
||||||
final int hopCount;
|
final int hopCount;
|
||||||
final int tripTimeMs;
|
final int tripTimeMs;
|
||||||
final DateTime timestamp;
|
final DateTime? timestamp;
|
||||||
final bool wasFloodDiscovery;
|
final bool wasFloodDiscovery;
|
||||||
|
final int byteCount;
|
||||||
final List<int> pathBytes;
|
final List<int> pathBytes;
|
||||||
final int successCount;
|
final int successCount;
|
||||||
final int failureCount;
|
final int failureCount;
|
||||||
|
final double routeWeight;
|
||||||
|
|
||||||
PathRecord({
|
PathRecord({
|
||||||
required this.hopCount,
|
required this.hopCount,
|
||||||
@@ -15,6 +17,8 @@ class PathRecord {
|
|||||||
required this.pathBytes,
|
required this.pathBytes,
|
||||||
required this.successCount,
|
required this.successCount,
|
||||||
required this.failureCount,
|
required this.failureCount,
|
||||||
|
this.routeWeight = 1.0,
|
||||||
|
this.byteCount = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
String get displayText =>
|
String get displayText =>
|
||||||
@@ -24,11 +28,12 @@ class PathRecord {
|
|||||||
return {
|
return {
|
||||||
'hop_count': hopCount,
|
'hop_count': hopCount,
|
||||||
'trip_time_ms': tripTimeMs,
|
'trip_time_ms': tripTimeMs,
|
||||||
'timestamp': timestamp.toIso8601String(),
|
'timestamp': timestamp?.toIso8601String(),
|
||||||
'was_flood': wasFloodDiscovery,
|
'was_flood': wasFloodDiscovery,
|
||||||
'path_bytes': pathBytes,
|
'path_bytes': pathBytes,
|
||||||
'success_count': successCount,
|
'success_count': successCount,
|
||||||
'failure_count': failureCount,
|
'failure_count': failureCount,
|
||||||
|
'route_weight': routeWeight,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,12 +41,16 @@ class PathRecord {
|
|||||||
return PathRecord(
|
return PathRecord(
|
||||||
hopCount: json['hop_count'] as int,
|
hopCount: json['hop_count'] as int,
|
||||||
tripTimeMs: json['trip_time_ms'] as int,
|
tripTimeMs: json['trip_time_ms'] as int,
|
||||||
timestamp: DateTime.parse(json['timestamp'] as String),
|
timestamp: json['timestamp'] != null
|
||||||
|
? DateTime.parse(json['timestamp'] as String)
|
||||||
|
: null,
|
||||||
wasFloodDiscovery: json['was_flood'] as bool,
|
wasFloodDiscovery: json['was_flood'] as bool,
|
||||||
pathBytes:
|
pathBytes:
|
||||||
(json['path_bytes'] as List?)?.map((b) => b as int).toList() ?? [],
|
(json['path_bytes'] as List?)?.map((b) => b as int).toList() ?? [],
|
||||||
successCount: json['success_count'] as int? ?? 0,
|
successCount: json['success_count'] as int? ?? 0,
|
||||||
failureCount: json['failure_count'] as int? ?? 0,
|
failureCount: json['failure_count'] as int? ?? 0,
|
||||||
|
routeWeight: (json['route_weight'] as num?)?.toDouble() ?? 1.0,
|
||||||
|
byteCount: json['byte_count'] as int? ?? 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'contact.dart';
|
||||||
|
|
||||||
|
const int recentAttemptDiversityWindow = 2;
|
||||||
|
|
||||||
class PathSelection {
|
class PathSelection {
|
||||||
final List<int> pathBytes;
|
final List<int> pathBytes;
|
||||||
final int hopCount;
|
final int hopCount;
|
||||||
@@ -9,3 +15,38 @@ class PathSelection {
|
|||||||
required this.useFlood,
|
required this.useFlood,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PathSelection resolvePathSelection(
|
||||||
|
Contact contact, {
|
||||||
|
PathSelection? selection,
|
||||||
|
bool forceFlood = false,
|
||||||
|
}) {
|
||||||
|
if (contact.pathOverride != null) {
|
||||||
|
if (contact.pathOverride! < 0) {
|
||||||
|
return const PathSelection(pathBytes: [], hopCount: -1, useFlood: true);
|
||||||
|
}
|
||||||
|
return PathSelection(
|
||||||
|
pathBytes: contact.pathOverrideBytes ?? Uint8List(0),
|
||||||
|
hopCount: contact.pathOverride!,
|
||||||
|
useFlood: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forceFlood || contact.pathLength < 0 || selection?.useFlood == true) {
|
||||||
|
return const PathSelection(pathBytes: [], hopCount: -1, useFlood: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection != null && selection.pathBytes.isNotEmpty) {
|
||||||
|
return PathSelection(
|
||||||
|
pathBytes: selection.pathBytes,
|
||||||
|
hopCount: selection.hopCount,
|
||||||
|
useFlood: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PathSelection(
|
||||||
|
pathBytes: contact.path,
|
||||||
|
hopCount: contact.pathLength,
|
||||||
|
useFlood: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ class RadioSettings {
|
|||||||
frequencyMHz: 433.0,
|
frequencyMHz: 433.0,
|
||||||
bandwidth: LoRaBandwidth.bw250,
|
bandwidth: LoRaBandwidth.bw250,
|
||||||
spreadingFactor: LoRaSpreadingFactor.sf11,
|
spreadingFactor: LoRaSpreadingFactor.sf11,
|
||||||
codingRate: LoRaCodingRate.cr4_5,
|
codingRate: LoRaCodingRate.cr4_8,
|
||||||
txPowerDbm: 20,
|
txPowerDbm: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -238,7 +238,7 @@ class RadioSettings {
|
|||||||
frequencyMHz: 869.0,
|
frequencyMHz: 869.0,
|
||||||
bandwidth: LoRaBandwidth.bw250,
|
bandwidth: LoRaBandwidth.bw250,
|
||||||
spreadingFactor: LoRaSpreadingFactor.sf11,
|
spreadingFactor: LoRaSpreadingFactor.sf11,
|
||||||
codingRate: LoRaCodingRate.cr4_5,
|
codingRate: LoRaCodingRate.cr4_8,
|
||||||
txPowerDbm: 14,
|
txPowerDbm: 14,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -248,7 +248,7 @@ class RadioSettings {
|
|||||||
frequencyMHz: 918.0,
|
frequencyMHz: 918.0,
|
||||||
bandwidth: LoRaBandwidth.bw250,
|
bandwidth: LoRaBandwidth.bw250,
|
||||||
spreadingFactor: LoRaSpreadingFactor.sf11,
|
spreadingFactor: LoRaSpreadingFactor.sf11,
|
||||||
codingRate: LoRaCodingRate.cr4_5,
|
codingRate: LoRaCodingRate.cr4_8,
|
||||||
txPowerDbm: 20,
|
txPowerDbm: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
enum MessageTranslationStatus { none, pending, completed, failed, skipped }
|
||||||
|
|
||||||
|
extension MessageTranslationStatusValue on MessageTranslationStatus {
|
||||||
|
String get value {
|
||||||
|
switch (this) {
|
||||||
|
case MessageTranslationStatus.pending:
|
||||||
|
return 'pending';
|
||||||
|
case MessageTranslationStatus.completed:
|
||||||
|
return 'completed';
|
||||||
|
case MessageTranslationStatus.failed:
|
||||||
|
return 'failed';
|
||||||
|
case MessageTranslationStatus.skipped:
|
||||||
|
return 'skipped';
|
||||||
|
case MessageTranslationStatus.none:
|
||||||
|
return 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageTranslationStatus parseMessageTranslationStatus(dynamic value) {
|
||||||
|
if (value is! String) {
|
||||||
|
return MessageTranslationStatus.none;
|
||||||
|
}
|
||||||
|
for (final status in MessageTranslationStatus.values) {
|
||||||
|
if (status.value == value) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MessageTranslationStatus.none;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TranslationModelRecord {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final String sourceUrl;
|
||||||
|
final String localPath;
|
||||||
|
final DateTime downloadedAt;
|
||||||
|
final int fileSizeBytes;
|
||||||
|
|
||||||
|
const TranslationModelRecord({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.sourceUrl,
|
||||||
|
required this.localPath,
|
||||||
|
required this.downloadedAt,
|
||||||
|
required this.fileSizeBytes,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'source_url': sourceUrl,
|
||||||
|
'local_path': localPath,
|
||||||
|
'downloaded_at': downloadedAt.millisecondsSinceEpoch,
|
||||||
|
'file_size_bytes': fileSizeBytes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory TranslationModelRecord.fromJson(Map<String, dynamic> json) {
|
||||||
|
return TranslationModelRecord(
|
||||||
|
id: json['id'] as String? ?? '',
|
||||||
|
name: json['name'] as String? ?? '',
|
||||||
|
sourceUrl: json['source_url'] as String? ?? '',
|
||||||
|
localPath: json['local_path'] as String? ?? '',
|
||||||
|
downloadedAt: DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
json['downloaded_at'] as int? ?? 0,
|
||||||
|
),
|
||||||
|
fileSizeBytes: json['file_size_bytes'] as int? ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String translationModelFriendlyName(TranslationModelRecord model) {
|
||||||
|
switch (model.id) {
|
||||||
|
case 'hy-mt1.5-1.8b-q4_k_m':
|
||||||
|
return 'Tencent HY-MT 1.5 1.8B Q4_K_M';
|
||||||
|
case 'hy-mt1.5-1.8b-q6_k':
|
||||||
|
return 'Tencent HY-MT 1.5 1.8B Q6_K';
|
||||||
|
default:
|
||||||
|
final trimmed = model.name.trim();
|
||||||
|
if (trimmed.endsWith('.gguf')) {
|
||||||
|
return trimmed.substring(0, trimmed.length - 5);
|
||||||
|
}
|
||||||
|
return trimmed.isEmpty ? model.id : trimmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TranslationLanguageOption {
|
||||||
|
final String code;
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
const TranslationLanguageOption({required this.code, required this.label});
|
||||||
|
}
|
||||||
|
|
||||||
|
const List<TranslationLanguageOption> supportedTranslationLanguages = [
|
||||||
|
TranslationLanguageOption(code: 'bg', label: 'Bulgarian'),
|
||||||
|
TranslationLanguageOption(code: 'de', label: 'German'),
|
||||||
|
TranslationLanguageOption(code: 'en', label: 'English'),
|
||||||
|
TranslationLanguageOption(code: 'es', label: 'Spanish'),
|
||||||
|
TranslationLanguageOption(code: 'fr', label: 'French'),
|
||||||
|
TranslationLanguageOption(code: 'hu', label: 'Hungarian'),
|
||||||
|
TranslationLanguageOption(code: 'it', label: 'Italian'),
|
||||||
|
TranslationLanguageOption(code: 'ja', label: 'Japanese'),
|
||||||
|
TranslationLanguageOption(code: 'ko', label: 'Korean'),
|
||||||
|
TranslationLanguageOption(code: 'nl', label: 'Dutch'),
|
||||||
|
TranslationLanguageOption(code: 'pl', label: 'Polish'),
|
||||||
|
TranslationLanguageOption(code: 'pt', label: 'Portuguese'),
|
||||||
|
TranslationLanguageOption(code: 'ru', label: 'Russian'),
|
||||||
|
TranslationLanguageOption(code: 'sk', label: 'Slovak'),
|
||||||
|
TranslationLanguageOption(code: 'sl', label: 'Slovenian'),
|
||||||
|
TranslationLanguageOption(code: 'sv', label: 'Swedish'),
|
||||||
|
TranslationLanguageOption(code: 'uk', label: 'Ukrainian'),
|
||||||
|
TranslationLanguageOption(code: 'zh', label: 'Chinese'),
|
||||||
|
];
|
||||||
|
|
||||||
|
final List<TranslationModelRecord> translationPresetModels = [
|
||||||
|
TranslationModelRecord(
|
||||||
|
id: 'hy-mt1.5-1.8b-q4_k_m',
|
||||||
|
name: 'HY-MT1.5-1.8B-Q4_K_M.gguf',
|
||||||
|
sourceUrl:
|
||||||
|
'https://huggingface.co/tencent/HY-MT1.5-1.8B-GGUF/resolve/main/HY-MT1.5-1.8B-Q4_K_M.gguf?download=true',
|
||||||
|
localPath: '',
|
||||||
|
downloadedAt: DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
fileSizeBytes: 0,
|
||||||
|
),
|
||||||
|
TranslationModelRecord(
|
||||||
|
id: 'hy-mt1.5-1.8b-q6_k',
|
||||||
|
name: 'HY-MT1.5-1.8B-Q6_K.gguf',
|
||||||
|
sourceUrl:
|
||||||
|
'https://huggingface.co/tencent/HY-MT1.5-1.8B-GGUF/resolve/main/HY-MT1.5-1.8B-Q6_K.gguf?download=true',
|
||||||
|
localPath: '',
|
||||||
|
downloadedAt: DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
fileSizeBytes: 0,
|
||||||
|
),
|
||||||
|
];
|
||||||
@@ -5,6 +5,7 @@ import 'package:provider/provider.dart';
|
|||||||
import '../l10n/l10n.dart';
|
import '../l10n/l10n.dart';
|
||||||
import '../services/app_debug_log_service.dart';
|
import '../services/app_debug_log_service.dart';
|
||||||
import '../widgets/adaptive_app_bar_title.dart';
|
import '../widgets/adaptive_app_bar_title.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
class AppDebugLogScreen extends StatelessWidget {
|
class AppDebugLogScreen extends StatelessWidget {
|
||||||
const AppDebugLogScreen({super.key});
|
const AppDebugLogScreen({super.key});
|
||||||
@@ -34,8 +35,9 @@ class AppDebugLogScreen extends StatelessWidget {
|
|||||||
.join('\n');
|
.join('\n');
|
||||||
await Clipboard.setData(ClipboardData(text: text));
|
await Clipboard.setData(ClipboardData(text: text));
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.debugLog_copied)),
|
context,
|
||||||
|
content: Text(context.l10n.debugLog_copied),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ import '../l10n/l10n.dart';
|
|||||||
import '../services/ble_debug_log_service.dart';
|
import '../services/ble_debug_log_service.dart';
|
||||||
import '../connector/meshcore_protocol.dart';
|
import '../connector/meshcore_protocol.dart';
|
||||||
import '../widgets/adaptive_app_bar_title.dart';
|
import '../widgets/adaptive_app_bar_title.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
enum _BleLogView { frames, rawLogRx }
|
enum _BleLogView { frames, rawLogRx }
|
||||||
|
|
||||||
@@ -52,10 +53,9 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
|
|||||||
.join('\n');
|
.join('\n');
|
||||||
await Clipboard.setData(ClipboardData(text: text));
|
await Clipboard.setData(ClipboardData(text: text));
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.debugLog_bleCopied),
|
content: Text(context.l10n.debugLog_bleCopied),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
@@ -118,6 +118,19 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
|
|||||||
: Icons.download,
|
: Icons.download,
|
||||||
size: 18,
|
size: 18,
|
||||||
),
|
),
|
||||||
|
onLongPress: () async {
|
||||||
|
await Clipboard.setData(
|
||||||
|
ClipboardData(
|
||||||
|
text: entry.payload
|
||||||
|
.map(
|
||||||
|
(b) => b
|
||||||
|
.toRadixString(16)
|
||||||
|
.padLeft(2, '0'),
|
||||||
|
)
|
||||||
|
.join(''),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,66 +283,66 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
|
|||||||
if (payload.length < 101) {
|
if (payload.length < 101) {
|
||||||
return 'ADVERT (short)';
|
return 'ADVERT (short)';
|
||||||
}
|
}
|
||||||
var offset = 0;
|
final reader = BufferReader(payload);
|
||||||
final pubKey = _bytesToHex(
|
try {
|
||||||
payload.sublist(offset, offset + 32),
|
final pubKey = _bytesToHex(reader.readBytes(pubKeySize), spaced: false);
|
||||||
spaced: false,
|
|
||||||
);
|
final timestamp = reader.readUInt32LE();
|
||||||
offset += 32;
|
reader.skipBytes(signatureSize);
|
||||||
final timestamp = readUint32LE(payload, offset);
|
final flags = reader.readByte();
|
||||||
offset += 4;
|
final role = _deviceRoleLabel(flags & 0x0F);
|
||||||
offset += 64; // signature
|
final hasLocation = (flags & 0x10) != 0;
|
||||||
final flags = payload[offset++];
|
final hasFeature1 = (flags & 0x20) != 0;
|
||||||
final role = _deviceRoleLabel(flags & 0x0F);
|
final hasFeature2 = (flags & 0x40) != 0;
|
||||||
final hasLocation = (flags & 0x10) != 0;
|
final hasName = (flags & 0x80) != 0;
|
||||||
final hasFeature1 = (flags & 0x20) != 0;
|
String? name;
|
||||||
final hasFeature2 = (flags & 0x40) != 0;
|
double? lat;
|
||||||
final hasName = (flags & 0x80) != 0;
|
double? lon;
|
||||||
String? name;
|
if (hasLocation) {
|
||||||
double? lat;
|
lat = reader.readInt32LE() / 1000000.0;
|
||||||
double? lon;
|
lon = reader.readInt32LE() / 1000000.0;
|
||||||
if (hasLocation && payload.length >= offset + 8) {
|
}
|
||||||
lat = readInt32LE(payload, offset) / 1000000.0;
|
if (hasFeature1) reader.skipBytes(2);
|
||||||
lon = readInt32LE(payload, offset + 4) / 1000000.0;
|
if (hasFeature2) reader.skipBytes(2);
|
||||||
offset += 8;
|
if (hasName) {
|
||||||
|
name = reader.readCStringGreedy(maxNameSize);
|
||||||
|
}
|
||||||
|
final namePart = (name != null && name.isNotEmpty) ? ' name="$name"' : '';
|
||||||
|
final locPart = (lat != null && lon != null)
|
||||||
|
? ' loc=${lat.toStringAsFixed(6)},${lon.toStringAsFixed(6)}'
|
||||||
|
: '';
|
||||||
|
return 'ADVERT role=$role ts=$timestamp$namePart$locPart key=${pubKey.substring(0, 12)}…';
|
||||||
|
} catch (e) {
|
||||||
|
return 'ADVERT (invalid)';
|
||||||
}
|
}
|
||||||
if (hasFeature1) offset += 2;
|
|
||||||
if (hasFeature2) offset += 2;
|
|
||||||
if (hasName && payload.length > offset) {
|
|
||||||
final rawName = String.fromCharCodes(payload.sublist(offset));
|
|
||||||
final nul = rawName.indexOf('\u0000');
|
|
||||||
name = nul >= 0 ? rawName.substring(0, nul) : rawName;
|
|
||||||
name = name.trim();
|
|
||||||
}
|
|
||||||
final namePart = (name != null && name.isNotEmpty) ? ' name="$name"' : '';
|
|
||||||
final locPart = (lat != null && lon != null)
|
|
||||||
? ' loc=${lat.toStringAsFixed(6)},${lon.toStringAsFixed(6)}'
|
|
||||||
: '';
|
|
||||||
return 'ADVERT role=$role ts=$timestamp$namePart$locPart key=${pubKey.substring(0, 12)}…';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String _decodeControlSummary(Uint8List payload) {
|
String _decodeControlSummary(Uint8List payload) {
|
||||||
if (payload.isEmpty) return 'CONTROL (empty)';
|
final reader = BufferReader(payload);
|
||||||
final flags = payload[0];
|
try {
|
||||||
final subType = flags & 0xF0;
|
final flags = reader.readByte();
|
||||||
if (subType == 0x80) {
|
final subType = flags & 0xF0;
|
||||||
if (payload.length < 6) return 'CONTROL DISCOVER_REQ (short)';
|
if (subType == 0x80) {
|
||||||
final typeFilter = payload[1];
|
if (payload.length < 6) return 'CONTROL DISCOVER_REQ (short)';
|
||||||
final tag = readUint32LE(payload, 2);
|
final typeFilter = reader.readByte();
|
||||||
final since = payload.length >= 10 ? readUint32LE(payload, 6) : 0;
|
final tag = reader.readInt32LE();
|
||||||
return 'CONTROL DISCOVER_REQ filter=0x${typeFilter.toRadixString(16).padLeft(2, '0')} tag=$tag since=$since';
|
final since = payload.length >= 10 ? reader.readInt32LE() : 0;
|
||||||
|
return 'CONTROL DISCOVER_REQ filter=0x${typeFilter.toRadixString(16).padLeft(2, '0')} tag=$tag since=$since';
|
||||||
|
}
|
||||||
|
if (subType == 0x90) {
|
||||||
|
if (payload.length < 14) return 'CONTROL DISCOVER_RESP (short)';
|
||||||
|
final nodeType = flags & 0x0F;
|
||||||
|
final snrRaw = payload[1];
|
||||||
|
final snrSigned = snrRaw > 127 ? snrRaw - 256 : snrRaw;
|
||||||
|
final snr = snrSigned / 4.0;
|
||||||
|
final tag = reader.readInt32LE();
|
||||||
|
final keyLen = payload.length - 6;
|
||||||
|
return 'CONTROL DISCOVER_RESP node=${_deviceRoleLabel(nodeType)} snr=${snr.toStringAsFixed(2)} tag=$tag key=$keyLen';
|
||||||
|
}
|
||||||
|
return 'CONTROL subtype=0x${subType.toRadixString(16).padLeft(2, '0')}';
|
||||||
|
} catch (e) {
|
||||||
|
return 'CONTROL (invalid)';
|
||||||
}
|
}
|
||||||
if (subType == 0x90) {
|
|
||||||
if (payload.length < 14) return 'CONTROL DISCOVER_RESP (short)';
|
|
||||||
final nodeType = flags & 0x0F;
|
|
||||||
final snrRaw = payload[1];
|
|
||||||
final snrSigned = snrRaw > 127 ? snrRaw - 256 : snrRaw;
|
|
||||||
final snr = snrSigned / 4.0;
|
|
||||||
final tag = readUint32LE(payload, 2);
|
|
||||||
final keyLen = payload.length - 6;
|
|
||||||
return 'CONTROL DISCOVER_RESP node=${_deviceRoleLabel(nodeType)} snr=${snr.toStringAsFixed(2)} tag=$tag key=$keyLen';
|
|
||||||
}
|
|
||||||
return 'CONTROL subtype=0x${subType.toRadixString(16).padLeft(2, '0')}';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String _payloadTypeLabel(int payloadType) {
|
String _payloadTypeLabel(int payloadType) {
|
||||||
|
|||||||
@@ -4,35 +4,50 @@ import 'dart:math' as math;
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../connector/meshcore_connector.dart';
|
import '../connector/meshcore_connector.dart';
|
||||||
|
import '../models/community.dart';
|
||||||
|
import '../storage/community_store.dart';
|
||||||
|
import '../utils/platform_info.dart';
|
||||||
import '../helpers/chat_scroll_controller.dart';
|
import '../helpers/chat_scroll_controller.dart';
|
||||||
import '../connector/meshcore_protocol.dart';
|
import '../connector/meshcore_protocol.dart';
|
||||||
import '../helpers/link_handler.dart';
|
import '../helpers/cyr2lat.dart';
|
||||||
|
import '../helpers/gif_helper.dart';
|
||||||
import '../helpers/reaction_helper.dart';
|
import '../helpers/reaction_helper.dart';
|
||||||
import '../helpers/utf8_length_limiter.dart';
|
import '../helpers/snack_bar_builder.dart';
|
||||||
import '../l10n/l10n.dart';
|
import '../l10n/l10n.dart';
|
||||||
import '../models/channel.dart';
|
import '../models/channel.dart';
|
||||||
import '../models/channel_message.dart';
|
import '../models/channel_message.dart';
|
||||||
|
import '../models/translation_support.dart';
|
||||||
import '../services/app_settings_service.dart';
|
import '../services/app_settings_service.dart';
|
||||||
import '../services/chat_text_scale_service.dart';
|
import '../services/chat_text_scale_service.dart';
|
||||||
|
import '../services/translation_service.dart';
|
||||||
import '../utils/emoji_utils.dart';
|
import '../utils/emoji_utils.dart';
|
||||||
|
import '../widgets/byte_count_input.dart';
|
||||||
import '../widgets/chat_zoom_wrapper.dart';
|
import '../widgets/chat_zoom_wrapper.dart';
|
||||||
import '../widgets/emoji_picker.dart';
|
import '../widgets/emoji_picker.dart';
|
||||||
import '../widgets/gif_message.dart';
|
import '../widgets/gif_message.dart';
|
||||||
import '../widgets/jump_to_bottom_button.dart';
|
import '../widgets/jump_to_bottom_button.dart';
|
||||||
import '../widgets/gif_picker.dart';
|
import '../widgets/gif_picker.dart';
|
||||||
|
import '../widgets/message_translation_button.dart';
|
||||||
import '../widgets/message_status_icon.dart';
|
import '../widgets/message_status_icon.dart';
|
||||||
|
import '../widgets/radio_stats_entry.dart';
|
||||||
|
import '../widgets/translated_message_content.dart';
|
||||||
|
import '../widgets/unread_divider.dart';
|
||||||
import 'channel_message_path_screen.dart';
|
import 'channel_message_path_screen.dart';
|
||||||
import 'map_screen.dart';
|
import 'map_screen.dart';
|
||||||
|
|
||||||
class ChannelChatScreen extends StatefulWidget {
|
class ChannelChatScreen extends StatefulWidget {
|
||||||
final Channel channel;
|
final Channel channel;
|
||||||
|
final int initialUnreadCount;
|
||||||
|
|
||||||
const ChannelChatScreen({super.key, required this.channel});
|
const ChannelChatScreen({
|
||||||
|
super.key,
|
||||||
|
required this.channel,
|
||||||
|
this.initialUnreadCount = 0,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChannelChatScreen> createState() => _ChannelChatScreenState();
|
State<ChannelChatScreen> createState() => _ChannelChatScreenState();
|
||||||
@@ -43,23 +58,97 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
final ChatScrollController _scrollController = ChatScrollController();
|
final ChatScrollController _scrollController = ChatScrollController();
|
||||||
final FocusNode _textFieldFocusNode = FocusNode();
|
final FocusNode _textFieldFocusNode = FocusNode();
|
||||||
ChannelMessage? _replyingToMessage;
|
ChannelMessage? _replyingToMessage;
|
||||||
|
final CommunityStore _communityStore = CommunityStore();
|
||||||
|
final CommunityPskIndex _communityIndex = CommunityPskIndex();
|
||||||
final Map<String, GlobalKey> _messageKeys = {};
|
final Map<String, GlobalKey> _messageKeys = {};
|
||||||
bool _isLoadingOlder = false;
|
bool _isLoadingOlder = false;
|
||||||
|
bool _communitiesLoaded = false;
|
||||||
|
|
||||||
MeshCoreConnector? _connector;
|
MeshCoreConnector? _connector;
|
||||||
|
DateTime? _lastChannelSendAt;
|
||||||
|
bool _channelSkipNextBottomSnap = false;
|
||||||
|
String? _unreadDividerMessageId;
|
||||||
|
|
||||||
|
String? _cachedFormatLocale;
|
||||||
|
late DateFormat _hmFormat;
|
||||||
|
late DateFormat _mdFormat;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_textFieldFocusNode.addListener(_onTextFieldFocusChange);
|
_textFieldFocusNode.addListener(_onTextFieldFocusChange);
|
||||||
_scrollController.onScrollNearTop = _loadOlderMessages;
|
_scrollController.onScrollNearTop = _loadOlderMessages;
|
||||||
|
_scrollController.showJumpToBottom.addListener(_clearDividerAtBottom);
|
||||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_connector = context.read<MeshCoreConnector>();
|
final connector = context.read<MeshCoreConnector>();
|
||||||
_connector?.setActiveChannel(widget.channel.index);
|
final settings = context.read<AppSettingsService>().settings;
|
||||||
|
final idx = widget.channel.index;
|
||||||
|
final unread = widget.initialUnreadCount;
|
||||||
|
final messages = connector.getChannelMessages(widget.channel);
|
||||||
|
_loadCommunities();
|
||||||
|
ChannelMessage? anchor;
|
||||||
|
if (unread > 0) {
|
||||||
|
anchor = _findOldestUnreadChannelAnchor(messages, unread);
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
if (anchor != null) _unreadDividerMessageId = anchor.messageId;
|
||||||
|
});
|
||||||
|
connector.setActiveChannel(idx);
|
||||||
|
_connector = connector;
|
||||||
|
if (anchor != null && settings.jumpToOldestUnread) {
|
||||||
|
_channelSkipNextBottomSnap = true;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
_scrollController.jumpToEstimatedOffset(
|
||||||
|
unreadCount: unread,
|
||||||
|
totalMessages: messages.length,
|
||||||
|
onJumped: () {
|
||||||
|
if (!mounted) return;
|
||||||
|
_scrollToMessage(anchor!.messageId);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Reload communities when returning from another screen
|
||||||
|
Future<void> _loadCommunities() async {
|
||||||
|
final connector = context.read<MeshCoreConnector>();
|
||||||
|
_communityStore.setPublicKeyHex = connector.selfPublicKeyHex;
|
||||||
|
final communities = await _communityStore.loadCommunities();
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_communityIndex.initialize(communities);
|
||||||
|
_communitiesLoaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelMessage? _findOldestUnreadChannelAnchor(
|
||||||
|
List<ChannelMessage> messages,
|
||||||
|
int unreadCount,
|
||||||
|
) {
|
||||||
|
if (unreadCount <= 0 || messages.isEmpty) return null;
|
||||||
|
var n = 0;
|
||||||
|
ChannelMessage? oldest;
|
||||||
|
for (final m in messages.reversed) {
|
||||||
|
if (m.isOutgoing) continue;
|
||||||
|
n++;
|
||||||
|
oldest = m;
|
||||||
|
if (n >= unreadCount) break;
|
||||||
|
}
|
||||||
|
return oldest;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clearDividerAtBottom() {
|
||||||
|
if (!_scrollController.showJumpToBottom.value &&
|
||||||
|
_unreadDividerMessageId != null) {
|
||||||
|
setState(() => _unreadDividerMessageId = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onTextFieldFocusChange() {
|
void _onTextFieldFocusChange() {
|
||||||
if (_textFieldFocusNode.hasFocus && mounted) {
|
if (_textFieldFocusNode.hasFocus && mounted) {
|
||||||
_scrollController.handleKeyboardOpen();
|
_scrollController.handleKeyboardOpen();
|
||||||
@@ -81,6 +170,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_connector?.setActiveChannel(null);
|
_connector?.setActiveChannel(null);
|
||||||
|
_scrollController.showJumpToBottom.removeListener(_clearDividerAtBottom);
|
||||||
_textFieldFocusNode.removeListener(_onTextFieldFocusChange);
|
_textFieldFocusNode.removeListener(_onTextFieldFocusChange);
|
||||||
_textFieldFocusNode.dispose();
|
_textFieldFocusNode.dispose();
|
||||||
_textController.dispose();
|
_textController.dispose();
|
||||||
@@ -103,11 +193,10 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
Future<void> _scrollToMessage(String messageId) async {
|
Future<void> _scrollToMessage(String messageId) async {
|
||||||
final key = _messageKeys[messageId];
|
final key = _messageKeys[messageId];
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.chat_originalMessageNotFound),
|
content: Text(context.l10n.chat_originalMessageNotFound),
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -123,16 +212,63 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _channelIcon(Channel channel) {
|
||||||
|
// Determine icon based on channel type
|
||||||
|
final ChannelType channelType = Channel.getChannelType(
|
||||||
|
channel,
|
||||||
|
_communityIndex,
|
||||||
|
);
|
||||||
|
final bool isCommunityChannel = Channel.isCommunityChannel(channelType);
|
||||||
|
IconData icon;
|
||||||
|
switch (channelType) {
|
||||||
|
case ChannelType.communityPublic:
|
||||||
|
icon = Icons.groups;
|
||||||
|
case ChannelType.communityHashtag:
|
||||||
|
icon = Icons.tag;
|
||||||
|
case ChannelType.public:
|
||||||
|
icon = Icons.public;
|
||||||
|
case ChannelType.hashtag:
|
||||||
|
icon = Icons.tag;
|
||||||
|
case ChannelType.private:
|
||||||
|
icon = Icons.lock;
|
||||||
|
}
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 3),
|
||||||
|
child: _communitiesLoaded
|
||||||
|
? Icon(icon, size: 20)
|
||||||
|
: SizedBox.square(dimension: 20),
|
||||||
|
),
|
||||||
|
if (isCommunityChannel)
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: Container(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.purple,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).cardColor,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.people, size: 8, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
_channelIcon(widget.channel),
|
||||||
widget.channel.isPublicChannel ? Icons.public : Icons.tag,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -166,6 +302,34 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
|
actions: [
|
||||||
|
const RadioStatsIconButton(),
|
||||||
|
PopupMenuButton<String>(
|
||||||
|
icon: const Icon(Icons.more_vert),
|
||||||
|
onSelected: (value) {
|
||||||
|
if (value == 'clearChat') {
|
||||||
|
context.read<MeshCoreConnector>().clearMessagesForChannel(
|
||||||
|
widget.channel.index,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemBuilder: (context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'clearChat',
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.delete, size: 20, color: Colors.red),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
context.l10n.contact_clearChat,
|
||||||
|
style: const TextStyle(color: Colors.red),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
@@ -213,9 +377,14 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
final reversedMessages = messages.reversed.toList();
|
final reversedMessages = messages.reversed.toList();
|
||||||
final itemCount =
|
final itemCount =
|
||||||
reversedMessages.length + (_isLoadingOlder ? 1 : 0);
|
reversedMessages.length + (_isLoadingOlder ? 1 : 0);
|
||||||
|
final keyedMessageIds = <String>{};
|
||||||
|
|
||||||
// Auto-scroll to bottom if user is already at bottom
|
// Auto-scroll to bottom if user is already at bottom
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (_channelSkipNextBottomSnap) {
|
||||||
|
_channelSkipNextBottomSnap = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
_scrollController.scrollToBottomIfAtBottom();
|
_scrollController.scrollToBottomIfAtBottom();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -245,21 +414,37 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
}
|
}
|
||||||
final messageIndex = index;
|
final messageIndex = index;
|
||||||
final message = reversedMessages[messageIndex];
|
final message = reversedMessages[messageIndex];
|
||||||
if (!_messageKeys.containsKey(message.messageId)) {
|
final shouldAttachMessageKey = keyedMessageIds.add(
|
||||||
|
message.messageId,
|
||||||
|
);
|
||||||
|
if (shouldAttachMessageKey &&
|
||||||
|
!_messageKeys.containsKey(message.messageId)) {
|
||||||
_messageKeys[message.messageId] = GlobalKey();
|
_messageKeys[message.messageId] = GlobalKey();
|
||||||
}
|
}
|
||||||
|
final isUnreadAnchor =
|
||||||
|
_unreadDividerMessageId != null &&
|
||||||
|
message.messageId == _unreadDividerMessageId;
|
||||||
return Container(
|
return Container(
|
||||||
key: _messageKeys[message.messageId]!,
|
key: shouldAttachMessageKey
|
||||||
|
? _messageKeys[message.messageId]
|
||||||
|
: null,
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final textScale = context
|
final textScale = context
|
||||||
.select<ChatTextScaleService, double>(
|
.select<ChatTextScaleService, double>(
|
||||||
(service) => service.scale,
|
(service) => service.scale,
|
||||||
);
|
);
|
||||||
return _buildMessageBubble(
|
final bubble = _buildMessageBubble(
|
||||||
message,
|
message,
|
||||||
textScale,
|
textScale,
|
||||||
);
|
);
|
||||||
|
if (isUnreadAnchor) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [const UnreadDivider(), bubble],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return bubble;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -279,12 +464,32 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _markAsUnread(ChannelMessage message) {
|
||||||
|
final connector = context.read<MeshCoreConnector>();
|
||||||
|
final messages = connector.getChannelMessages(widget.channel);
|
||||||
|
var count = 0;
|
||||||
|
var found = false;
|
||||||
|
for (final m in messages) {
|
||||||
|
if (m.messageId == message.messageId) found = true;
|
||||||
|
if (found && !m.isOutgoing) count++;
|
||||||
|
}
|
||||||
|
connector.setChannelUnreadCount(widget.channel.index, count);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildMessageBubble(ChannelMessage message, double textScale) {
|
Widget _buildMessageBubble(ChannelMessage message, double textScale) {
|
||||||
final settingsService = context.watch<AppSettingsService>();
|
final settingsService = context.watch<AppSettingsService>();
|
||||||
final enableTracing = settingsService.settings.enableMessageTracing;
|
final enableTracing = settingsService.settings.enableMessageTracing;
|
||||||
final isOutgoing = message.isOutgoing;
|
final isOutgoing = message.isOutgoing;
|
||||||
final gifId = _parseGifId(message.text);
|
final gifId = GifHelper.parseGif(message.text);
|
||||||
final poi = _parsePoiMessage(message.text);
|
final poi = parseMarkerText(message.text);
|
||||||
|
final translatedDisplayText =
|
||||||
|
message.translatedText != null &&
|
||||||
|
message.translatedText!.trim().isNotEmpty
|
||||||
|
? message.translatedText!.trim()
|
||||||
|
: message.text;
|
||||||
|
final originalDisplayText = message.isOutgoing
|
||||||
|
? message.originalText
|
||||||
|
: (translatedDisplayText != message.text ? message.text : null);
|
||||||
final displayPath = message.pathBytes.isNotEmpty
|
final displayPath = message.pathBytes.isNotEmpty
|
||||||
? message.pathBytes
|
? message.pathBytes
|
||||||
: (message.pathVariants.isNotEmpty
|
: (message.pathVariants.isNotEmpty
|
||||||
@@ -311,8 +516,13 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
],
|
],
|
||||||
Flexible(
|
Flexible(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => _showMessagePathInfo(message),
|
onTap: PlatformInfo.isDesktop
|
||||||
|
? null
|
||||||
|
: () => _showMessagePathInfo(message),
|
||||||
onLongPress: () => _showMessageActions(message),
|
onLongPress: () => _showMessageActions(message),
|
||||||
|
onSecondaryTapUp: PlatformInfo.isDesktop
|
||||||
|
? (_) => _showMessageActions(message)
|
||||||
|
: null,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: gifId != null
|
padding: gifId != null
|
||||||
? const EdgeInsets.all(4)
|
? const EdgeInsets.all(4)
|
||||||
@@ -359,6 +569,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
poi,
|
poi,
|
||||||
isOutgoing,
|
isOutgoing,
|
||||||
textScale,
|
textScale,
|
||||||
|
message.senderName,
|
||||||
trailing: (!enableTracing && isOutgoing)
|
trailing: (!enableTracing && isOutgoing)
|
||||||
? Padding(
|
? Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 2),
|
padding: const EdgeInsets.only(bottom: 2),
|
||||||
@@ -430,24 +641,17 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Linkify(
|
child: TranslatedMessageContent(
|
||||||
text: message.text,
|
displayText: translatedDisplayText,
|
||||||
|
originalText: originalDisplayText,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: bodyFontSize * textScale,
|
fontSize: bodyFontSize * textScale,
|
||||||
),
|
),
|
||||||
linkStyle: TextStyle(
|
originalStyle: TextStyle(
|
||||||
fontSize: bodyFontSize * textScale,
|
fontSize: bodyFontSize * textScale,
|
||||||
color: Colors.green,
|
fontStyle: FontStyle.italic,
|
||||||
decoration: TextDecoration.underline,
|
color: Theme.of(context).colorScheme.onSurface
|
||||||
),
|
.withValues(alpha: 0.72),
|
||||||
options: const LinkifyOptions(
|
|
||||||
humanize: false,
|
|
||||||
defaultToHttps: false,
|
|
||||||
),
|
|
||||||
linkifiers: const [UrlLinkifier()],
|
|
||||||
onOpen: (link) => LinkHandler.handleLinkTap(
|
|
||||||
context,
|
|
||||||
link.url,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -476,7 +680,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
? const EdgeInsets.symmetric(horizontal: 8)
|
? const EdgeInsets.symmetric(horizontal: 8)
|
||||||
: EdgeInsets.zero,
|
: EdgeInsets.zero,
|
||||||
child: Text(
|
child: Text(
|
||||||
'via ${_formatPathPrefixes(displayPath)}',
|
context.l10n.channels_via(
|
||||||
|
_formatPathPrefixes(displayPath),
|
||||||
|
),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: Colors.grey[600],
|
color: Colors.grey[600],
|
||||||
@@ -497,7 +703,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
_formatTime(message.timestamp),
|
_formatTime(context, message.timestamp),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: Colors.grey[600],
|
color: Colors.grey[600],
|
||||||
@@ -557,7 +763,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isOutgoing) {
|
if (!isOutgoing && !PlatformInfo.isDesktop) {
|
||||||
return _SwipeReplyBubble(
|
return _SwipeReplyBubble(
|
||||||
maxSwipeOffset: maxSwipeOffset,
|
maxSwipeOffset: maxSwipeOffset,
|
||||||
replySwipeThreshold: replySwipeThreshold,
|
replySwipeThreshold: replySwipeThreshold,
|
||||||
@@ -621,8 +827,8 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
final previewTextColor = colorScheme.onSurface.withValues(alpha: 0.7);
|
final previewTextColor = colorScheme.onSurface.withValues(alpha: 0.7);
|
||||||
|
|
||||||
final gifId = _parseGifId(replyText);
|
final gifId = GifHelper.parseGif(replyText);
|
||||||
final poi = _parsePoiMessage(replyText);
|
final poi = parseMarkerText(replyText);
|
||||||
|
|
||||||
Widget contentPreview;
|
Widget contentPreview;
|
||||||
if (gifId != null) {
|
if (gifId != null) {
|
||||||
@@ -733,30 +939,12 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _parseGifId(String text) {
|
|
||||||
final trimmed = text.trim();
|
|
||||||
final match = RegExp(r'^g:([A-Za-z0-9_-]+)$').firstMatch(trimmed);
|
|
||||||
return match?.group(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
_PoiInfo? _parsePoiMessage(String text) {
|
|
||||||
final trimmed = text.trim();
|
|
||||||
final match = RegExp(
|
|
||||||
r'm:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|',
|
|
||||||
).firstMatch(trimmed);
|
|
||||||
if (match == null) return null;
|
|
||||||
final lat = double.tryParse(match.group(1) ?? '');
|
|
||||||
final lon = double.tryParse(match.group(2) ?? '');
|
|
||||||
if (lat == null || lon == null) return null;
|
|
||||||
final label = match.group(3) ?? '';
|
|
||||||
return _PoiInfo(lat: lat, lon: lon, label: label);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildPoiMessage(
|
Widget _buildPoiMessage(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
_PoiInfo poi,
|
MarkerPayload poi,
|
||||||
bool isOutgoing,
|
bool isOutgoing,
|
||||||
double textScale, {
|
double textScale,
|
||||||
|
String senderName, {
|
||||||
Widget? trailing,
|
Widget? trailing,
|
||||||
}) {
|
}) {
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
@@ -776,12 +964,22 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
|
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
final selfName = context.read<MeshCoreConnector>().selfName ?? 'Me';
|
||||||
|
final fromName = isOutgoing ? selfName : senderName;
|
||||||
|
final key = buildSharedMarkerKey(
|
||||||
|
sourceId: 'channel:${widget.channel.index}',
|
||||||
|
label: poi.label,
|
||||||
|
fromName: fromName,
|
||||||
|
flags: poi.flags,
|
||||||
|
isChannel: true,
|
||||||
|
);
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => MapScreen(
|
builder: (context) => MapScreen(
|
||||||
highlightPosition: LatLng(poi.lat, poi.lon),
|
highlightPosition: poi.position,
|
||||||
highlightLabel: poi.label,
|
highlightLabel: poi.label,
|
||||||
|
highlightMarkerKey: key,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -819,7 +1017,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (context) => GifPicker(
|
builder: (context) => GifPicker(
|
||||||
onGifSelected: (gifId) {
|
onGifSelected: (gifId) {
|
||||||
_textController.text = 'g:$gifId';
|
_textController.text = GifHelper.encodeGif(gifId);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -932,7 +1130,11 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
|
|
||||||
Widget _buildMessageComposer() {
|
Widget _buildMessageComposer() {
|
||||||
final connector = context.watch<MeshCoreConnector>();
|
final connector = context.watch<MeshCoreConnector>();
|
||||||
|
if (!connector.isConnected) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
final maxBytes = maxChannelMessageBytes(connector.selfName);
|
final maxBytes = maxChannelMessageBytes(connector.selfName);
|
||||||
|
final settings = context.watch<AppSettingsService>().settings;
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -964,11 +1166,17 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
onPressed: () => _showGifPicker(context),
|
onPressed: () => _showGifPicker(context),
|
||||||
tooltip: context.l10n.chat_sendGif,
|
tooltip: context.l10n.chat_sendGif,
|
||||||
),
|
),
|
||||||
|
if (settings.translationEnabled)
|
||||||
|
MessageTranslationButton(
|
||||||
|
enabled: settings.composerTranslationEnabled,
|
||||||
|
languageCode: settings.translationTargetLanguageCode,
|
||||||
|
onPressed: _showTranslationOptions,
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ValueListenableBuilder<TextEditingValue>(
|
child: ValueListenableBuilder<TextEditingValue>(
|
||||||
valueListenable: _textController,
|
valueListenable: _textController,
|
||||||
builder: (context, value, child) {
|
builder: (context, value, child) {
|
||||||
final gifId = _parseGifId(value.text);
|
final gifId = GifHelper.parseGif(value.text);
|
||||||
if (gifId != null) {
|
if (gifId != null) {
|
||||||
return Focus(
|
return Focus(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
@@ -1013,27 +1221,38 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return ByteCountedTextField(
|
||||||
return TextField(
|
maxBytes: maxBytes,
|
||||||
controller: _textController,
|
controller: _textController,
|
||||||
focusNode: _textFieldFocusNode,
|
focusNode: _textFieldFocusNode,
|
||||||
inputFormatters: [
|
hintText: context.l10n.chat_typeMessage,
|
||||||
Utf8LengthLimitingTextInputFormatter(maxBytes),
|
onSubmitted: (_) => _sendMessage(),
|
||||||
],
|
encoder:
|
||||||
textCapitalization: TextCapitalization.sentences,
|
(connector.isChannelSmazEnabled(
|
||||||
|
widget.channel.index,
|
||||||
|
) ||
|
||||||
|
connector.isChannelCyr2LatEnabled(
|
||||||
|
widget.channel.index,
|
||||||
|
))
|
||||||
|
? (text) => connector.prepareChannelOutboundText(
|
||||||
|
widget.channel.index,
|
||||||
|
text,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: context.l10n.chat_typeMessage,
|
hintText: context.l10n.chat_typeMessage,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
),
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainerLow,
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: 16,
|
horizontal: 20,
|
||||||
vertical: 8,
|
vertical: 14,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
maxLines: null,
|
|
||||||
textInputAction: TextInputAction.send,
|
|
||||||
onSubmitted: (_) => _sendMessage(),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -1041,6 +1260,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.send),
|
icon: const Icon(Icons.send),
|
||||||
|
tooltip: context.l10n.chat_sendMessage,
|
||||||
onPressed: _sendMessage,
|
onPressed: _sendMessage,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
@@ -1051,39 +1271,119 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _sendMessage() {
|
Future<void> _showTranslationOptions() async {
|
||||||
|
final settingsService = context.read<AppSettingsService>();
|
||||||
|
final settings = settingsService.settings;
|
||||||
|
await showMessageTranslationSheet(
|
||||||
|
context: context,
|
||||||
|
enabled: settings.composerTranslationEnabled,
|
||||||
|
selectedLanguageCode: settings.translationTargetLanguageCode,
|
||||||
|
onEnabledChanged: settingsService.setComposerTranslationEnabled,
|
||||||
|
onLanguageSelected: settingsService.setTranslationTargetLanguageCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _sendMessage() async {
|
||||||
|
final connector = context.read<MeshCoreConnector>();
|
||||||
|
if (!connector.isConnected) return;
|
||||||
|
|
||||||
final text = _textController.text.trim();
|
final text = _textController.text.trim();
|
||||||
if (text.isEmpty) return;
|
if (text.isEmpty) return;
|
||||||
|
|
||||||
final connector = context.read<MeshCoreConnector>();
|
final now = DateTime.now();
|
||||||
|
if (_lastChannelSendAt != null &&
|
||||||
|
now.difference(_lastChannelSendAt!) < const Duration(seconds: 1)) {
|
||||||
|
showDismissibleSnackBar(
|
||||||
|
context,
|
||||||
|
content: Text(context.l10n.chat_sendCooldown),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_lastChannelSendAt = now;
|
||||||
|
|
||||||
|
final settings = context.read<AppSettingsService>().settings;
|
||||||
|
final translationService = context.read<TranslationService>();
|
||||||
|
|
||||||
String messageText = text;
|
String messageText = text;
|
||||||
|
String? originalText;
|
||||||
|
String? translatedLanguageCode;
|
||||||
|
String? translationModelId;
|
||||||
|
if (settings.translationEnabled) {
|
||||||
|
final targetLanguageCode = translationService.resolvedTargetLanguageCode(
|
||||||
|
Localizations.localeOf(context).languageCode,
|
||||||
|
);
|
||||||
|
if (translationService.shouldTranslateOutgoing(
|
||||||
|
text: text,
|
||||||
|
targetLanguageCode: targetLanguageCode,
|
||||||
|
)) {
|
||||||
|
final result = await translationService.translateOutgoingText(
|
||||||
|
text: text,
|
||||||
|
targetLanguageCode: targetLanguageCode,
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (result != null &&
|
||||||
|
result.status == MessageTranslationStatus.completed &&
|
||||||
|
result.translatedText.isNotEmpty) {
|
||||||
|
messageText = result.translatedText;
|
||||||
|
originalText = text;
|
||||||
|
translatedLanguageCode = result.targetLanguageCode;
|
||||||
|
translationModelId = result.modelId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (_replyingToMessage != null) {
|
if (_replyingToMessage != null) {
|
||||||
messageText = '@[${_replyingToMessage!.senderName}] $text';
|
messageText = '@[${_replyingToMessage!.senderName}] $messageText';
|
||||||
}
|
}
|
||||||
|
|
||||||
final maxBytes = maxChannelMessageBytes(connector.selfName);
|
final maxBytes = maxChannelMessageBytes(connector.selfName);
|
||||||
if (utf8.encode(messageText).length > maxBytes) {
|
final outboundText = connector.prepareChannelOutboundText(
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
widget.channel.index,
|
||||||
SnackBar(content: Text(context.l10n.chat_messageTooLong(maxBytes))),
|
messageText,
|
||||||
|
);
|
||||||
|
if (utf8.encode(outboundText).length > maxBytes) {
|
||||||
|
showDismissibleSnackBar(
|
||||||
|
context,
|
||||||
|
content: Text(context.l10n.chat_messageTooLong(maxBytes)),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
connector.sendChannelMessage(widget.channel, messageText);
|
// When messageText is transformed with cyr2lat, it (generally) hasn't visual differences,
|
||||||
|
// but we getting messages doubles in chat screen (source text and transformed).
|
||||||
|
// To prevent, we'll perform transform of source before pass to main sender logic.
|
||||||
|
// We can pass whole text, senderName will be kept intact
|
||||||
|
if (connector.isChannelCyr2LatEnabled(widget.channel.index)) {
|
||||||
|
messageText = Cyr2Lat.encode(messageText);
|
||||||
|
}
|
||||||
|
// end transform
|
||||||
|
|
||||||
_textController.clear();
|
_textController.clear();
|
||||||
_cancelReply();
|
_cancelReply();
|
||||||
_textFieldFocusNode.requestFocus();
|
_textFieldFocusNode.requestFocus();
|
||||||
|
connector.sendChannelMessage(
|
||||||
|
widget.channel,
|
||||||
|
messageText,
|
||||||
|
originalText: originalText,
|
||||||
|
translatedLanguageCode: translatedLanguageCode,
|
||||||
|
translationModelId: translationModelId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatTime(DateTime time) {
|
String _formatTime(BuildContext context, DateTime time) {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final diff = now.difference(time);
|
final diff = now.difference(time);
|
||||||
|
final locale = Localizations.localeOf(context).toString();
|
||||||
|
if (locale != _cachedFormatLocale) {
|
||||||
|
_cachedFormatLocale = locale;
|
||||||
|
_hmFormat = DateFormat.Hm(locale);
|
||||||
|
_mdFormat = DateFormat.Md(locale);
|
||||||
|
}
|
||||||
|
final hm = _hmFormat.format(time);
|
||||||
|
|
||||||
if (diff.inDays > 0) {
|
if (diff.inDays > 0) {
|
||||||
return '${time.day}/${time.month} ${time.hour}:${time.minute.toString().padLeft(2, '0')}';
|
return '${_mdFormat.format(time)} $hm';
|
||||||
} else {
|
} else {
|
||||||
return '${time.hour}:${time.minute.toString().padLeft(2, '0')}';
|
return hm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1112,6 +1412,15 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
_setReplyingTo(message);
|
_setReplyingTo(message);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (PlatformInfo.isDesktop)
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.route),
|
||||||
|
title: Text(context.l10n.chat_path),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(sheetContext);
|
||||||
|
_showMessagePathInfo(message);
|
||||||
|
},
|
||||||
|
),
|
||||||
// Can't react to your own messages
|
// Can't react to your own messages
|
||||||
if (!message.isOutgoing)
|
if (!message.isOutgoing)
|
||||||
ListTile(
|
ListTile(
|
||||||
@@ -1130,6 +1439,15 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
_copyMessageText(message.text);
|
_copyMessageText(message.text);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (!message.isOutgoing)
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.mark_chat_unread_outlined),
|
||||||
|
title: Text(context.l10n.chat_markAsUnread),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(sheetContext);
|
||||||
|
_markAsUnread(message);
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.delete_outline),
|
leading: const Icon(Icons.delete_outline),
|
||||||
title: Text(context.l10n.common_delete),
|
title: Text(context.l10n.common_delete),
|
||||||
@@ -1171,23 +1489,25 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
message.senderName,
|
message.senderName,
|
||||||
message.text,
|
message.text,
|
||||||
);
|
);
|
||||||
final reactionText = 'r:$hash:$emojiIndex';
|
final reactionText = ReactionHelper.encodeReaction(hash, emojiIndex);
|
||||||
connector.sendChannelMessage(widget.channel, reactionText);
|
connector.sendChannelMessage(widget.channel, reactionText);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _copyMessageText(String text) {
|
void _copyMessageText(String text) {
|
||||||
Clipboard.setData(ClipboardData(text: text));
|
Clipboard.setData(ClipboardData(text: text));
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(context.l10n.chat_messageCopied)));
|
content: Text(context.l10n.chat_messageCopied),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _deleteMessage(ChannelMessage message) async {
|
Future<void> _deleteMessage(ChannelMessage message) async {
|
||||||
await context.read<MeshCoreConnector>().deleteChannelMessage(message);
|
await context.read<MeshCoreConnector>().deleteChannelMessage(message);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
showDismissibleSnackBar(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(context.l10n.chat_messageDeleted)));
|
content: Text(context.l10n.chat_messageDeleted),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatPathPrefixes(Uint8List pathBytes) {
|
String _formatPathPrefixes(Uint8List pathBytes) {
|
||||||
@@ -1347,11 +1667,3 @@ class _SwipeReplyBubbleState extends State<_SwipeReplyBubble> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PoiInfo {
|
|
||||||
final double lat;
|
|
||||||
final double lon;
|
|
||||||
final String label;
|
|
||||||
|
|
||||||
const _PoiInfo({required this.lat, required this.lon, required this.label});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
@@ -40,8 +40,7 @@ class ChannelMessagePathScreen extends StatelessWidget {
|
|||||||
final primaryPath = !channelMessage && !message.isOutgoing
|
final primaryPath = !channelMessage && !message.isOutgoing
|
||||||
? Uint8List.fromList(primaryPathTmp.reversed.toList())
|
? Uint8List.fromList(primaryPathTmp.reversed.toList())
|
||||||
: primaryPathTmp;
|
: primaryPathTmp;
|
||||||
|
final hops = _buildPathHops(primaryPath, connector, l10n);
|
||||||
final hops = _buildPathHops(primaryPath, connector.contacts, l10n);
|
|
||||||
final hasHopDetails = primaryPath.isNotEmpty;
|
final hasHopDetails = primaryPath.isNotEmpty;
|
||||||
final observedLabel = _formatObservedHops(
|
final observedLabel = _formatObservedHops(
|
||||||
primaryPath.length,
|
primaryPath.length,
|
||||||
@@ -62,8 +61,12 @@ class ChannelMessagePathScreen extends StatelessWidget {
|
|||||||
builder: (context) => PathTraceMapScreen(
|
builder: (context) => PathTraceMapScreen(
|
||||||
title: context.l10n.contacts_repeaterPathTrace,
|
title: context.l10n.contacts_repeaterPathTrace,
|
||||||
path: primaryPath,
|
path: primaryPath,
|
||||||
flipPathRound: true,
|
flipPathAround: true,
|
||||||
reversePathRound: !message.isOutgoing && !channelMessage,
|
reversePathAround:
|
||||||
|
!(!channelMessage && !message.isOutgoing),
|
||||||
|
pathHashByteWidth: context
|
||||||
|
.read<MeshCoreConnector>()
|
||||||
|
.pathHashByteWidth,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -301,11 +304,15 @@ class ChannelMessagePathMapScreen extends StatefulWidget {
|
|||||||
class _ChannelMessagePathMapScreenState
|
class _ChannelMessagePathMapScreenState
|
||||||
extends State<ChannelMessagePathMapScreen> {
|
extends State<ChannelMessagePathMapScreen> {
|
||||||
static const double _labelZoomThreshold = 8.5;
|
static const double _labelZoomThreshold = 8.5;
|
||||||
|
static const double _mapMinZoom = 2.0;
|
||||||
|
static const double _mapMaxZoom = 18.0;
|
||||||
|
|
||||||
|
final MapController _mapController = MapController();
|
||||||
Uint8List? _selectedPath;
|
Uint8List? _selectedPath;
|
||||||
double _pathDistance = 0.0;
|
double _pathDistance = 0.0;
|
||||||
bool _showNodeLabels = true;
|
bool _showNodeLabels = true;
|
||||||
bool _didReceivePositionUpdate = false;
|
bool _didReceivePositionUpdate = false;
|
||||||
|
int? _focusedHopIndex;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -325,6 +332,18 @@ class _ChannelMessagePathMapScreenState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_mapController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isDesktopPlatform(TargetPlatform platform) {
|
||||||
|
return platform == TargetPlatform.linux ||
|
||||||
|
platform == TargetPlatform.windows ||
|
||||||
|
platform == TargetPlatform.macOS;
|
||||||
|
}
|
||||||
|
|
||||||
double _getPathDistance(List<LatLng> points) {
|
double _getPathDistance(List<LatLng> points) {
|
||||||
double totalDistance = 0.0;
|
double totalDistance = 0.0;
|
||||||
final distanceCalculator = Distance();
|
final distanceCalculator = Distance();
|
||||||
@@ -336,6 +355,86 @@ class _ChannelMessagePathMapScreenState
|
|||||||
return totalDistance;
|
return totalDistance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _focusHop(_PathHop hop) {
|
||||||
|
if (!hop.hasLocation) return;
|
||||||
|
final targetZoom = _didReceivePositionUpdate
|
||||||
|
? max(_mapController.camera.zoom, 10.0)
|
||||||
|
: 12.0;
|
||||||
|
_mapController.move(hop.position!, targetZoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onHopTapped(_PathHop hop) {
|
||||||
|
_focusHop(hop);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_focusedHopIndex = hop.index;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _zoomMapBy(double delta) {
|
||||||
|
final camera = _mapController.camera;
|
||||||
|
final nextZoom = (camera.zoom + delta)
|
||||||
|
.clamp(_mapMinZoom, _mapMaxZoom)
|
||||||
|
.toDouble();
|
||||||
|
_mapController.move(camera.center, nextZoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _resetMapView({
|
||||||
|
required LatLng initialCenter,
|
||||||
|
required double initialZoom,
|
||||||
|
required LatLngBounds? bounds,
|
||||||
|
}) {
|
||||||
|
if (bounds != null) {
|
||||||
|
_mapController.fitCamera(
|
||||||
|
CameraFit.bounds(
|
||||||
|
bounds: bounds,
|
||||||
|
padding: const EdgeInsets.all(64),
|
||||||
|
maxZoom: 16,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_mapController.move(initialCenter, initialZoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDesktopMapControls({
|
||||||
|
required LatLng initialCenter,
|
||||||
|
required double initialZoom,
|
||||||
|
required LatLngBounds? bounds,
|
||||||
|
}) {
|
||||||
|
return Positioned(
|
||||||
|
left: 16,
|
||||||
|
top: 16,
|
||||||
|
child: Card(
|
||||||
|
elevation: 4,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
tooltip: 'Zoom in',
|
||||||
|
onPressed: () => _zoomMapBy(1),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.remove),
|
||||||
|
tooltip: 'Zoom out',
|
||||||
|
onPressed: () => _zoomMapBy(-1),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.my_location),
|
||||||
|
tooltip: 'Center map',
|
||||||
|
onPressed: () => _resetMapView(
|
||||||
|
initialCenter: initialCenter,
|
||||||
|
initialZoom: initialZoom,
|
||||||
|
bounds: bounds,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<MeshCoreConnector>(
|
return Consumer<MeshCoreConnector>(
|
||||||
@@ -351,6 +450,7 @@ class _ChannelMessagePathMapScreenState
|
|||||||
primaryPath,
|
primaryPath,
|
||||||
widget.message.pathVariants,
|
widget.message.pathVariants,
|
||||||
);
|
);
|
||||||
|
final isDesktop = _isDesktopPlatform(defaultTargetPlatform);
|
||||||
final selectedPathTmp = _resolveSelectedPath(
|
final selectedPathTmp = _resolveSelectedPath(
|
||||||
_selectedPath,
|
_selectedPath,
|
||||||
observedPaths,
|
observedPaths,
|
||||||
@@ -364,11 +464,7 @@ class _ChannelMessagePathMapScreenState
|
|||||||
: selectedPathTmp;
|
: selectedPathTmp;
|
||||||
|
|
||||||
final selectedIndex = _indexForPath(selectedPath, observedPaths);
|
final selectedIndex = _indexForPath(selectedPath, observedPaths);
|
||||||
final hops = _buildPathHops(
|
final hops = _buildPathHops(selectedPath, connector, context.l10n);
|
||||||
selectedPath,
|
|
||||||
connector.contacts,
|
|
||||||
context.l10n,
|
|
||||||
);
|
|
||||||
|
|
||||||
final points = <LatLng>[];
|
final points = <LatLng>[];
|
||||||
|
|
||||||
@@ -423,6 +519,7 @@ class _ChannelMessagePathMapScreenState
|
|||||||
children: [
|
children: [
|
||||||
FlutterMap(
|
FlutterMap(
|
||||||
key: mapKey,
|
key: mapKey,
|
||||||
|
mapController: _mapController,
|
||||||
options: MapOptions(
|
options: MapOptions(
|
||||||
initialCenter: initialCenter,
|
initialCenter: initialCenter,
|
||||||
initialZoom: initialZoom,
|
initialZoom: initialZoom,
|
||||||
@@ -433,10 +530,20 @@ class _ChannelMessagePathMapScreenState
|
|||||||
padding: const EdgeInsets.all(64),
|
padding: const EdgeInsets.all(64),
|
||||||
maxZoom: 16,
|
maxZoom: 16,
|
||||||
),
|
),
|
||||||
minZoom: 2.0,
|
minZoom: _mapMinZoom,
|
||||||
maxZoom: 18.0,
|
maxZoom: _mapMaxZoom,
|
||||||
interactionOptions: InteractionOptions(
|
interactionOptions: InteractionOptions(
|
||||||
flags: ~InteractiveFlag.rotate,
|
flags: ~InteractiveFlag.rotate,
|
||||||
|
scrollWheelVelocity: isDesktop ? 0.012 : 0.005,
|
||||||
|
cursorKeyboardRotationOptions:
|
||||||
|
CursorKeyboardRotationOptions.disabled(),
|
||||||
|
keyboardOptions: isDesktop
|
||||||
|
? const KeyboardOptions(
|
||||||
|
enableArrowKeysPanning: true,
|
||||||
|
enableWASDPanning: true,
|
||||||
|
enableRFZooming: true,
|
||||||
|
)
|
||||||
|
: const KeyboardOptions.disabled(),
|
||||||
),
|
),
|
||||||
onPositionChanged: (camera, hasGesture) {
|
onPositionChanged: (camera, hasGesture) {
|
||||||
final shouldShow = camera.zoom >= _labelZoomThreshold;
|
final shouldShow = camera.zoom >= _labelZoomThreshold;
|
||||||
@@ -468,12 +575,19 @@ class _ChannelMessagePathMapScreenState
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
_buildDesktopMapControls(
|
||||||
|
initialCenter: initialCenter,
|
||||||
|
initialZoom: initialZoom,
|
||||||
|
bounds: bounds,
|
||||||
|
),
|
||||||
if (observedPaths.length > 1)
|
if (observedPaths.length > 1)
|
||||||
_buildPathSelector(context, observedPaths, selectedIndex, (
|
_buildPathSelector(context, observedPaths, selectedIndex, (
|
||||||
index,
|
index,
|
||||||
) {
|
) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedPath = observedPaths[index].pathBytes;
|
_selectedPath = observedPaths[index].pathBytes;
|
||||||
|
_focusedHopIndex = null;
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
if (points.isEmpty)
|
if (points.isEmpty)
|
||||||
@@ -729,8 +843,17 @@ class _ChannelMessagePathMapScreenState
|
|||||||
separatorBuilder: (_, _) => const Divider(height: 1),
|
separatorBuilder: (_, _) => const Divider(height: 1),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final hop = hops[index];
|
final hop = hops[index];
|
||||||
|
final isFocused = _focusedHopIndex == hop.index;
|
||||||
return ListTile(
|
return ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
|
enabled: hop.hasLocation,
|
||||||
|
selected: isFocused,
|
||||||
|
selectedTileColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary.withValues(alpha: 0.12),
|
||||||
|
onTap: hop.hasLocation
|
||||||
|
? () => _onHopTapped(hop)
|
||||||
|
: null,
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
radius: 14,
|
radius: 14,
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -789,19 +912,83 @@ class _ObservedPath {
|
|||||||
|
|
||||||
List<_PathHop> _buildPathHops(
|
List<_PathHop> _buildPathHops(
|
||||||
Uint8List pathBytes,
|
Uint8List pathBytes,
|
||||||
List<Contact> contacts,
|
MeshCoreConnector connector,
|
||||||
AppLocalizations l10n,
|
AppLocalizations l10n,
|
||||||
) {
|
) {
|
||||||
|
if (pathBytes.isEmpty) return const [];
|
||||||
|
final candidatesByPrefix = <int, List<Contact>>{};
|
||||||
|
final allContacts = connector.allContacts;
|
||||||
|
for (final contact in allContacts) {
|
||||||
|
if (contact.publicKey.isEmpty) continue;
|
||||||
|
if (contact.type != advTypeRepeater && contact.type != advTypeRoom) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final prefix = contact.publicKey.first;
|
||||||
|
candidatesByPrefix.putIfAbsent(prefix, () => <Contact>[]).add(contact);
|
||||||
|
}
|
||||||
|
for (final candidates in candidatesByPrefix.values) {
|
||||||
|
candidates.sort((a, b) => b.lastSeen.compareTo(a.lastSeen));
|
||||||
|
}
|
||||||
|
final startPoint =
|
||||||
|
(connector.selfLatitude != null && connector.selfLongitude != null)
|
||||||
|
? LatLng(connector.selfLatitude!, connector.selfLongitude!)
|
||||||
|
: null;
|
||||||
|
var previousPosition = startPoint;
|
||||||
|
final distance = Distance();
|
||||||
|
var lastDistance = 0.0;
|
||||||
|
var bestDistance = 0.0;
|
||||||
final hops = <_PathHop>[];
|
final hops = <_PathHop>[];
|
||||||
for (var i = 0; i < pathBytes.length; i++) {
|
for (var i = 0; i < pathBytes.length; i++) {
|
||||||
final prefix = pathBytes[i];
|
final searchPoint = i == 0 ? startPoint : previousPosition;
|
||||||
final contact = _matchContactForPrefix(contacts, prefix);
|
final candidates = candidatesByPrefix[pathBytes[i]];
|
||||||
|
Contact? contact;
|
||||||
|
if (candidates != null && candidates.isNotEmpty) {
|
||||||
|
var bestIndex = 0;
|
||||||
|
if (searchPoint != null) {
|
||||||
|
bestDistance = double.infinity;
|
||||||
|
for (var j = 0; j < candidates.length; j++) {
|
||||||
|
final candidate = candidates[j];
|
||||||
|
if (!candidate.hasLocation ||
|
||||||
|
candidate.latitude == null ||
|
||||||
|
candidate.longitude == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final currentDistance = distance(
|
||||||
|
searchPoint,
|
||||||
|
LatLng(candidate.latitude!, candidate.longitude!),
|
||||||
|
);
|
||||||
|
if (currentDistance < bestDistance) {
|
||||||
|
bestDistance = currentDistance;
|
||||||
|
bestIndex = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contact = candidates.removeAt(bestIndex);
|
||||||
|
if (candidates.isEmpty) {
|
||||||
|
candidatesByPrefix.remove(pathBytes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final resolvedPosition = _resolvePosition(contact);
|
||||||
|
if (resolvedPosition != null) {
|
||||||
|
previousPosition = resolvedPosition;
|
||||||
|
}
|
||||||
|
// If the best candidate is much farther than the previous hop, it's likely not the correct match.
|
||||||
|
if (lastDistance + bestDistance > 50000 &&
|
||||||
|
candidates != null &&
|
||||||
|
candidates.isNotEmpty) {
|
||||||
|
i--;
|
||||||
|
lastDistance = bestDistance;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
lastDistance = bestDistance;
|
||||||
|
|
||||||
hops.add(
|
hops.add(
|
||||||
_PathHop(
|
_PathHop(
|
||||||
index: i + 1,
|
index: i + 1,
|
||||||
prefix: prefix,
|
prefix: pathBytes[i],
|
||||||
contact: contact,
|
contact: contact,
|
||||||
position: _resolvePosition(contact),
|
position: resolvedPosition,
|
||||||
l10n: l10n,
|
l10n: l10n,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -809,42 +996,13 @@ List<_PathHop> _buildPathHops(
|
|||||||
return hops;
|
return hops;
|
||||||
}
|
}
|
||||||
|
|
||||||
Contact? _matchContactForPrefix(List<Contact> contacts, int prefix) {
|
|
||||||
final matches = contacts
|
|
||||||
.where(
|
|
||||||
(contact) =>
|
|
||||||
(contact.type == advTypeRepeater || contact.type == advTypeRoom) &&
|
|
||||||
contact.publicKey.isNotEmpty &&
|
|
||||||
contact.publicKey[0] == prefix,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
if (matches.isEmpty) return null;
|
|
||||||
|
|
||||||
Contact? pickWhere(bool Function(Contact) predicate) {
|
|
||||||
for (final contact in matches) {
|
|
||||||
if (predicate(contact)) return contact;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pickWhere((c) => c.type == advTypeRepeater && _hasValidLocation(c)) ??
|
|
||||||
pickWhere((c) => c.type == advTypeRepeater) ??
|
|
||||||
pickWhere(_hasValidLocation) ??
|
|
||||||
matches.first;
|
|
||||||
}
|
|
||||||
|
|
||||||
LatLng? _resolvePosition(Contact? contact) {
|
LatLng? _resolvePosition(Contact? contact) {
|
||||||
if (contact == null) return null;
|
if (contact == null) return null;
|
||||||
if (!_hasValidLocation(contact)) return null;
|
if (!contact.hasLocation) return null;
|
||||||
return LatLng(contact.latitude!, contact.longitude!);
|
final latitude = contact.latitude;
|
||||||
}
|
final longitude = contact.longitude;
|
||||||
|
if (latitude == null || longitude == null) return null;
|
||||||
bool _hasValidLocation(Contact contact) {
|
return LatLng(latitude, longitude);
|
||||||
final lat = contact.latitude;
|
|
||||||
final lon = contact.longitude;
|
|
||||||
if (lat == null || lon == null) return false;
|
|
||||||
if (lat == 0 && lon == 0) return false;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatPrefix(int prefix) {
|
String _formatPrefix(int prefix) {
|
||||||
|
|||||||
+363
-321
File diff suppressed because it is too large
Load Diff
+797
-233
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@ import '../models/community.dart';
|
|||||||
import '../storage/community_store.dart';
|
import '../storage/community_store.dart';
|
||||||
import '../widgets/adaptive_app_bar_title.dart';
|
import '../widgets/adaptive_app_bar_title.dart';
|
||||||
import '../widgets/qr_scanner_widget.dart';
|
import '../widgets/qr_scanner_widget.dart';
|
||||||
|
import '../helpers/snack_bar_builder.dart';
|
||||||
|
|
||||||
/// Screen for scanning community QR codes to join communities.
|
/// Screen for scanning community QR codes to join communities.
|
||||||
///
|
///
|
||||||
@@ -51,6 +52,9 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
|
|||||||
_isProcessing = true;
|
_isProcessing = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final connector = context.read<MeshCoreConnector>();
|
||||||
|
_communityStore.setPublicKeyHex = connector.selfPublicKeyHex;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Parse the community data
|
// Parse the community data
|
||||||
final community = Community.fromQrData(const Uuid().v4(), data);
|
final community = Community.fromQrData(const Uuid().v4(), data);
|
||||||
@@ -73,11 +77,10 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.community_invalidQrCode),
|
content: Text(context.l10n.community_invalidQrCode),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -90,12 +93,11 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showInvalidQrError(BuildContext context) {
|
void _showInvalidQrError(BuildContext context) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.community_invalidQrCode),
|
content: Text(context.l10n.community_invalidQrCode),
|
||||||
backgroundColor: Colors.orange,
|
backgroundColor: Colors.orange,
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,6 +211,8 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
|
|||||||
bool addPublicChannel,
|
bool addPublicChannel,
|
||||||
) async {
|
) async {
|
||||||
// Save community to local storage
|
// Save community to local storage
|
||||||
|
final connector = context.read<MeshCoreConnector>();
|
||||||
|
_communityStore.setPublicKeyHex = connector.selfPublicKeyHex;
|
||||||
await _communityStore.addCommunity(community);
|
await _communityStore.addCommunity(community);
|
||||||
|
|
||||||
// Optionally add the community public channel to the device
|
// Optionally add the community public channel to the device
|
||||||
@@ -224,11 +228,10 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showDismissibleSnackBar(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(context.l10n.community_joined(community.name)),
|
content: Text(context.l10n.community_joined(community.name)),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Return to previous screen
|
// Return to previous screen
|
||||||
|
|||||||
@@ -0,0 +1,252 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:meshcore_open/connector/meshcore_connector.dart';
|
||||||
|
import 'package:meshcore_open/models/companion_radio_stats.dart';
|
||||||
|
import 'package:meshcore_open/l10n/l10n.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class CompanionRadioStatsScreen extends StatefulWidget {
|
||||||
|
const CompanionRadioStatsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CompanionRadioStatsScreen> createState() =>
|
||||||
|
_CompanionRadioStatsScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CompanionRadioStatsScreenState extends State<CompanionRadioStatsScreen> {
|
||||||
|
final List<double> _noiseHistory = [];
|
||||||
|
static const int _maxSamples = 120;
|
||||||
|
MeshCoreConnector? _connector;
|
||||||
|
DateTime? _lastChartSampleAt;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final c = context.read<MeshCoreConnector>();
|
||||||
|
_connector = c;
|
||||||
|
c.acquireRadioStatsPolling();
|
||||||
|
c.setPollingInterval(1);
|
||||||
|
c.radioStatsNotifier.addListener(_onStatsUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onStatsUpdate() {
|
||||||
|
final s = _connector?.radioStatsNotifier.value;
|
||||||
|
if (s == null || !mounted) return;
|
||||||
|
if (_lastChartSampleAt == s.receivedAt) return;
|
||||||
|
_lastChartSampleAt = s.receivedAt;
|
||||||
|
setState(() {
|
||||||
|
_noiseHistory.add(s.noiseFloorDbm.toDouble());
|
||||||
|
while (_noiseHistory.length > _maxSamples) {
|
||||||
|
_noiseHistory.removeAt(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_connector?.radioStatsNotifier.removeListener(_onStatsUpdate);
|
||||||
|
_connector?.releaseRadioStatsPolling();
|
||||||
|
_connector?.setPollingInterval(30);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(l10n.radioStats_screenTitle),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: Selector<MeshCoreConnector, ({bool connected, bool supported})>(
|
||||||
|
selector: (_, c) => (
|
||||||
|
connected: c.isConnected,
|
||||||
|
supported: c.supportsCompanionRadioStats,
|
||||||
|
),
|
||||||
|
builder: (context, state, _) {
|
||||||
|
if (!state.connected) {
|
||||||
|
return Center(child: Text(l10n.radioStats_notConnected));
|
||||||
|
}
|
||||||
|
if (!state.supported) {
|
||||||
|
return Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Text(
|
||||||
|
l10n.radioStats_firmwareTooOld,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final connector = context.read<MeshCoreConnector>();
|
||||||
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final tt = Theme.of(context).textTheme;
|
||||||
|
|
||||||
|
return ValueListenableBuilder<CompanionRadioStats?>(
|
||||||
|
valueListenable: connector.radioStatsNotifier,
|
||||||
|
builder: (context, stats, _) {
|
||||||
|
return ListView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
children: [
|
||||||
|
if (stats != null) ...[
|
||||||
|
Text(
|
||||||
|
l10n.radioStats_noiseFloor(stats.noiseFloorDbm),
|
||||||
|
style: tt.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(l10n.radioStats_lastRssi(stats.lastRssiDbm)),
|
||||||
|
Text(
|
||||||
|
l10n.radioStats_lastSnr(
|
||||||
|
stats.lastSnrDb.toStringAsFixed(1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(l10n.radioStats_txAir(stats.txAirSecs)),
|
||||||
|
Text(l10n.radioStats_rxAir(stats.rxAirSecs)),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
] else
|
||||||
|
Text(l10n.radioStats_waiting),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: CustomPaint(
|
||||||
|
painter: _NoiseChartPainter(
|
||||||
|
samples: List<double>.from(_noiseHistory),
|
||||||
|
colorScheme: scheme,
|
||||||
|
textTheme: tt,
|
||||||
|
),
|
||||||
|
child: const SizedBox.expand(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
l10n.radioStats_chartCaption,
|
||||||
|
style: tt.bodySmall?.copyWith(
|
||||||
|
color: scheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NoiseChartPainter extends CustomPainter {
|
||||||
|
final List<double> samples;
|
||||||
|
final ColorScheme colorScheme;
|
||||||
|
final TextTheme textTheme;
|
||||||
|
|
||||||
|
_NoiseChartPainter({
|
||||||
|
required this.samples,
|
||||||
|
required this.colorScheme,
|
||||||
|
required this.textTheme,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final bg = Paint()..color = colorScheme.surfaceContainerHighest;
|
||||||
|
final border = Paint()
|
||||||
|
..color = colorScheme.outlineVariant
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 1;
|
||||||
|
final grid = Paint()
|
||||||
|
..color = colorScheme.outlineVariant.withValues(alpha: 0.5)
|
||||||
|
..strokeWidth = 1;
|
||||||
|
final line = Paint()
|
||||||
|
..color = colorScheme.primary
|
||||||
|
..strokeWidth = 2
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
|
||||||
|
final rect = Rect.fromLTWH(0, 0, size.width, size.height);
|
||||||
|
canvas.drawRRect(
|
||||||
|
RRect.fromRectAndRadius(rect, const Radius.circular(8)),
|
||||||
|
bg,
|
||||||
|
);
|
||||||
|
canvas.drawRRect(
|
||||||
|
RRect.fromRectAndRadius(rect, const Radius.circular(8)),
|
||||||
|
border,
|
||||||
|
);
|
||||||
|
|
||||||
|
const padL = 40.0;
|
||||||
|
const padR = 8.0;
|
||||||
|
const padT = 8.0;
|
||||||
|
const padB = 24.0;
|
||||||
|
final chart = Rect.fromLTRB(
|
||||||
|
padL,
|
||||||
|
padT,
|
||||||
|
size.width - padR,
|
||||||
|
size.height - padB,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (var i = 0; i <= 4; i++) {
|
||||||
|
final y = chart.top + (chart.height * i / 4);
|
||||||
|
canvas.drawLine(Offset(chart.left, y), Offset(chart.right, y), grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samples.length < 2) {
|
||||||
|
final tp = TextPainter(
|
||||||
|
text: TextSpan(
|
||||||
|
text: '—',
|
||||||
|
style: textTheme.bodySmall?.copyWith(
|
||||||
|
color: colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
)..layout();
|
||||||
|
tp.paint(
|
||||||
|
canvas,
|
||||||
|
Offset(chart.left + 4, chart.top + chart.height / 2 - tp.height / 2),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double minV = samples.reduce((a, b) => a < b ? a : b);
|
||||||
|
double maxV = samples.reduce((a, b) => a > b ? a : b);
|
||||||
|
if ((maxV - minV).abs() < 1) {
|
||||||
|
minV -= 2;
|
||||||
|
maxV += 2;
|
||||||
|
}
|
||||||
|
final span = maxV - minV;
|
||||||
|
|
||||||
|
for (var i = 0; i <= 2; i++) {
|
||||||
|
final v = maxV - span * i / 2;
|
||||||
|
final tp = _yAxisLabel(v);
|
||||||
|
final y = chart.top + (chart.height * i / 2) - tp.height / 2;
|
||||||
|
tp.paint(canvas, Offset(4, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
final path = Path();
|
||||||
|
for (var i = 0; i < samples.length; i++) {
|
||||||
|
final x = chart.left + (chart.width * i / (samples.length - 1));
|
||||||
|
final t = (samples[i] - minV) / span;
|
||||||
|
final y = chart.bottom - t * chart.height;
|
||||||
|
if (i == 0) {
|
||||||
|
path.moveTo(x, y);
|
||||||
|
} else {
|
||||||
|
path.lineTo(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canvas.drawPath(path, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant _NoiseChartPainter oldDelegate) {
|
||||||
|
return oldDelegate.samples.length != samples.length ||
|
||||||
|
oldDelegate.colorScheme != colorScheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextPainter _yAxisLabel(double v) {
|
||||||
|
final tp = TextPainter(
|
||||||
|
text: TextSpan(
|
||||||
|
text: v.round().toString(),
|
||||||
|
style: textTheme.labelSmall?.copyWith(
|
||||||
|
color: colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
)..layout();
|
||||||
|
return tp;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user